diff --git a/.env.template b/.env.template index 3cddb235..e06d4606 100644 --- a/.env.template +++ b/.env.template @@ -1,5 +1,5 @@ # ==================================================== -# MiroTalk P2P v.1.8.00 - Environment Configuration +# MiroTalk P2P v.1.8.01 - Environment Configuration # ==================================================== # App environment diff --git a/app/src/config.template.js b/app/src/config.template.js index 65accc6f..119ac279 100644 --- a/app/src/config.template.js +++ b/app/src/config.template.js @@ -2,7 +2,7 @@ /** * ============================================== - * MiroTalk P2P v.1.8.00 - Configuration File + * MiroTalk P2P v.1.8.01 - Configuration File * ============================================== * * This file is the central configuration source. diff --git a/app/src/server.js b/app/src/server.js index a439c823..c1f9190e 100755 --- a/app/src/server.js +++ b/app/src/server.js @@ -45,7 +45,7 @@ dependencies: { * @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.8.00 + * @version 1.8.01 * */ diff --git a/package-lock.json b/package-lock.json index 6353318a..7334ae3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mirotalk", - "version": "1.8.00", + "version": "1.8.01", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mirotalk", - "version": "1.8.00", + "version": "1.8.01", "license": "AGPL-3.0", "dependencies": { "@mattermost/client": "11.5.0", diff --git a/package.json b/package.json index f6ea3f7e..4a37b590 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalk", - "version": "1.8.00", + "version": "1.8.01", "description": "A free WebRTC browser-based video call", "main": "server.js", "scripts": { diff --git a/public/css/activeRooms.css b/public/css/activeRooms.css index f9783345..95451362 100644 --- a/public/css/activeRooms.css +++ b/public/css/activeRooms.css @@ -1,195 +1,347 @@ -@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@500&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap'); :root { - --primary-bg: #1e2229; - --card-bg: #15181d; - --accent: #1870d7; - --accent2: #2186ff; - --text: #f6f8fa; - --card-shadow: 0 2px 8px #15181d; + --primary-bg: #0f1117; + --container-bg: #181a20; + --card-bg: #1e2128; + --card-bg-hover: #252830; + --accent: #3b82f6; + --accent-hover: #2563eb; + --accent-glow: rgba(59, 130, 246, 0.15); + --text: #e8ecf1; + --text-muted: #8b95a5; + --border: #2a2d35; + --success: #22c55e; + --gold: #fbbf24; + --card-shadow: 0 4px 24px rgba(0, 0, 0, 0.25); + --transition: 0.2s ease; +} + +* { + box-sizing: border-box; } body { - font-family: 'Montserrat'; + font-family: 'Montserrat', sans-serif; background: var(--primary-bg); margin: 0; padding: 0; color: var(--text); + min-height: 100vh; } .container { - max-width: 1200px; - margin: 40px auto; - background: var(--card-bg); - border-radius: 12px; - box-shadow: var(--card-shadow); - padding: 32px; + max-width: 1280px; + margin: 0 auto; + padding: 32px 24px; } -h1 { - text-align: center; - color: var(--accent2); - margin-bottom: 32px; - letter-spacing: 1px; -} - -.search-bar { +/* Header */ +.header { display: flex; - justify-content: center; + align-items: center; + justify-content: space-between; margin-bottom: 28px; } -.search-input { - padding: 10px 16px; - border-radius: 8px 0 0 8px; - border: none; - font-size: 1.1em; - background: #23262e; - color: var(--text); - outline: none; - width: 220px; -} - -.search-btn { - padding: 10px 20px; - border-radius: 0 8px 8px 0; - border: none; - background: var(--accent); - color: #fff; - font-size: 1.1em; - cursor: pointer; - font-weight: bold; - transition: background 0.2s; -} - -.search-btn:hover { - background: var(--accent2); -} - -.rooms { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 28px; - justify-content: center; -} - -.room-card { - background: var(--accent); - border-radius: 12px; - box-shadow: 0 1px 6px #15181d; - padding: 32px 24px; - min-height: 220px; - text-align: center; - transition: - box-shadow 0.2s, - transform 0.2s; - color: var(--text); - position: relative; +.header-left { display: flex; - flex-direction: column; - justify-content: flex-start; + align-items: center; + gap: 12px; } -.room-card:hover { - cursor: pointer; - transform: translateY(-4px) scale(1.03); +.header-left i { + font-size: 1.5em; + color: var(--accent); } -.room-title { - font-size: 1.15em; - color: #f6f8fa; - margin-bottom: 14px; - font-weight: bold; - letter-spacing: 0.5px; - word-break: break-all; - overflow-wrap: anywhere; - max-width: 100%; +.header h1 { + font-size: 1.5em; + font-weight: 700; + color: var(--text); + margin: 0; + letter-spacing: -0.3px; } -.peer-count { - font-size: 2.3em; - color: #ffd700; - font-weight: bold; - margin-bottom: 8px; - margin-top: auto; - align-self: center; -} - -.peer-label { - font-size: 0.9em; - color: #f6f8fa; - opacity: 0.7; - margin-bottom: 60px; - align-self: center; -} - -.empty { - text-align: center; - color: #b3c6e0; - margin-top: 40px; - font-size: 1.2em; +.room-count-badge { + background: var(--accent-glow); + color: var(--accent); + font-size: 0.75em; + font-weight: 600; + padding: 4px 10px; + border-radius: 20px; + border: 1px solid rgba(59, 130, 246, 0.25); } .refresh-btn { - display: block; - margin: 0 auto 32px auto; - padding: 10px 28px; - background: var(--accent2); - color: #fff; - border: none; - border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: var(--card-bg); + color: var(--text-muted); + border: 1px solid var(--border); + border-radius: 10px; cursor: pointer; - font-size: 1.1em; - font-weight: bold; - letter-spacing: 0.5px; - box-shadow: 0 1px 4px #15181d; - transition: background 0.2s; + font-size: 1em; + transition: all var(--transition); } .refresh-btn:hover { background: var(--accent); + color: #fff; + border-color: var(--accent); +} + +.refresh-btn.spinning i { + animation: spin 0.6s ease; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +/* Search */ +.search-bar { + position: relative; + margin-bottom: 28px; +} + +.search-bar i { + position: absolute; + left: 14px; + top: 50%; + transform: translateY(-50%); + color: var(--text-muted); + font-size: 0.9em; + pointer-events: none; +} + +.search-input { + width: 100%; + padding: 12px 16px 12px 40px; + border-radius: 10px; + border: 1px solid var(--border); + font-size: 0.95em; + font-family: 'Montserrat', sans-serif; + background: var(--card-bg); + color: var(--text); + outline: none; + transition: + border-color var(--transition), + box-shadow var(--transition); +} + +.search-input::placeholder { + color: var(--text-muted); +} + +.search-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-glow); +} + +/* Stats summary */ +.stats-bar { + display: flex; + gap: 16px; + margin-bottom: 28px; +} + +.stat-item { + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: 12px; + padding: 16px 24px; + flex: 1; + text-align: center; +} + +.stat-value { + font-size: 1.8em; + font-weight: 700; + color: var(--text); + line-height: 1.2; +} + +.stat-value.accent { + color: var(--accent); +} + +.stat-value.gold { + color: var(--gold); +} + +.stat-label { + font-size: 0.75em; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1px; + margin-top: 4px; +} + +/* Room grid */ +.rooms { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 16px; +} + +.room-card { + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: 14px; + padding: 20px; + transition: + background 0.2s ease, + border-color 0.2s ease, + transform 0.2s ease; + color: var(--text); + display: flex; + flex-direction: column; + gap: 16px; +} + +.room-card:hover { + background: var(--card-bg-hover); + border-color: var(--accent); + transform: translateY(-2px); +} + +.room-card-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.room-title { + font-size: 0.85em; + color: var(--text); + font-weight: 600; + word-break: break-all; + overflow-wrap: anywhere; + line-height: 1.4; + flex: 1; +} + +.room-title i { + color: var(--accent); + margin-right: 6px; +} + +.peer-badge { + display: flex; + align-items: center; + gap: 6px; + background: var(--accent-glow); + color: var(--accent); + padding: 4px 10px; + border-radius: 20px; + font-size: 0.8em; + font-weight: 600; + white-space: nowrap; + border: 1px solid rgba(59, 130, 246, 0.2); +} + +.peer-badge i { + font-size: 0.85em; +} + +.room-card-footer { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: auto; +} + +.peer-status { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.8em; + color: var(--text-muted); +} + +.peer-status .dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--success); + display: inline-block; + transition: box-shadow 0.3s ease; } .join-btn { - display: block; - margin: 0 auto 0 auto; - position: absolute; - left: 50%; - bottom: 18px; - transform: translateX(-50%); - padding: 10px 24px; - background: var(--accent2); + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 18px; + background: var(--accent); color: #fff; border-radius: 8px; - font-size: 1em; - font-weight: bold; + font-size: 0.85em; + font-weight: 600; text-decoration: none; - transition: background 0.2s; - box-shadow: 0 1px 4px #15181d; + transition: all var(--transition); + font-family: 'Montserrat', sans-serif; } .join-btn:hover { - background: var(--accent); + background: var(--accent-hover); + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.35); } +/* Empty state */ +.empty { + text-align: center; + color: var(--text-muted); + padding: 60px 20px; + font-size: 1em; + grid-column: 1 / -1; +} + +.empty i { + display: block; + font-size: 2.5em; + margin-bottom: 16px; + opacity: 0.4; +} + +/* Responsive */ @media (max-width: 900px) { .container { - max-width: 98vw; - padding: 16px; + padding: 20px 16px; } - .rooms { - gap: 16px; + .stats-bar { + gap: 10px; + } + .stat-item { + padding: 12px 16px; + } + .stat-value { + font-size: 1.4em; } } @media (max-width: 600px) { + .header { + flex-wrap: wrap; + gap: 12px; + } + .stats-bar { + flex-direction: column; + } .rooms { grid-template-columns: 1fr; } - .room-card { - padding: 20px 10px; - } - .peer-label { - margin-bottom: 70px; + .header h1 { + font-size: 1.2em; } } diff --git a/public/js/activeRooms.js b/public/js/activeRooms.js index 777651a0..39f6ffeb 100644 --- a/public/js/activeRooms.js +++ b/public/js/activeRooms.js @@ -6,13 +6,20 @@ const isTest = false; // Set to true for testing with mock data const roomsDiv = document.getElementById('rooms'); const searchInput = document.getElementById('searchInput'); -const searchBtn = document.getElementById('search-btn'); const refreshBtn = document.getElementById('refresh-btn'); +const roomCountBadge = document.getElementById('roomCountBadge'); +const statRooms = document.getElementById('statRooms'); +const statPeers = document.getElementById('statPeers'); let allRooms = []; -searchBtn.addEventListener('click', handleSearch); -refreshBtn.addEventListener('click', fetchRooms); +searchInput.addEventListener('input', handleSearch); +refreshBtn.addEventListener('click', () => { + refreshBtn.classList.add('spinning'); + fetchRooms().finally(() => { + setTimeout(() => refreshBtn.classList.remove('spinning'), 600); + }); +}); function setRoomsContent(html) { roomsDiv.innerHTML = html; @@ -39,22 +46,31 @@ function getUUID() { ); } +function updateStats(rooms) { + const totalPeers = rooms.reduce((sum, r) => sum + r.peers, 0); + statRooms.textContent = rooms.length; + statPeers.textContent = totalPeers; + roomCountBadge.textContent = rooms.length === 1 ? '1 room' : `${rooms.length} rooms`; +} + async function fetchRooms() { - setRoomsContent('
Loading...
'); + setRoomsContent('
Loading rooms...
'); try { const res = await axios.get('/api/v1/activeRooms'); if (res.status !== 200) throw new Error('Failed to fetch active rooms'); allRooms = getRoomsData(res); + updateStats(allRooms); renderRooms(allRooms); } catch (err) { const errorMsg = err.response?.data?.error || err.message; - setRoomsContent(`
${errorMsg}
`); + setRoomsContent(`
${errorMsg}
`); + updateStats([]); } } function renderRooms(rooms) { if (!rooms.length) { - setRoomsContent('
No active rooms found.
'); + setRoomsContent('
No active rooms found.
'); return; } setRoomsContent( @@ -62,18 +78,24 @@ function renderRooms(rooms) { .map( (room) => `
-
- - ${room.id} +
+
+ ${room.id} +
+
+ + ${room.peers} +
-
- - ${room.peers} + -
${room.peers === 1 ? 'peer' : 'peers'}
- - Join -
` ) @@ -83,7 +105,8 @@ function renderRooms(rooms) { function handleSearch() { const value = searchInput.value.trim().toLowerCase(); - renderRooms(!value ? allRooms : allRooms.filter((room) => room.id.toLowerCase().includes(value))); + const filtered = !value ? allRooms : allRooms.filter((room) => room.id.toLowerCase().includes(value)); + renderRooms(filtered); } fetchRooms(); diff --git a/public/js/brand.js b/public/js/brand.js index 918acc21..460006cf 100644 --- a/public/js/brand.js +++ b/public/js/brand.js @@ -109,7 +109,7 @@ let brand = { }, about: { imageUrl: '../images/mirotalk-logo.gif', - title: 'WebRTC P2P v1.8.00', + title: 'WebRTC P2P v1.8.01', html: ` +
+
+ +

Active Rooms

+ 0 rooms +
+ +
+ +
+
+
0
+
Rooms
+
+
+
0
+
Total Peers
+
-