Added vote feature for chat parcipants

Added statusbar
This commit is contained in:
defnax 2026-07-17 19:42:35 +02:00
parent 2053a29867
commit 9cb3b9bb4c
4 changed files with 521 additions and 113 deletions

View File

@ -247,6 +247,15 @@ const Message = () => {
} }
} }
const isMuted = ChatHubState.mutedUsers && ChatHubState.mutedUsers.has(gxsId);
const details = ChatHubState.gxsDetails[gxsId];
const opinion = details && details.mReputation ? details.mReputation.mOwnOpinion : 1;
const isBanned = opinion === 0;
if (isMuted || isBanned) {
return null;
}
let username = rs.userList.username(gxsId) || msg.peerName || '???'; let username = rs.userList.username(gxsId) || msg.peerName || '???';
// If we only have the hex ID, try to fallback to the peerName from the message // If we only have the hex ID, try to fallback to the peerName from the message
if (username === gxsId && msg.peerName) { if (username === gxsId && msg.peerName) {
@ -695,8 +704,10 @@ const ChatHubState = {
newRoomTopic: '', newRoomTopic: '',
newRoomIdentity: '', newRoomIdentity: '',
newRoomPublic: true, newRoomPublic: true,
newRoomSigned: false,
ownGxsIdentities: [], ownGxsIdentities: [],
createRoomError: '', createRoomError: '',
userSortMethod: 'name',
}; };
// ========================= Emoji Data ========================= // ========================= Emoji Data =========================
@ -1240,109 +1251,160 @@ const ChatConversationView = () => {
]), ]),
m('.chat-hub-rightbar', [ m('.chat-hub-rightbar', [
m('.rightbar-title', 'Participants'), m('.rightbar-title', 'Participants'),
m('.rightbar-users-list', ChatLobbyModel.users.map((user) => { m('.rightbar-users-list', (() => {
const gxsId = user.key; const sortedUsers = [...ChatLobbyModel.users];
const name = user.name; if (ChatHubState.userSortMethod === 'activity') {
sortedUsers.sort((a, b) => b.lastAct - a.lastAct);
// Load details for avatar if not cached } else {
if (gxsId && ChatHubState.gxsDetails[gxsId] === undefined) { sortedUsers.sort((a, b) => a.name.localeCompare(b.name));
ChatHubState.gxsDetails[gxsId] = null; // Mark as loading
rs.rsJsonApiRequest('/rsIdentity/getIdDetails', { id: gxsId }, (data) => {
if (data && data.details) {
ChatHubState.gxsDetails[gxsId] = data.details;
m.redraw();
}
});
} }
return sortedUsers.map((user) => {
const gxsId = user.key;
const name = user.name;
const details = ChatHubState.gxsDetails[gxsId]; // Load details for avatar if not cached
const avatar = getSafeAvatar(details); if (gxsId && ChatHubState.gxsDetails[gxsId] === undefined) {
const firstLetter = (name || '?').slice(0, 1).toUpperCase(); ChatHubState.gxsDetails[gxsId] = null; // Mark as loading
rs.rsJsonApiRequest('/rsIdentity/getIdDetails', { id: gxsId }, (data) => {
// Calculate status color and tooltip if (data && data.details) {
const now = Math.floor(Date.now() / 1000); ChatHubState.gxsDetails[gxsId] = data.details;
const tLastAct = user.lastAct || 0; m.redraw();
const isOwn = gxsId === rs.idToHex(ChatLobbyModel.currentLobby.gxs_id || '');
const isMuted = ChatHubState.mutedUsers && ChatHubState.mutedUsers.has(gxsId);
let statusColor = '#22c55e'; // active (green)
let statusTooltip = 'Active';
if (isMuted) {
statusColor = '#ef4444'; // muted (red)
statusTooltip = 'Muted';
} else if (isOwn) {
statusColor = '#3ba4d7'; // own identity (blue)
statusTooltip = 'You';
} else if (tLastAct + 600 < now) {
statusColor = '#cbd5e1'; // inactive > 10 mins (grey)
statusTooltip = 'Inactive';
} else if (tLastAct + 300 < now) {
statusColor = '#eab308'; // away > 5 mins (yellow)
statusTooltip = 'Away';
}
return m('.user', {
onmouseenter: (e) => {
if (ChatHubState.activeMenu) return; // skip tooltip if menu is open
const rect = e.currentTarget.getBoundingClientRect();
const rightbar = document.querySelector('.chat-hub-rightbar');
if (rightbar) {
const parentRect = rightbar.getBoundingClientRect();
const top = rect.top - parentRect.top + rect.height / 2;
ChatHubState.hoveredUser = { gxsId, name, top };
}
},
onmouseleave: () => {
ChatHubState.hoveredUser = null;
},
onclick: (e) => {
e.preventDefault();
e.stopPropagation();
ChatHubState.hoveredUser = null; // hide tooltip
const rect = e.currentTarget.getBoundingClientRect();
const rightbar = document.querySelector('.chat-hub-rightbar');
if (rightbar) {
const parentRect = rightbar.getBoundingClientRect();
const top = rect.bottom - parentRect.top;
if (ChatHubState.activeMenu && ChatHubState.activeMenu.gxsId === gxsId) {
ChatHubState.activeMenu = null;
} else {
ChatHubState.activeMenu = { gxsId, name, top };
} }
m.redraw(); });
}
},
oncontextmenu: (e) => {
e.preventDefault();
e.stopPropagation();
ChatHubState.hoveredUser = null; // hide tooltip
const rect = e.currentTarget.getBoundingClientRect();
const rightbar = document.querySelector('.chat-hub-rightbar');
if (rightbar) {
const parentRect = rightbar.getBoundingClientRect();
const top = rect.bottom - parentRect.top;
ChatHubState.activeMenu = { gxsId, name, top };
m.redraw();
}
} }
}, [
m(peopleUtil.UserAvatar, { avatar, firstLetter, identityId: gxsId, size: 32 }), const details = ChatHubState.gxsDetails[gxsId];
m('span.user-name', name), const avatar = getSafeAvatar(details);
statusColor !== '#22c55e' && m('i.fas.fa-circle', { const firstLetter = (name || '?').slice(0, 1).toUpperCase();
style: {
color: statusColor, const opinion = details && details.mReputation ? details.mReputation.mOwnOpinion : 1;
fontSize: '0.65rem', const isBanned = opinion === 0;
marginLeft: 'auto', if (isBanned) return null;
flexShrink: 0,
transition: 'color 0.3s ease', // Calculate status color and tooltip
const now = Math.floor(Date.now() / 1000);
const tLastAct = user.lastAct || 0;
const isOwn = gxsId === rs.idToHex(ChatLobbyModel.currentLobby.gxs_id || '');
const isMuted = ChatHubState.mutedUsers && ChatHubState.mutedUsers.has(gxsId);
let statusColor = '#22c55e'; // active (green)
let statusTooltip = 'Active';
if (isMuted) {
statusColor = '#ef4444'; // muted (red)
statusTooltip = 'Muted';
} else if (isOwn) {
statusColor = '#3ba4d7'; // own identity (blue)
statusTooltip = 'You';
} else if (tLastAct + 600 < now) {
statusColor = '#cbd5e1'; // inactive > 10 mins (grey)
statusTooltip = 'Inactive';
} else if (tLastAct + 300 < now) {
statusColor = '#eab308'; // away > 5 mins (yellow)
statusTooltip = 'Away';
}
return m('.user', {
onmouseenter: (e) => {
if (ChatHubState.activeMenu) return; // skip tooltip if menu is open
const rect = e.currentTarget.getBoundingClientRect();
const rightbar = document.querySelector('.chat-hub-rightbar');
if (rightbar) {
const parentRect = rightbar.getBoundingClientRect();
const top = rect.top - parentRect.top + rect.height / 2;
ChatHubState.hoveredUser = { gxsId, name, top };
}
}, },
title: statusTooltip onmouseleave: () => {
}) ChatHubState.hoveredUser = null;
]); },
})), onclick: (e) => {
e.preventDefault();
e.stopPropagation();
ChatHubState.hoveredUser = null; // hide tooltip
const rect = e.currentTarget.getBoundingClientRect();
const rightbar = document.querySelector('.chat-hub-rightbar');
if (rightbar) {
const parentRect = rightbar.getBoundingClientRect();
const itemBottom = rect.bottom - parentRect.top;
const estimatedMenuHeight = 310;
let top = itemBottom;
if (itemBottom + estimatedMenuHeight > parentRect.height) {
top = rect.top - parentRect.top - estimatedMenuHeight;
if (top < 10) top = 10;
}
if (ChatHubState.activeMenu && ChatHubState.activeMenu.gxsId === gxsId) {
ChatHubState.activeMenu = null;
} else {
ChatHubState.activeMenu = { gxsId, name, top };
}
m.redraw();
}
},
oncontextmenu: (e) => {
e.preventDefault();
e.stopPropagation();
ChatHubState.hoveredUser = null; // hide tooltip
const rect = e.currentTarget.getBoundingClientRect();
const rightbar = document.querySelector('.chat-hub-rightbar');
if (rightbar) {
const parentRect = rightbar.getBoundingClientRect();
const itemBottom = rect.bottom - parentRect.top;
const estimatedMenuHeight = 310;
let top = itemBottom;
if (itemBottom + estimatedMenuHeight > parentRect.height) {
top = rect.top - parentRect.top - estimatedMenuHeight;
if (top < 10) top = 10;
}
ChatHubState.activeMenu = { gxsId, name, top };
m.redraw();
}
}
}, [
m(peopleUtil.UserAvatar, { avatar, firstLetter, identityId: gxsId, size: 32 }),
m('span.user-name', name),
(() => {
if (isBanned) {
return m('i.fas.fa-ban', {
style: {
color: '#ef4444',
fontSize: '0.85rem',
marginLeft: 'auto',
flexShrink: 0,
},
title: 'Banned'
});
}
if (isMuted) {
return m('i.fas.fa-volume-mute', {
style: {
color: '#ef4444',
fontSize: '0.85rem',
marginLeft: 'auto',
flexShrink: 0,
},
title: 'Muted'
});
}
if (statusColor !== '#22c55e') {
return m('i.fas.fa-circle', {
style: {
color: statusColor,
fontSize: '0.65rem',
marginLeft: 'auto',
flexShrink: 0,
transition: 'color 0.3s ease',
},
title: statusTooltip
});
}
return null;
})()
]);
});
})()),
ChatHubState.hoveredUser && (() => { ChatHubState.hoveredUser && (() => {
const hUser = ChatHubState.hoveredUser; const hUser = ChatHubState.hoveredUser;
const details = ChatHubState.gxsDetails[hUser.gxsId]; const details = ChatHubState.gxsDetails[hUser.gxsId];
@ -1392,6 +1454,64 @@ const ChatConversationView = () => {
e.stopPropagation(); e.stopPropagation();
} }
}, [ }, [
m('.menu-item', {
onclick: () => {
ChatHubState.userSortMethod = 'activity';
ChatHubState.activeMenu = null;
m.redraw();
}
}, [
m('i.fas.fa-circle', {
style: {
color: '#000',
marginRight: '0.5rem',
fontSize: '0.4rem',
width: '18px',
textAlign: 'center',
visibility: ChatHubState.userSortMethod === 'activity' ? 'visible' : 'hidden'
}
}),
'Sort by Activity'
]),
m('.menu-item', {
onclick: () => {
ChatHubState.userSortMethod = 'name';
ChatHubState.activeMenu = null;
m.redraw();
}
}, [
m('i.fas.fa-circle', {
style: {
color: '#000',
marginRight: '0.5rem',
fontSize: '0.4rem',
width: '18px',
textAlign: 'center',
visibility: ChatHubState.userSortMethod === 'name' ? 'visible' : 'hidden'
}
}),
'Sort by Name'
]),
m('hr', { style: 'margin: 0.25rem 0; border: none; border-top: 1px solid #e2e8f0;' }),
!isOwn && m('.menu-item', {
onclick: () => {
ChatHubState.activeMenu = null;
people.setSelectedId(menu.gxsId, 'chat');
}
}, [
m('i.fas.fa-comments', { style: 'color: #3b82f6; margin-right: 0.5rem; width: 18px; text-align: center;' }),
'Start private chat'
]),
!isOwn && m('.menu-item', {
onclick: () => {
ChatHubState.activeMenu = null;
people.setSelectedId(menu.gxsId, 'details', true);
}
}, [
m('i.fas.fa-envelope', { style: 'color: #10b981; margin-right: 0.5rem; width: 18px; text-align: center;' }),
'Send Message'
]),
!isOwn && m('hr', { style: 'margin: 0.25rem 0; border: none; border-top: 1px solid #e2e8f0;' }),
!isOwn && m('.menu-item', { !isOwn && m('.menu-item', {
onclick: () => { onclick: () => {
if (isMuted) { if (isMuted) {
@ -1403,26 +1523,83 @@ const ChatConversationView = () => {
m.redraw(); m.redraw();
} }
}, [ }, [
m('i.fas.fa-volume-mute', { style: 'color: #ef4444; margin-right: 0.5rem;' }), m('i', {
class: isMuted ? 'fas fa-volume-up' : 'fas fa-volume-mute',
style: {
color: isMuted ? '#22c55e' : '#ef4444',
marginRight: '0.5rem',
fontSize: '0.95rem',
width: '18px',
textAlign: 'center'
}
}),
isMuted ? 'Unmute participant' : 'Mute participant' isMuted ? 'Unmute participant' : 'Mute participant'
]), ]),
!isOwn && m('.menu-item', { !isOwn && m('.menu-item', {
onclick: () => { onclick: () => {
ChatHubState.activeMenu = null; ChatHubState.activeMenu = null;
people.setSelectedId(menu.gxsId, 'chat'); rs.rsJsonApiRequest('/rsreputations/setOwnOpinion', { id: menu.gxsId, opinion: 2 }, (data, success) => {
if (success) {
if (!ChatHubState.gxsDetails[menu.gxsId]) ChatHubState.gxsDetails[menu.gxsId] = { mReputation: {} };
if (!ChatHubState.gxsDetails[menu.gxsId].mReputation) ChatHubState.gxsDetails[menu.gxsId].mReputation = {};
ChatHubState.gxsDetails[menu.gxsId].mReputation.mOwnOpinion = 2;
m.redraw();
rs.rsJsonApiRequest('/rsIdentity/getIdDetails', { id: menu.gxsId }, (d) => {
if (d && d.details) {
ChatHubState.gxsDetails[menu.gxsId] = d.details;
m.redraw();
}
});
}
});
} }
}, [ }, [
m('i.fas.fa-comments', { style: 'color: #3b82f6; margin-right: 0.5rem;' }), m('span', { style: 'background-color: #22c55e; border-radius: 50%; width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; margin-right: 0.5rem; font-size: 0.7rem; color: #ffffff;' }, m('i.fas.fa-thumbs-up')),
'Start private chat' 'Give positive opinion'
]), ]),
!isOwn && m('.menu-item', { !isOwn && m('.menu-item', {
onclick: () => { onclick: () => {
ChatHubState.activeMenu = null; ChatHubState.activeMenu = null;
people.setSelectedId(menu.gxsId, 'details', true); rs.rsJsonApiRequest('/rsreputations/setOwnOpinion', { id: menu.gxsId, opinion: 1 }, (data, success) => {
if (success) {
if (!ChatHubState.gxsDetails[menu.gxsId]) ChatHubState.gxsDetails[menu.gxsId] = { mReputation: {} };
if (!ChatHubState.gxsDetails[menu.gxsId].mReputation) ChatHubState.gxsDetails[menu.gxsId].mReputation = {};
ChatHubState.gxsDetails[menu.gxsId].mReputation.mOwnOpinion = 1;
m.redraw();
rs.rsJsonApiRequest('/rsIdentity/getIdDetails', { id: menu.gxsId }, (d) => {
if (d && d.details) {
ChatHubState.gxsDetails[menu.gxsId] = d.details;
m.redraw();
}
});
}
});
} }
}, [ }, [
m('i.fas.fa-envelope', { style: 'color: #10b981; margin-right: 0.5rem;' }), m('span', { style: 'background-color: #f59e0b; border-radius: 50%; width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; margin-right: 0.5rem; font-size: 0.7rem; color: #ffffff;' }, m('i.fas.fa-hand-paper')),
'Send Message' 'Give neutral opinion'
]),
!isOwn && m('.menu-item', {
onclick: () => {
ChatHubState.activeMenu = null;
rs.rsJsonApiRequest('/rsreputations/setOwnOpinion', { id: menu.gxsId, opinion: 0 }, (data, success) => {
if (success) {
if (!ChatHubState.gxsDetails[menu.gxsId]) ChatHubState.gxsDetails[menu.gxsId] = { mReputation: {} };
if (!ChatHubState.gxsDetails[menu.gxsId].mReputation) ChatHubState.gxsDetails[menu.gxsId].mReputation = {};
ChatHubState.gxsDetails[menu.gxsId].mReputation.mOwnOpinion = 0;
m.redraw();
rs.rsJsonApiRequest('/rsIdentity/getIdDetails', { id: menu.gxsId }, (d) => {
if (d && d.details) {
ChatHubState.gxsDetails[menu.gxsId] = d.details;
m.redraw();
}
});
}
});
}
}, [
m('span', { style: 'background-color: #ef4444; border-radius: 50%; width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; margin-right: 0.5rem; font-size: 0.7rem; color: #ffffff;' }, m('i.fas.fa-thumbs-down')),
'Ban this person (Sets negative opinion)'
]), ]),
m('.menu-item', { m('.menu-item', {
onclick: () => { onclick: () => {
@ -1430,7 +1607,7 @@ const ChatConversationView = () => {
people.setSelectedId(menu.gxsId, 'details'); people.setSelectedId(menu.gxsId, 'details');
} }
}, [ }, [
m('i.fas.fa-user', { style: 'color: #8b5cf6; margin-right: 0.5rem;' }), m('i.fas.fa-user', { style: 'color: #8b5cf6; margin-right: 0.5rem; width: 18px; text-align: center;' }),
'Show author in people tab' 'Show author in people tab'
]) ])
]); ]);
@ -1803,11 +1980,23 @@ const Layout = {
]), ]),
m('.form-field', { style: 'display: flex; gap: 0.5rem; align-items: center; margin-top: 0.75rem;' }, [ m('.form-field', { style: 'display: flex; gap: 0.5rem; align-items: center; margin-top: 0.75rem;' }, [
m('input[type=checkbox]', { m('label', { style: 'display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; color: #475569; cursor: pointer; user-select: none;' }, [
checked: ChatHubState.newRoomPublic, m('input[type=checkbox]', {
onclick: (e) => { ChatHubState.newRoomPublic = e.target.checked; } checked: ChatHubState.newRoomPublic,
}), onclick: (e) => { ChatHubState.newRoomPublic = e.target.checked; }
m('label', { style: 'font-size: 0.9rem; color: #475569;' }, 'Public Room') }),
'Public Room'
])
]),
m('.form-field', { style: 'display: flex; gap: 0.5rem; align-items: center; margin-top: 0.5rem;' }, [
m('label', { style: 'display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; color: #475569; cursor: pointer; user-select: none;' }, [
m('input[type=checkbox]', {
checked: ChatHubState.newRoomSigned,
onclick: (e) => { ChatHubState.newRoomSigned = e.target.checked; }
}),
'PGP signed identities'
])
]), ]),
ChatHubState.createRoomError && m('p.error-text', { style: 'color: #ef4444; font-size: 0.85rem; margin: 0.5rem 0 0 0;' }, ChatHubState.createRoomError), ChatHubState.createRoomError && m('p.error-text', { style: 'color: #ef4444; font-size: 0.85rem; margin: 0.5rem 0 0 0;' }, ChatHubState.createRoomError),
@ -1820,7 +2009,10 @@ const Layout = {
const topic = ChatHubState.newRoomTopic.trim(); const topic = ChatHubState.newRoomTopic.trim();
const identity = ChatHubState.newRoomIdentity; const identity = ChatHubState.newRoomIdentity;
const isPublic = ChatHubState.newRoomPublic; const isPublic = ChatHubState.newRoomPublic;
const flags = isPublic ? 4 : 0; // RS_CHAT_LOBBY_FLAGS_PUBLIC const isSigned = ChatHubState.newRoomSigned;
let flags = 0;
if (isPublic) flags |= 4; // RS_CHAT_LOBBY_FLAGS_PUBLIC
if (isSigned) flags |= 8; // RS_CHAT_LOBBY_FLAGS_SIGNED_ONLY
rs.rsJsonApiRequest('/rsChats/createChatLobby', { rs.rsJsonApiRequest('/rsChats/createChatLobby', {
lobby_name: name, lobby_name: name,
@ -1833,6 +2025,7 @@ const Layout = {
ChatHubState.showCreateRoomModal = false; ChatHubState.showCreateRoomModal = false;
ChatHubState.newRoomName = ''; ChatHubState.newRoomName = '';
ChatHubState.newRoomTopic = ''; ChatHubState.newRoomTopic = '';
ChatHubState.newRoomSigned = false;
ChatHubState.createRoomError = ''; ChatHubState.createRoomError = '';
// Refresh rooms list // Refresh rooms list
ChatRoomsModel.loadSubscribedRooms(); ChatRoomsModel.loadSubscribedRooms();
@ -1849,6 +2042,7 @@ const Layout = {
ChatHubState.showCreateRoomModal = false; ChatHubState.showCreateRoomModal = false;
ChatHubState.newRoomName = ''; ChatHubState.newRoomName = '';
ChatHubState.newRoomTopic = ''; ChatHubState.newRoomTopic = '';
ChatHubState.newRoomSigned = false;
ChatHubState.createRoomError = ''; ChatHubState.createRoomError = '';
} }
}, 'Cancel') }, 'Cancel')

View File

@ -12,6 +12,7 @@ const channels = require('channels/channels');
const forums = require('forums/forums'); const forums = require('forums/forums');
const boards = require('boards/boards'); const boards = require('boards/boards');
const config = require('config/config_resolver'); const config = require('config/config_resolver');
const statusbar = require('statusbar');
const navIcon = { const navIcon = {
home: m('i.fas.fa-home.sidenav-icon'), home: m('i.fas.fa-home.sidenav-icon'),
@ -127,7 +128,10 @@ const Layout = () => {
config: '/config/network', config: '/config/network',
}, },
}), }),
m('.tab-content', vnode.children), m('.main-container', { style: { display: 'flex', flexDirection: 'column', width: '100%', height: '100%', overflow: 'hidden' } }, [
m('.tab-content', { style: { flex: '1', overflow: 'auto' } }, vnode.children),
m(statusbar)
]),
]), ]),
}; };
}; };

