mirror of
https://github.com/RetroShare/RSNewWebUI.git
synced 2026-09-12 19:50:04 +05:00
* Added attach image button * Improved status bar to retrieve nat status & down/up infos * torstatus added but not yet functional needs the api
406 lines
15 KiB
JavaScript
406 lines
15 KiB
JavaScript
const m = require('mithril');
|
|
const rs = require('rswebui');
|
|
|
|
// RS_HIDDEN_TYPE constants (from config_util.js / retroshare/rspeers.h)
|
|
const RS_HIDDEN_TYPE_NONE = 0;
|
|
const RS_HIDDEN_TYPE_TOR = 2;
|
|
const RS_HIDDEN_TYPE_I2P = 4;
|
|
|
|
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,
|
|
|
|
// Hidden-mode / Tor+I2P state (mirrors TorStatus widget in Qt)
|
|
hiddenType: RS_HIDDEN_TYPE_NONE, // 0=none, 2=Tor, 4=I2P
|
|
torProxyOk: null, // null=unchecked, true=ok, false=fail
|
|
torChecking: false,
|
|
|
|
// Bandwidth rate status (mirrors RatesStatus widget in Qt)
|
|
rateIn: 0.0,
|
|
totalIn: 0,
|
|
rateOut: 0.0,
|
|
totalOut: 0,
|
|
};
|
|
|
|
function parse64Num(val) {
|
|
if (val === null || val === undefined) return 0;
|
|
if (typeof val === 'number') return val;
|
|
if (typeof val === 'string') return parseFloat(val) || 0;
|
|
if (typeof val === 'object') {
|
|
if (val.xuint64 !== undefined) return parseFloat(val.xuint64) || 0;
|
|
if (val.xint64 !== undefined) return parseFloat(val.xint64) || 0;
|
|
if (val.xstr64 !== undefined) return parseFloat(val.xstr64) || 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function formatUnit(val) {
|
|
const num = parse64Num(val);
|
|
if (!num) return '0';
|
|
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
|
|
if (num >= 1000) return (num / 1000).toFixed(1) + 'k';
|
|
return num.toString();
|
|
}
|
|
|
|
function formatBytes(rawBytes) {
|
|
const bytes = parse64Num(rawBytes);
|
|
if (!bytes || bytes <= 0 || isNaN(bytes)) return '0 B';
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
const safeI = Math.max(0, Math.min(i, sizes.length - 1));
|
|
return parseFloat((bytes / Math.pow(k, safeI)).toFixed(1)) + ' ' + sizes[safeI];
|
|
}
|
|
|
|
/**
|
|
* Fetch the own peer's hidden type and proxy status using /rsTor API.
|
|
* Mirrors Qt's TorStatus::getTorStatus().
|
|
*/
|
|
function updateTorStatus() {
|
|
if (!rs.loginKey.isVerified) return;
|
|
|
|
rs.rsJsonApiRequest('/rsAccounts/getCurrentAccountId').then((res) => {
|
|
if (!res || !res.body || !res.body.retval) return;
|
|
const sslId = res.body.id;
|
|
|
|
rs.rsJsonApiRequest('/rsPeers/getPeerDetails', { sslId }).then((pres) => {
|
|
if (!pres || !pres.body || !pres.body.retval) return;
|
|
const details = pres.body.det;
|
|
|
|
const isHiddenNode = Boolean(
|
|
details && (
|
|
details.hiddenType === RS_HIDDEN_TYPE_TOR ||
|
|
details.hiddenType === RS_HIDDEN_TYPE_I2P ||
|
|
details.extAddr === 'Hidden'
|
|
)
|
|
);
|
|
|
|
if (!isHiddenNode) {
|
|
State.hiddenType = RS_HIDDEN_TYPE_NONE;
|
|
State.torProxyOk = null;
|
|
State.torChecking = false;
|
|
m.redraw();
|
|
return;
|
|
}
|
|
|
|
const targetType = details.hiddenType || RS_HIDDEN_TYPE_TOR;
|
|
State.hiddenType = targetType;
|
|
|
|
// Check if node uses automated Tor management via /rsAccounts/isTorAuto (same as Qt)
|
|
rs.rsJsonApiRequest('/rsAccounts/isTorAuto', {}).then((autoRes) => {
|
|
const isAuto = autoRes && (autoRes.retval || (autoRes.body && autoRes.body.retval));
|
|
if (isAuto) {
|
|
Promise.all([
|
|
rs.rsJsonApiRequest('/rsTor/torStatus', {}),
|
|
rs.rsJsonApiRequest('/rsTor/torConnectivityStatus', {}),
|
|
]).then(([torRes, connRes]) => {
|
|
const torStatus = torRes && torRes.body ? torRes.body.retval : (torRes && torRes.retval !== undefined ? torRes.retval : 0);
|
|
const connStatus = connRes && connRes.body ? connRes.body.retval : (connRes && connRes.retval !== undefined ? connRes.retval : 0);
|
|
const torControlOk = connStatus === 6; // HIDDEN_SERVICE_READY
|
|
const torReady = torStatus === 2; // READY
|
|
|
|
if (torReady && torControlOk) {
|
|
State.torProxyOk = true;
|
|
State.torChecking = false;
|
|
} else if (torStatus === 1 || connStatus === 0 || connStatus === 1) {
|
|
// OFFLINE, ERROR, or NOT_CONNECTED
|
|
State.torProxyOk = false;
|
|
State.torChecking = false;
|
|
} else if (connStatus >= 2 && connStatus <= 5) {
|
|
// CONNECTING, SOCKET_CONNECTED, AUTHENTICATING, AUTHENTICATED
|
|
State.torProxyOk = null;
|
|
State.torChecking = true;
|
|
} else {
|
|
// UNKNOWN / default
|
|
State.torProxyOk = null;
|
|
State.torChecking = false;
|
|
}
|
|
m.redraw();
|
|
}).catch(() => {
|
|
State.torProxyOk = true;
|
|
State.torChecking = false;
|
|
m.redraw();
|
|
});
|
|
} else {
|
|
// Manual Tor / I2P proxy node
|
|
State.torProxyOk = true;
|
|
State.torChecking = false;
|
|
m.redraw();
|
|
}
|
|
}).catch(() => {
|
|
// Fallback for uncompiled backend: query /rsTor endpoints directly or assume active
|
|
Promise.all([
|
|
rs.rsJsonApiRequest('/rsTor/torStatus', {}),
|
|
rs.rsJsonApiRequest('/rsTor/torConnectivityStatus', {}),
|
|
]).then(([torRes, connRes]) => {
|
|
const torStatus = torRes && torRes.body ? torRes.body.retval : (torRes && torRes.retval !== undefined ? torRes.retval : 0);
|
|
const connStatus = connRes && connRes.body ? connRes.body.retval : (connRes && connRes.retval !== undefined ? connRes.retval : 0);
|
|
const torControlOk = connStatus === 6; // HIDDEN_SERVICE_READY
|
|
const torReady = torStatus === 2; // READY
|
|
|
|
if (torReady && torControlOk) {
|
|
State.torProxyOk = true;
|
|
State.torChecking = false;
|
|
} else if (torStatus === 1 || connStatus === 0 || connStatus === 1) {
|
|
State.torProxyOk = false;
|
|
State.torChecking = false;
|
|
} else if (connStatus >= 2 && connStatus <= 5) {
|
|
State.torProxyOk = null;
|
|
State.torChecking = true;
|
|
} else {
|
|
State.torProxyOk = null;
|
|
State.torChecking = false;
|
|
}
|
|
m.redraw();
|
|
}).catch(() => {
|
|
State.torProxyOk = true;
|
|
State.torChecking = false;
|
|
m.redraw();
|
|
});
|
|
});
|
|
});
|
|
}).catch(() => {
|
|
State.hiddenType = RS_HIDDEN_TYPE_NONE;
|
|
State.torProxyOk = null;
|
|
});
|
|
}
|
|
|
|
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 & NAT state
|
|
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;
|
|
|
|
// Compute NAT state directly from RsConfigNetStatus
|
|
if (!data.status.netLocalOk && !data.status.netExtAddressOk) {
|
|
State.natState = 2; // BAD_OFFLINE
|
|
} else if (data.status.firewalled && !data.status.forwardPort && !data.status.netUpnpOk) {
|
|
State.natState = 6; // WARNING_NATTED
|
|
} else if (data.status.forwardPort || data.status.netUpnpOk) {
|
|
State.natState = 9; // ADV_FORWARD
|
|
} else {
|
|
State.natState = 8; // GOOD
|
|
}
|
|
}
|
|
});
|
|
|
|
// 3. NAT netState from /rsConfig/getNetState
|
|
rs.rsJsonApiRequest('/rsConfig/getNetState', {}, (data) => {
|
|
if (data && data.retval !== undefined) {
|
|
State.natState = data.retval;
|
|
m.redraw();
|
|
} else if (data && data.body && data.body.retval !== undefined) {
|
|
State.natState = data.body.retval;
|
|
m.redraw();
|
|
}
|
|
});
|
|
|
|
// 4. Tor/I2P hidden-mode status (same as Qt TorStatus widget)
|
|
updateTorStatus();
|
|
|
|
// 5. Bandwidth rates (same as Qt RatesStatus widget)
|
|
rs.rsJsonApiRequest('/rsConfig/getTotalBandwidthRates', {}, (data) => {
|
|
const rates = (data && data.rates) || (data && data.body && data.body.rates);
|
|
if (rates) {
|
|
State.rateIn = rates.mRateIn !== undefined ? rates.mRateIn : (rates.rateIn || 0.0);
|
|
State.totalIn = rates.mTotalIn !== undefined ? rates.mTotalIn : (rates.totalIn || 0);
|
|
State.rateOut = rates.mRateOut !== undefined ? rates.mRateOut : (rates.rateOut || 0.0);
|
|
State.totalOut = rates.mTotalOut !== undefined ? rates.mTotalOut : (rates.totalOut || 0);
|
|
m.redraw();
|
|
}
|
|
});
|
|
}
|
|
|
|
let intervalId = null;
|
|
|
|
const StatusBar = {
|
|
oninit() {
|
|
updateStatus();
|
|
intervalId = setInterval(updateStatus, 10000); // update every 10s
|
|
},
|
|
onremove() {
|
|
if (intervalId) {
|
|
clearInterval(intervalId);
|
|
}
|
|
},
|
|
view() {
|
|
const isHiddenMode = State.hiddenType === RS_HIDDEN_TYPE_TOR ||
|
|
State.hiddenType === RS_HIDDEN_TYPE_I2P;
|
|
|
|
// ── DHT Status (hidden when in hidden/darknet mode) ────────────────────
|
|
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 (hidden when in hidden/darknet mode) ────────────────────
|
|
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;
|
|
}
|
|
|
|
// ── Tor / I2P status indicator ─────────────────────────────────────────
|
|
// Only shown when peer is in RS_NETMODE_HIDDEN with a proxy type set.
|
|
// Mirrors Qt TorStatus widget label + icon logic.
|
|
let torLabel, torColor, torIcon, torTooltip;
|
|
if (isHiddenMode) {
|
|
torLabel = State.hiddenType === RS_HIDDEN_TYPE_TOR ? 'Tor:' : 'I2P:';
|
|
if (State.torChecking) {
|
|
torColor = '#f59e0b';
|
|
torIcon = 'fas fa-spinner fa-spin';
|
|
torTooltip = 'Checking proxy…';
|
|
} else if (State.torProxyOk === null) {
|
|
torColor = '#94a3b8';
|
|
torIcon = 'fas fa-shield-alt';
|
|
torTooltip = State.hiddenType === RS_HIDDEN_TYPE_TOR
|
|
? 'No Tor configuration'
|
|
: 'No I2P configuration';
|
|
} else if (State.torProxyOk) {
|
|
torColor = '#22c55e';
|
|
torIcon = 'fas fa-shield-alt';
|
|
torTooltip = State.hiddenType === RS_HIDDEN_TYPE_TOR
|
|
? 'Tor proxy is OK'
|
|
: 'I2P proxy is OK';
|
|
} else {
|
|
torColor = '#ef4444';
|
|
torIcon = 'fas fa-shield-alt';
|
|
torTooltip = State.hiddenType === RS_HIDDEN_TYPE_TOR
|
|
? 'Tor proxy is not available'
|
|
: 'I2P proxy is not available';
|
|
}
|
|
}
|
|
|
|
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}`),
|
|
]),
|
|
|
|
// NAT — hidden when in hidden/darknet mode (same as Qt)
|
|
!isHiddenMode && m('.statusbar-divider'),
|
|
!isHiddenMode && m('.statusbar-item', { title: natTooltip, style: 'cursor: help;' }, [
|
|
m('span', { style: 'margin-right: 0.5rem;' }, 'NAT:'),
|
|
m('.status-bullet', { style: { backgroundColor: natColor } }),
|
|
]),
|
|
|
|
// DHT — hidden when in hidden/darknet mode (same as Qt)
|
|
!isHiddenMode && m('.statusbar-divider'),
|
|
!isHiddenMode && 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)})`),
|
|
]),
|
|
|
|
// Tor / I2P — only shown when in hidden/darknet mode (same as Qt)
|
|
isHiddenMode && m('.statusbar-divider'),
|
|
isHiddenMode && m('.statusbar-item.statusbar-item--tor', {
|
|
title: torTooltip,
|
|
style: 'cursor: help;',
|
|
}, [
|
|
m('span.tor-label', { style: 'margin-right: 0.4rem; font-weight: 600;' }, torLabel),
|
|
m('i.' + torIcon, { style: { color: torColor, fontSize: '1rem', transition: 'color 0.3s' } }),
|
|
]),
|
|
]),
|
|
|
|
// RatesStatus — Bandwidth speeds & total cumulative transfer (Down | Up)
|
|
m('.statusbar-right', { style: 'display: flex; align-items: center; gap: 0.75rem;' }, [
|
|
m('.statusbar-item', {
|
|
title: `Downloaded: ${formatBytes(State.totalIn)}`,
|
|
style: 'cursor: help; display: flex; align-items: center;'
|
|
}, [
|
|
m('i.fas.fa-arrow-down', { style: 'color: #22c55e; margin-right: 0.35rem;' }),
|
|
m('span', `Down: ${State.rateIn.toFixed(2)} kB/s`),
|
|
m('span', { style: 'color: #64748b; font-size: 0.8rem; margin-left: 0.35rem;' }, `(${formatBytes(State.totalIn)})`),
|
|
]),
|
|
m('.statusbar-divider'),
|
|
m('.statusbar-item', {
|
|
title: `Uploaded: ${formatBytes(State.totalOut)}`,
|
|
style: 'cursor: help; display: flex; align-items: center;'
|
|
}, [
|
|
m('i.fas.fa-arrow-up', { style: 'color: #3b82f6; margin-right: 0.35rem;' }),
|
|
m('span', `Up: ${State.rateOut.toFixed(2)} kB/s`),
|
|
m('span', { style: 'color: #64748b; font-size: 0.8rem; margin-left: 0.35rem;' }, `(${formatBytes(State.totalOut)})`),
|
|
]),
|
|
]),
|
|
]);
|
|
},
|
|
};
|
|
|
|
module.exports = StatusBar;
|