@memtensor/memos-local-openclaw-plugin 1.0.4-beta.12 → 1.0.4-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +48 -11
- package/dist/client/connector.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +59 -29
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +1 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +26 -3
- package/dist/viewer/server.js.map +1 -1
- package/package.json +1 -1
- package/src/client/connector.ts +46 -11
- package/src/viewer/html.ts +59 -29
- package/src/viewer/server.ts +25 -4
package/package.json
CHANGED
package/src/client/connector.ts
CHANGED
|
@@ -208,20 +208,55 @@ export async function getHubStatus(store: SqliteStore, config: MemosLocalConfig)
|
|
|
208
208
|
} catch (err: any) {
|
|
209
209
|
const is401 = typeof err?.message === "string" && err.message.includes("(401)");
|
|
210
210
|
if (is401 && conn) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
211
|
+
const teamToken = config.sharing?.client?.teamToken ?? "";
|
|
212
|
+
if (hubAddress && teamToken) {
|
|
213
|
+
try {
|
|
214
|
+
const regResult = await hubRequestJson(normalizeHubUrl(hubAddress), "", "/api/v1/hub/registration-status", {
|
|
215
|
+
method: "POST",
|
|
216
|
+
body: JSON.stringify({ teamToken, userId: conn.userId }),
|
|
217
|
+
}) as any;
|
|
218
|
+
if (regResult.status === "active" && regResult.userToken) {
|
|
219
|
+
store.setClientHubConnection({
|
|
220
|
+
...conn,
|
|
221
|
+
hubUrl: normalizeHubUrl(hubAddress),
|
|
222
|
+
userToken: regResult.userToken,
|
|
223
|
+
connectedAt: Date.now(),
|
|
224
|
+
lastKnownStatus: "active",
|
|
225
|
+
});
|
|
226
|
+
try {
|
|
227
|
+
const me = await hubRequestJson(normalizeHubUrl(hubAddress), regResult.userToken, "/api/v1/hub/me", { method: "GET" }) as any;
|
|
228
|
+
return {
|
|
229
|
+
connected: true,
|
|
230
|
+
hubUrl: normalizeHubUrl(hubAddress),
|
|
231
|
+
user: {
|
|
232
|
+
id: String(me.id),
|
|
233
|
+
username: String(me.username ?? ""),
|
|
234
|
+
role: String(me.role ?? "member") as UserRole,
|
|
235
|
+
status: String(me.status ?? "active"),
|
|
236
|
+
groups: Array.isArray(me.groups) ? me.groups : [],
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
} catch { /* fall through to token-only return */ }
|
|
240
|
+
return {
|
|
241
|
+
connected: true,
|
|
242
|
+
hubUrl: normalizeHubUrl(hubAddress),
|
|
243
|
+
user: { id: conn.userId, username: conn.username || "", role: conn.role as UserRole || "member", status: "active" },
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const realStatus = regResult.status as string;
|
|
247
|
+
store.setClientHubConnection({ ...conn, userToken: "", lastKnownStatus: realStatus });
|
|
248
|
+
return {
|
|
249
|
+
connected: false,
|
|
250
|
+
hubUrl: normalizeHubUrl(hubAddress),
|
|
251
|
+
user: { id: conn.userId, username: conn.username || "", role: "member", status: realStatus },
|
|
252
|
+
};
|
|
253
|
+
} catch { /* registration-status also failed, fall through */ }
|
|
254
|
+
}
|
|
255
|
+
store.setClientHubConnection({ ...conn, userToken: "", lastKnownStatus: "token_expired" });
|
|
216
256
|
return {
|
|
217
257
|
connected: false,
|
|
218
258
|
hubUrl: normalizeHubUrl(hubAddress),
|
|
219
|
-
user: {
|
|
220
|
-
id: conn.userId,
|
|
221
|
-
username: conn.username || "",
|
|
222
|
-
role: "member",
|
|
223
|
-
status: "removed",
|
|
224
|
-
},
|
|
259
|
+
user: { id: conn.userId, username: conn.username || "", role: "member", status: "token_expired" },
|
|
225
260
|
};
|
|
226
261
|
}
|
|
227
262
|
return { connected: false, user: null };
|
package/src/viewer/html.ts
CHANGED
|
@@ -976,8 +976,6 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
976
976
|
.team-guide-steps li::marker{color:var(--pri);font-weight:700;font-size:11px}
|
|
977
977
|
.team-guide-opt .btn-guide{font-size:11px;padding:5px 14px;border-radius:6px;font-weight:600;border:1px solid rgba(99,102,241,.25);background:rgba(99,102,241,.08);color:var(--pri);cursor:pointer;transition:background .15s,border-color .15s}
|
|
978
978
|
.team-guide-opt .btn-guide:hover{background:rgba(99,102,241,.14);border-color:var(--pri)}
|
|
979
|
-
.team-guide-dismiss{position:absolute;top:10px;right:12px;background:none;border:none;color:var(--text-muted);font-size:15px;cursor:pointer;padding:4px;line-height:1;opacity:.5;transition:opacity .15s}
|
|
980
|
-
.team-guide-dismiss:hover{opacity:1}
|
|
981
979
|
[data-theme="light"] .team-guide{background:linear-gradient(135deg,rgba(6,182,212,.03),rgba(79,70,229,.02));border-color:rgba(6,182,212,.15)}
|
|
982
980
|
[data-theme="light"] .team-guide-opt{box-shadow:0 1px 3px rgba(0,0,0,.03)}
|
|
983
981
|
[data-theme="light"] .team-guide-opt:hover{box-shadow:0 4px 16px rgba(0,0,0,.04)}
|
|
@@ -1641,9 +1639,8 @@ input,textarea,select{font-family:inherit;font-size:inherit}
|
|
|
1641
1639
|
</div>
|
|
1642
1640
|
</div>
|
|
1643
1641
|
<div class="settings-card-body">
|
|
1644
|
-
<!-- team setup guide (inside Hub card) -->
|
|
1642
|
+
<!-- team setup guide (inside Hub card) — always visible when sharing is not configured -->
|
|
1645
1643
|
<div class="team-guide" id="teamSetupGuide">
|
|
1646
|
-
<button class="team-guide-dismiss" onclick="dismissTeamGuide()" title="Dismiss">×</button>
|
|
1647
1644
|
<div class="team-guide-title">\u{1F680} <span data-i18n="guide.title">Get Started with Team Collaboration</span></div>
|
|
1648
1645
|
<div class="team-guide-subtitle" data-i18n="guide.subtitle">MemOS supports team memory sharing. Choose one of the following options to enable collaboration, or continue using local-only mode.</div>
|
|
1649
1646
|
<div class="team-guide-options">
|
|
@@ -2126,6 +2123,8 @@ const I18N={
|
|
|
2126
2123
|
'notif.userJoin':'New user requests to join the team',
|
|
2127
2124
|
'notif.userOnline':'User came online',
|
|
2128
2125
|
'notif.userOffline':'User went offline',
|
|
2126
|
+
'notif.membershipApproved':'Your team join request has been approved',
|
|
2127
|
+
'notif.membershipRejected':'Your team join request has been declined',
|
|
2129
2128
|
'notif.clearAll':'Clear all',
|
|
2130
2129
|
'notif.timeAgo.just':'just now',
|
|
2131
2130
|
'notif.timeAgo.min':'{n}m ago',
|
|
@@ -2448,6 +2447,9 @@ const I18N={
|
|
|
2448
2447
|
'sharing.pendingApproval.hint':'Your join request has been submitted. Please wait for the team admin to approve.',
|
|
2449
2448
|
'sharing.rejected.hint':'Your join request was rejected by the team admin. Please contact the admin or retry.',
|
|
2450
2449
|
'sharing.removed.hint':'You have been removed from the team by the admin. You can re-apply to join.',
|
|
2450
|
+
'sharing.joinTeam':'Join Team',
|
|
2451
|
+
'sharing.joinSent.pending':'Join request sent! Waiting for admin approval.',
|
|
2452
|
+
'sharing.joinSent.active':'Successfully joined the team!',
|
|
2451
2453
|
'sharing.retryJoin':'Retry Join',
|
|
2452
2454
|
'sharing.retryJoin.hint':'Clears local data and re-submits the join request',
|
|
2453
2455
|
'sharing.retryJoin.confirm':'This will clear your current connection and re-submit a join request. Continue?',
|
|
@@ -2840,6 +2842,8 @@ const I18N={
|
|
|
2840
2842
|
'notif.userJoin':'有新用户申请加入团队',
|
|
2841
2843
|
'notif.userOnline':'用户上线了',
|
|
2842
2844
|
'notif.userOffline':'用户下线了',
|
|
2845
|
+
'notif.membershipApproved':'你的团队加入申请已通过',
|
|
2846
|
+
'notif.membershipRejected':'你的团队加入申请已被拒绝',
|
|
2843
2847
|
'notif.clearAll':'清除全部',
|
|
2844
2848
|
'notif.timeAgo.just':'刚刚',
|
|
2845
2849
|
'notif.timeAgo.min':'{n}分钟前',
|
|
@@ -3162,6 +3166,9 @@ const I18N={
|
|
|
3162
3166
|
'sharing.pendingApproval.hint':'加入申请已提交,请等待团队管理员审核通过。',
|
|
3163
3167
|
'sharing.rejected.hint':'您的加入申请已被团队管理员拒绝,请联系管理员或重新申请。',
|
|
3164
3168
|
'sharing.removed.hint':'您已被管理员从团队中移除,可以重新申请加入。',
|
|
3169
|
+
'sharing.joinTeam':'加入团队',
|
|
3170
|
+
'sharing.joinSent.pending':'加入申请已发送,等待管理员审批。',
|
|
3171
|
+
'sharing.joinSent.active':'成功加入团队!',
|
|
3165
3172
|
'sharing.retryJoin':'重新申请',
|
|
3166
3173
|
'sharing.retryJoin.hint':'清除本地连接数据并重新提交加入申请',
|
|
3167
3174
|
'sharing.retryJoin.confirm':'这将清除当前连接数据并重新提交加入申请,是否继续?',
|
|
@@ -3606,6 +3613,9 @@ function selectSharingRole(role){
|
|
|
3606
3613
|
var tn=document.getElementById('cfgHubTeamName');
|
|
3607
3614
|
if(!tn.value.trim()) tn.value='My Team';
|
|
3608
3615
|
}
|
|
3616
|
+
var card=document.getElementById('settingsSharingConfig');
|
|
3617
|
+
var saveBtn=card&&card.querySelector('.settings-actions .btn-primary');
|
|
3618
|
+
if(saveBtn&&typeof _hubSaveBtnLabel==='function') saveBtn.textContent=_hubSaveBtnLabel();
|
|
3609
3619
|
}
|
|
3610
3620
|
var _cachedLocalIP='';
|
|
3611
3621
|
function updateHubShareInfo(){
|
|
@@ -3697,15 +3707,14 @@ function switchView(view){
|
|
|
3697
3707
|
else if(view==='analytics') loadMetrics();
|
|
3698
3708
|
else if(view==='logs') loadLogs();
|
|
3699
3709
|
else if(view==='settings'){loadConfig().then(function(){
|
|
3700
|
-
var notDismissed=localStorage.getItem('memos-team-guide-dismissed')!=='1';
|
|
3701
3710
|
var sharingOn=document.getElementById('cfgSharingEnabled');
|
|
3702
3711
|
var sharingNotEnabled=!sharingOn||!sharingOn.checked;
|
|
3703
|
-
if(
|
|
3712
|
+
if(sharingNotEnabled){
|
|
3704
3713
|
switchSettingsTab('hub',document.querySelector('.settings-tab-btn[data-tab="hub"]'));
|
|
3705
3714
|
}
|
|
3706
3715
|
});loadModelHealth();}
|
|
3707
3716
|
else if(view==='import'){if(!window._migrateRunning) migrateScan(false);}
|
|
3708
|
-
else if(view==='admin'){loadAdminData();}
|
|
3717
|
+
else if(view==='admin'){_lastAdminFingerprint='';loadAdminData();}
|
|
3709
3718
|
}
|
|
3710
3719
|
|
|
3711
3720
|
function onMemoryScopeChange(){
|
|
@@ -3775,10 +3784,13 @@ async function loadSharingStatus(forcePending){
|
|
|
3775
3784
|
if(_lastSharingConnStatus==='pending'&&curStatus==='connected'){
|
|
3776
3785
|
toast(t('sharing.approved.toast'),'success');
|
|
3777
3786
|
loadMemories();loadTasks();loadSkills();
|
|
3787
|
+
if(_notifSSE){_notifSSE.close();_notifSSE=null;_notifSSEConnected=false;}
|
|
3788
|
+
connectNotifSSE();
|
|
3789
|
+
loadNotifications();
|
|
3778
3790
|
}
|
|
3779
3791
|
_lastSharingConnStatus=curStatus;
|
|
3780
3792
|
if(curStatus==='pending'&&!_clientPendingPollTimer){
|
|
3781
|
-
_clientPendingPollTimer=setInterval(function(){loadSharingStatus(false);},
|
|
3793
|
+
_clientPendingPollTimer=setInterval(function(){loadSharingStatus(false);},5000);
|
|
3782
3794
|
}
|
|
3783
3795
|
if(curStatus!=='pending'&&_clientPendingPollTimer){
|
|
3784
3796
|
clearInterval(_clientPendingPollTimer);
|
|
@@ -4059,7 +4071,7 @@ async function approveSharingUser(userId,username){
|
|
|
4059
4071
|
try{
|
|
4060
4072
|
const r=await fetch('/api/sharing/approve-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
|
|
4061
4073
|
const d=await r.json();
|
|
4062
|
-
if(d.ok){toast(t('toast.userApproved'),'success');loadSharingPendingUsers();loadSharingStatus(true);} else {toast(d.error||t('toast.approveFail'),'error');}
|
|
4074
|
+
if(d.ok){toast(t('toast.userApproved'),'success');loadSharingPendingUsers();loadSharingStatus(true);_lastAdminFingerprint='';loadAdminData();} else {toast(d.error||t('toast.approveFail'),'error');}
|
|
4063
4075
|
}catch(e){toast(t('toast.approveFail')+': '+e.message,'error');}
|
|
4064
4076
|
}
|
|
4065
4077
|
|
|
@@ -4067,24 +4079,17 @@ async function rejectSharingUser(userId,username){
|
|
|
4067
4079
|
try{
|
|
4068
4080
|
const r=await fetch('/api/sharing/reject-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
|
|
4069
4081
|
const d=await r.json();
|
|
4070
|
-
if(d.ok){toast(t('toast.userRejected'),'success');loadSharingPendingUsers();} else {toast(d.error||t('toast.rejectFail'),'error');}
|
|
4082
|
+
if(d.ok){toast(t('toast.userRejected'),'success');loadSharingPendingUsers();_lastAdminFingerprint='';loadAdminData();} else {toast(d.error||t('toast.rejectFail'),'error');}
|
|
4071
4083
|
}catch(e){toast(t('toast.rejectFail')+': '+e.message,'error');}
|
|
4072
4084
|
}
|
|
4073
4085
|
|
|
4074
4086
|
/* ─── Team Setup Guide ─── */
|
|
4075
|
-
var TEAM_GUIDE_DISMISSED_KEY='memos-team-guide-dismissed';
|
|
4076
4087
|
function updateTeamGuide(sharingData){
|
|
4077
4088
|
var el=document.getElementById('teamSetupGuide');
|
|
4078
4089
|
if(!el) return;
|
|
4079
|
-
if(localStorage.getItem(TEAM_GUIDE_DISMISSED_KEY)==='1'){el.style.display='none';return;}
|
|
4080
4090
|
var isConfigured=sharingData&&sharingData.enabled;
|
|
4081
4091
|
el.style.display=isConfigured?'none':'block';
|
|
4082
4092
|
}
|
|
4083
|
-
function dismissTeamGuide(){
|
|
4084
|
-
localStorage.setItem(TEAM_GUIDE_DISMISSED_KEY,'1');
|
|
4085
|
-
var el=document.getElementById('teamSetupGuide');
|
|
4086
|
-
if(el) el.style.display='none';
|
|
4087
|
-
}
|
|
4088
4093
|
function guideGoToHub(role){
|
|
4089
4094
|
switchSettingsTab('hub',document.querySelector('.settings-tab-btn[data-tab="hub"]'));
|
|
4090
4095
|
var chk=document.getElementById('cfgSharingEnabled');
|
|
@@ -4200,7 +4205,7 @@ async function loadAdminData(){
|
|
|
4200
4205
|
var _newMemories=Array.isArray(memoriesR.memories)?memoriesR.memories:[];
|
|
4201
4206
|
var pending=isAdmin?(Array.isArray(pendingR.users)?pendingR.users:[]):[];
|
|
4202
4207
|
var _fp=_newUsers.length+':'+_newTasks.length+':'+_newSkills.length+':'+_newMemories.length+':'+pending.length
|
|
4203
|
-
+':'+_newUsers.map(function(u){return u.id+'|'+(u.isOnline?1:0)+'|'+(u.role||'')+'|'+(u.username||'')+'|'+(u.memoryCount||0)+'|'+(u.taskCount||0)+'|'+(u.skillCount||0)}).join(',')
|
|
4208
|
+
+':'+_newUsers.map(function(u){return u.id+'|'+(u.isOnline?1:0)+'|'+(u.role||'')+'|'+(u.status||'')+'|'+(u.username||'')+'|'+(u.memoryCount||0)+'|'+(u.taskCount||0)+'|'+(u.skillCount||0)}).join(',')
|
|
4204
4209
|
+':'+_newMemories.map(function(m){return m.id}).join(',')
|
|
4205
4210
|
+':'+_newTasks.map(function(t){return t.id+'|'+(t.status||'')}).join(',')
|
|
4206
4211
|
+':'+_newSkills.map(function(s){return s.id+'|'+(s.status||'')}).join(',')
|
|
@@ -4396,7 +4401,7 @@ async function adminApproveUser(userId,username){
|
|
|
4396
4401
|
try{
|
|
4397
4402
|
var r=await fetch('/api/sharing/approve-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
|
|
4398
4403
|
var d=await r.json();
|
|
4399
|
-
if(d.ok){toast(t('toast.userApproved'),'success');loadAdminData();}else{toast(d.error||t('toast.approveFail'),'error');}
|
|
4404
|
+
if(d.ok){toast(t('toast.userApproved'),'success');_lastAdminFingerprint='';loadAdminData();}else{toast(d.error||t('toast.approveFail'),'error');}
|
|
4400
4405
|
}catch(e){toast(t('toast.approveFail')+': '+e.message,'error');}
|
|
4401
4406
|
}
|
|
4402
4407
|
async function adminRejectUser(userId){
|
|
@@ -4404,7 +4409,7 @@ async function adminRejectUser(userId){
|
|
|
4404
4409
|
try{
|
|
4405
4410
|
var r=await fetch('/api/sharing/reject-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId})});
|
|
4406
4411
|
var d=await r.json();
|
|
4407
|
-
if(d.ok){toast(t('toast.userRejected'),'success');loadAdminData();}else{toast(d.error||t('toast.rejectFail'),'error');}
|
|
4412
|
+
if(d.ok){toast(t('toast.userRejected'),'success');_lastAdminFingerprint='';loadAdminData();}else{toast(d.error||t('toast.rejectFail'),'error');}
|
|
4408
4413
|
}catch(e){toast(t('toast.rejectFail')+': '+e.message,'error');}
|
|
4409
4414
|
}
|
|
4410
4415
|
async function adminToggleRole(userId,newRole){
|
|
@@ -4413,7 +4418,7 @@ async function adminToggleRole(userId,newRole){
|
|
|
4413
4418
|
try{
|
|
4414
4419
|
var r=await fetch('/api/sharing/change-role',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,role:newRole})});
|
|
4415
4420
|
var d=await r.json();
|
|
4416
|
-
if(d.ok){toast(t('toast.roleChanged'),'success');loadAdminData();}
|
|
4421
|
+
if(d.ok){toast(t('toast.roleChanged'),'success');_lastAdminFingerprint='';loadAdminData();}
|
|
4417
4422
|
else if(d.error==='cannot_demote_owner'){toast(t('admin.ownerHint'),'error');}
|
|
4418
4423
|
else{toast(d.error||t('toast.roleChangeFail'),'error');}
|
|
4419
4424
|
}catch(e){toast(t('toast.roleChangeFail')+': '+e.message,'error');}
|
|
@@ -4465,7 +4470,7 @@ async function adminRemoveUser(userId,username){
|
|
|
4465
4470
|
try{
|
|
4466
4471
|
var r=await fetch('/api/sharing/remove-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,cleanResources:clean})});
|
|
4467
4472
|
var d=await r.json();
|
|
4468
|
-
if(d.ok){toast(t('toast.userRemoved'),'success');loadAdminData();}
|
|
4473
|
+
if(d.ok){toast(t('toast.userRemoved'),'success');_lastAdminFingerprint='';loadAdminData();}
|
|
4469
4474
|
else if(d.error==='cannot_remove_owner'){toast(t('admin.ownerHint'),'error');}
|
|
4470
4475
|
else{toast(d.error||t('toast.removeFail'),'error');}
|
|
4471
4476
|
}catch(e){toast(t('toast.removeFail')+': '+e.message,'error');}
|
|
@@ -6597,16 +6602,16 @@ async function doSaveConfig(cfg, btnEl, savedId){
|
|
|
6597
6602
|
function done(){btnEl.disabled=false;btnEl.textContent=t('settings.save');}
|
|
6598
6603
|
try{
|
|
6599
6604
|
const r=await fetch('/api/config',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(cfg)});
|
|
6600
|
-
if(r.status===401){done();toast(t('settings.session.expired'),'error');return
|
|
6605
|
+
if(r.status===401){done();toast(t('settings.session.expired'),'error');return null;}
|
|
6601
6606
|
if(!r.ok) throw new Error(await r.text());
|
|
6607
|
+
var data=await r.json().catch(function(){return {ok:true};});
|
|
6602
6608
|
flashSaved(savedId);
|
|
6603
|
-
toast(t('settings.saved'),'success');
|
|
6604
6609
|
done();
|
|
6605
|
-
return
|
|
6610
|
+
return data;
|
|
6606
6611
|
}catch(e){
|
|
6607
6612
|
toast(t('settings.save.fail')+': '+e.message,'error');
|
|
6608
6613
|
done();
|
|
6609
|
-
return
|
|
6614
|
+
return null;
|
|
6610
6615
|
}
|
|
6611
6616
|
}
|
|
6612
6617
|
|
|
@@ -6701,11 +6706,19 @@ async function saveModelsConfig(){
|
|
|
6701
6706
|
await doSaveConfig(cfg, saveBtn, 'modelsSaved');
|
|
6702
6707
|
}
|
|
6703
6708
|
|
|
6709
|
+
function _hubSaveBtnLabel(){
|
|
6710
|
+
var on=document.getElementById('cfgSharingEnabled');
|
|
6711
|
+
if(on&&on.checked&&_sharingRole==='client'){
|
|
6712
|
+
var prevClient=sharingStatusCache&&sharingStatusCache.enabled&&sharingStatusCache.role==='client';
|
|
6713
|
+
return prevClient?t('settings.save'):t('sharing.joinTeam');
|
|
6714
|
+
}
|
|
6715
|
+
return t('settings.save');
|
|
6716
|
+
}
|
|
6704
6717
|
async function saveHubConfig(){
|
|
6705
6718
|
var card=document.getElementById('settingsSharingConfig');
|
|
6706
6719
|
var saveBtn=card.querySelector('.settings-actions .btn-primary');
|
|
6707
6720
|
saveBtn.disabled=true;saveBtn.textContent=t('settings.test.loading');
|
|
6708
|
-
function done(){saveBtn.disabled=false;saveBtn.textContent=
|
|
6721
|
+
function done(){saveBtn.disabled=false;saveBtn.textContent=_hubSaveBtnLabel();}
|
|
6709
6722
|
|
|
6710
6723
|
const cfg={};
|
|
6711
6724
|
var sharingEnabled=document.getElementById('cfgSharingEnabled').checked;
|
|
@@ -6772,14 +6785,25 @@ async function saveHubConfig(){
|
|
|
6772
6785
|
if(!(await confirmModal(switchMsg,{danger:true}))){done();return;}
|
|
6773
6786
|
}
|
|
6774
6787
|
|
|
6775
|
-
var
|
|
6776
|
-
if(
|
|
6788
|
+
var result=await doSaveConfig(cfg, saveBtn, 'hubSaved');
|
|
6789
|
+
if(result){
|
|
6777
6790
|
if(sharingEnabled&&_sharingRole==='hub'){
|
|
6778
6791
|
var adminNameEl=document.getElementById('cfgHubAdminName');
|
|
6779
6792
|
if(adminNameEl&&adminNameEl.value.trim()){
|
|
6780
6793
|
try{await fetch('/api/sharing/update-username',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:adminNameEl.value.trim()})});}catch(e){}
|
|
6781
6794
|
}
|
|
6782
6795
|
}
|
|
6796
|
+
if(sharingEnabled&&_sharingRole==='client'&&result.joinStatus){
|
|
6797
|
+
if(result.joinStatus==='pending'){
|
|
6798
|
+
toast(t('sharing.joinSent.pending'),'success');
|
|
6799
|
+
}else if(result.joinStatus==='active'){
|
|
6800
|
+
toast(t('sharing.joinSent.active'),'success');
|
|
6801
|
+
}else{
|
|
6802
|
+
toast(t('settings.saved'),'success');
|
|
6803
|
+
}
|
|
6804
|
+
}else{
|
|
6805
|
+
toast(t('settings.saved'),'success');
|
|
6806
|
+
}
|
|
6783
6807
|
_lastSidebarFingerprint='';
|
|
6784
6808
|
_lastSettingsFingerprint='';
|
|
6785
6809
|
_lastSharingConnStatus='';
|
|
@@ -7354,6 +7378,12 @@ function notifTypeText(n){
|
|
|
7354
7378
|
if(n.type==='user_offline'){
|
|
7355
7379
|
return t('notif.userOffline');
|
|
7356
7380
|
}
|
|
7381
|
+
if(n.type==='membership_approved'){
|
|
7382
|
+
return t('notif.membershipApproved');
|
|
7383
|
+
}
|
|
7384
|
+
if(n.type==='membership_rejected'){
|
|
7385
|
+
return t('notif.membershipRejected');
|
|
7386
|
+
}
|
|
7357
7387
|
return n.message||n.type;
|
|
7358
7388
|
}
|
|
7359
7389
|
|
package/src/viewer/server.ts
CHANGED
|
@@ -2440,6 +2440,7 @@ export class ViewerServer {
|
|
|
2440
2440
|
res.write("data: {\"type\":\"connected\"}\n\n");
|
|
2441
2441
|
this.notifSSEClients.push(res);
|
|
2442
2442
|
if (!this.notifPollTimer) this.startNotifPoll();
|
|
2443
|
+
else this.notifPollImmediate();
|
|
2443
2444
|
req.on("close", () => {
|
|
2444
2445
|
this.notifSSEClients = this.notifSSEClients.filter((c) => c !== res);
|
|
2445
2446
|
if (this.notifSSEClients.length === 0) this.stopNotifPoll();
|
|
@@ -2475,6 +2476,20 @@ export class ViewerServer {
|
|
|
2475
2476
|
if (this.notifPollTimer) { clearInterval(this.notifPollTimer); this.notifPollTimer = undefined; }
|
|
2476
2477
|
}
|
|
2477
2478
|
|
|
2479
|
+
private notifPollImmediate(): void {
|
|
2480
|
+
const hub = this.resolveHubConnection();
|
|
2481
|
+
if (!hub) return;
|
|
2482
|
+
hubRequestJson(hub.hubUrl, hub.userToken, "/api/v1/hub/notifications?unread=1")
|
|
2483
|
+
.then((data: any) => {
|
|
2484
|
+
const count = data?.unreadCount ?? 0;
|
|
2485
|
+
if (count !== this.lastKnownNotifCount) {
|
|
2486
|
+
this.lastKnownNotifCount = count;
|
|
2487
|
+
this.broadcastNotifSSE({ type: "update", unreadCount: count });
|
|
2488
|
+
}
|
|
2489
|
+
})
|
|
2490
|
+
.catch(() => {});
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2478
2493
|
private startHubHeartbeat(): void {
|
|
2479
2494
|
this.stopHubHeartbeat();
|
|
2480
2495
|
const sendHeartbeat = async () => {
|
|
@@ -2660,11 +2675,16 @@ export class ViewerServer {
|
|
|
2660
2675
|
const finalSharing = config.sharing as Record<string, unknown> | undefined;
|
|
2661
2676
|
const nowClient = Boolean(finalSharing?.enabled) && finalSharing?.role === "client";
|
|
2662
2677
|
const previouslyClient = oldSharingEnabled && oldSharingRole === "client";
|
|
2678
|
+
let joinStatus: string | undefined;
|
|
2663
2679
|
if (nowClient && !previouslyClient) {
|
|
2664
|
-
|
|
2680
|
+
try {
|
|
2681
|
+
joinStatus = await this.autoJoinOnSave(finalSharing);
|
|
2682
|
+
} catch (e) {
|
|
2683
|
+
this.log.warn(`Auto-join on save failed: ${e}`);
|
|
2684
|
+
}
|
|
2665
2685
|
}
|
|
2666
2686
|
|
|
2667
|
-
this.jsonResponse(res, { ok: true });
|
|
2687
|
+
this.jsonResponse(res, { ok: true, joinStatus });
|
|
2668
2688
|
} catch (e) {
|
|
2669
2689
|
this.log.warn(`handleSaveConfig error: ${e}`);
|
|
2670
2690
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -2673,11 +2693,11 @@ export class ViewerServer {
|
|
|
2673
2693
|
});
|
|
2674
2694
|
}
|
|
2675
2695
|
|
|
2676
|
-
private async autoJoinOnSave(sharing: Record<string, unknown>): Promise<
|
|
2696
|
+
private async autoJoinOnSave(sharing: Record<string, unknown>): Promise<string | undefined> {
|
|
2677
2697
|
const clientCfg = sharing.client as Record<string, unknown> | undefined;
|
|
2678
2698
|
const hubAddress = String(clientCfg?.hubAddress || "");
|
|
2679
2699
|
const teamToken = String(clientCfg?.teamToken || "");
|
|
2680
|
-
if (!hubAddress || !teamToken) return;
|
|
2700
|
+
if (!hubAddress || !teamToken) return undefined;
|
|
2681
2701
|
const hubUrl = normalizeHubUrl(hubAddress);
|
|
2682
2702
|
const os = await import("os");
|
|
2683
2703
|
const nickname = String(clientCfg?.nickname || "");
|
|
@@ -2704,6 +2724,7 @@ export class ViewerServer {
|
|
|
2704
2724
|
if (result.userToken) {
|
|
2705
2725
|
this.startHubHeartbeat();
|
|
2706
2726
|
}
|
|
2727
|
+
return result.status;
|
|
2707
2728
|
}
|
|
2708
2729
|
|
|
2709
2730
|
private async notifyHubLeave(): Promise<void> {
|