162
webui-src/app/statusbar.js Normal file
View File

@ -0,0 +1,162 @@
const m = require('mithril');
const rs = require('rswebui');
const State = {
friendCount: 0,
onlineCount: 0,
dhtActive: false,
dhtOk: false,
dhtRsNetSize: 0,
dhtNetSize: 0,
natState: 1, // BAD_UNKNOWN
firewalled: true,
forwardPort: false,
stunOk: false,
extAddressOk: false,
};
function formatUnit(val) {
if (!val) return '0';
if (val >= 1000000) return (val / 1000000).toFixed(1) + 'M';
if (val >= 1000) return (val / 1000).toFixed(1) + 'k';
return val.toString();
}
function updateStatus() {
if (!rs.loginKey.isVerified) return;
// 1. Friends count
rs.rsJsonApiRequest('/rsPeers/getFriendList', {}, (data) => {
if (data && data.sslIds) {
State.friendCount = data.sslIds.length;
}
});
rs.rsJsonApiRequest('/rsPeers/getOnlineList', {}, (data) => {
if (data && data.sslIds) {
State.onlineCount = data.sslIds.length;
}
});
// 2. Net / DHT config status
rs.rsJsonApiRequest('/rsConfig/getConfigNetStatus', {}, (data) => {
if (data && data.status) {
State.dhtActive = data.status.DHTActive;
State.dhtOk = data.status.netDhtOk;
State.dhtRsNetSize = data.status.netDhtRsNetSize;
State.dhtNetSize = data.status.netDhtNetSize;
State.firewalled = data.status.firewalled;
State.forwardPort = data.status.forwardPort;
State.stunOk = data.status.netStunOk;
State.extAddressOk = data.status.netExtAddressOk;
}
});
// 3. NAT netState
rs.rsJsonApiRequest('/rsConfig/getNetState', {}, (data) => {
if (data && data.retval !== undefined) {
State.natState = data.retval;
} else {
// Fallback calculation based on getConfigNetStatus
if (State.firewalled && !State.forwardPort) {
State.natState = 6; // WARNING_NATTED
} else {
State.natState = 8; // GOOD
}
}
});
}
let intervalId = null;
const StatusBar = {
oninit() {
updateStatus();
intervalId = setInterval(updateStatus, 10000); // update every 10s
},
onremove() {
if (intervalId) {
clearInterval(intervalId);
}
},
view() {
// DHT Status color & tooltip
let dhtColor = '#94a3b8'; // grey (off)
let dhtTooltip = 'DHT Off';
if (State.dhtActive) {
if (State.dhtOk) {
if (State.dhtRsNetSize < 10) {
dhtColor = '#eab308'; // yellow (searching)
dhtTooltip = 'DHT Searching for RetroShare Peers';
} else {
dhtColor = '#22c55e'; // green (good)
dhtTooltip = 'DHT Good';
}
} else {
dhtColor = '#ef4444'; // red (error)
dhtTooltip = 'No peer found in DHT';
}
}
// NAT Status color & tooltip
let natColor = '#94a3b8';
let natTooltip = 'Offline';
switch (State.natState) {
case 1: // BAD_UNKNOWN
natColor = '#eab308';
natTooltip = 'Network Status Unknown';
break;
case 2: // BAD_OFFLINE
natColor = '#94a3b8';
natTooltip = 'Offline';
break;
case 3: // BAD_NATSYM
case 4: // BAD_NODHT_NAT
natColor = '#ef4444';
natTooltip = State.natState === 4 ? 'DHT Disabled and Firewalled' : 'Nasty Firewall';
break;
case 5: // WARNING_RESTART
natColor = '#eab308';
natTooltip = 'Network Restarting';
break;
case 6: // WARNING_NATTED
natColor = '#eab308';
natTooltip = 'Behind Firewall';
break;
case 7: // WARNING_NODHT
natColor = '#eab308';
natTooltip = 'DHT Disabled';
break;
case 8: // GOOD
natColor = '#22c55e';
natTooltip = 'RetroShare Server';
break;
case 9: // ADV_FORWARD
natColor = '#22c55e';
natTooltip = 'Forwarded Port';
break;
}
return m('.statusbar', [
m('.statusbar-left', { style: 'display: flex; align-items: center; gap: 0.75rem;' }, [
m('.statusbar-item', [
m('i.fas.fa-users', { style: 'margin-right: 0.5rem; color: #94a3b8;' }),
m('span', `Friends: ${State.onlineCount}/${State.friendCount}`),
]),
m('.statusbar-divider'),
m('.statusbar-item', { title: natTooltip, style: 'cursor: help;' }, [
m('span', { style: 'margin-right: 0.5rem;' }, 'NAT:'),
m('.status-bullet', { style: { backgroundColor: natColor } }),
]),
m('.statusbar-divider'),
m('.statusbar-item', { title: dhtTooltip, style: 'cursor: help;' }, [
m('span', { style: 'margin-right: 0.5rem;' }, 'DHT:'),
m('.status-bullet', { style: { backgroundColor: dhtColor } }),
State.dhtActive && State.dhtOk && m('span', { style: 'margin-left: 0.5rem;' }, `${formatUnit(State.dhtRsNetSize)} (${formatUnit(State.dhtNetSize)})`),
]),
]),
m('.statusbar-right'),
]);
},
};
module.exports = StatusBar;

View File

@ -1950,3 +1950,51 @@ table.mails th.sortable-th:hover {
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
} }
/* Status Bar Styles */
.statusbar {
display: flex;
justify-content: space-between;
align-items: center;
height: 28px;
background-color: #14141b;
border-top: 1px solid #2e2e38;
padding: 0 1rem;
font-size: 0.8rem;
color: #94a3b8;
z-index: 100;
box-sizing: border-box;
user-select: none;
flex-shrink: 0;
}
.statusbar-left {
display: flex;
align-items: center;
}
.statusbar-right {
display: flex;
align-items: center;
gap: 1.5rem;
}
.statusbar-item {
display: flex;
align-items: center;
}
.statusbar-divider {
width: 1px;
height: 14px;
background-color: #2e2e38;
}
.status-bullet {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
}