@memtensor/memos-local-openclaw-plugin 1.0.4-beta.12 → 1.0.4-beta.14
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/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +1 -0
- package/dist/hub/server.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +96 -32
- 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/config.ts +2 -1
- package/src/hub/server.ts +1 -0
- package/src/viewer/html.ts +96 -32
- 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/config.ts
CHANGED
|
@@ -128,7 +128,8 @@ export function resolveConfig(raw: Partial<MemosLocalConfig> | undefined, stateD
|
|
|
128
128
|
userToken: cfg.sharing?.client?.userToken ?? "",
|
|
129
129
|
teamToken: cfg.sharing?.client?.teamToken ?? "",
|
|
130
130
|
pendingUserId: cfg.sharing?.client?.pendingUserId ?? "",
|
|
131
|
-
|
|
131
|
+
nickname: cfg.sharing?.client?.nickname ?? "",
|
|
132
|
+
} : { hubAddress: "", userToken: "", teamToken: "", pendingUserId: "", nickname: "" };
|
|
132
133
|
return { enabled, role, hub, client, capabilities: sharingCapabilities };
|
|
133
134
|
})(),
|
|
134
135
|
};
|
package/src/hub/server.ts
CHANGED
|
@@ -314,6 +314,7 @@ export class HubServer {
|
|
|
314
314
|
{ userId: user.id, username: user.username, role: user.role, status: user.status },
|
|
315
315
|
this.authSecret,
|
|
316
316
|
);
|
|
317
|
+
this.userManager.approveUser(user.id, token);
|
|
317
318
|
return this.json(res, 200, { status: "active", userToken: token });
|
|
318
319
|
}
|
|
319
320
|
return this.json(res, 200, { status: user.status });
|
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':'这将清除当前连接数据并重新提交加入申请,是否继续?',
|
|
@@ -3592,20 +3599,25 @@ function selectSharingRole(role){
|
|
|
3592
3599
|
var tp=document.getElementById('sharingTeamPanel');
|
|
3593
3600
|
var ap=document.getElementById('sharingAdminPanel');
|
|
3594
3601
|
if(role==='client'){
|
|
3595
|
-
if(sp) sp.style.display='none';
|
|
3602
|
+
if(sp) { sp.style.display='none'; sp.innerHTML=''; }
|
|
3596
3603
|
if(tp) tp.style.display='none';
|
|
3597
3604
|
if(ap) ap.style.display='none';
|
|
3598
3605
|
}else{
|
|
3599
|
-
if(sp) sp.style.display='';
|
|
3606
|
+
if(sp) { sp.style.display='none'; sp.innerHTML=''; }
|
|
3600
3607
|
if(tp) tp.style.display='';
|
|
3601
|
-
if(ap)
|
|
3608
|
+
if(ap) tp.style.display='';
|
|
3602
3609
|
}
|
|
3610
|
+
_lastSettingsFingerprint='';
|
|
3611
|
+
setTimeout(function(){ loadSharingStatus(true); },200);
|
|
3603
3612
|
if(role==='hub'){
|
|
3604
3613
|
var tk=document.getElementById('cfgHubTeamToken');
|
|
3605
3614
|
if(!tk.value.trim()) tk.value=_genToken(18);
|
|
3606
3615
|
var tn=document.getElementById('cfgHubTeamName');
|
|
3607
3616
|
if(!tn.value.trim()) tn.value='My Team';
|
|
3608
3617
|
}
|
|
3618
|
+
var card=document.getElementById('settingsSharingConfig');
|
|
3619
|
+
var saveBtn=card&&card.querySelector('.settings-actions .btn-primary');
|
|
3620
|
+
if(saveBtn&&typeof _hubSaveBtnLabel==='function') saveBtn.textContent=_hubSaveBtnLabel();
|
|
3609
3621
|
}
|
|
3610
3622
|
var _cachedLocalIP='';
|
|
3611
3623
|
function updateHubShareInfo(){
|
|
@@ -3697,19 +3709,19 @@ function switchView(view){
|
|
|
3697
3709
|
else if(view==='analytics') loadMetrics();
|
|
3698
3710
|
else if(view==='logs') loadLogs();
|
|
3699
3711
|
else if(view==='settings'){loadConfig().then(function(){
|
|
3700
|
-
var notDismissed=localStorage.getItem('memos-team-guide-dismissed')!=='1';
|
|
3701
3712
|
var sharingOn=document.getElementById('cfgSharingEnabled');
|
|
3702
3713
|
var sharingNotEnabled=!sharingOn||!sharingOn.checked;
|
|
3703
|
-
if(
|
|
3714
|
+
if(sharingNotEnabled){
|
|
3704
3715
|
switchSettingsTab('hub',document.querySelector('.settings-tab-btn[data-tab="hub"]'));
|
|
3705
3716
|
}
|
|
3706
3717
|
});loadModelHealth();}
|
|
3707
3718
|
else if(view==='import'){if(!window._migrateRunning) migrateScan(false);}
|
|
3708
|
-
else if(view==='admin'){loadAdminData();}
|
|
3719
|
+
else if(view==='admin'){_lastAdminFingerprint='';loadAdminData();}
|
|
3709
3720
|
}
|
|
3710
3721
|
|
|
3711
3722
|
function onMemoryScopeChange(){
|
|
3712
3723
|
memorySearchScope=document.getElementById('memorySearchScope')?.value||'local';
|
|
3724
|
+
try{localStorage.setItem('memos_memorySearchScope',memorySearchScope);}catch(e){}
|
|
3713
3725
|
currentPage=1;
|
|
3714
3726
|
activeSession=null;activeRole='';
|
|
3715
3727
|
_lastMemoriesFingerprint='';
|
|
@@ -3775,10 +3787,13 @@ async function loadSharingStatus(forcePending){
|
|
|
3775
3787
|
if(_lastSharingConnStatus==='pending'&&curStatus==='connected'){
|
|
3776
3788
|
toast(t('sharing.approved.toast'),'success');
|
|
3777
3789
|
loadMemories();loadTasks();loadSkills();
|
|
3790
|
+
if(_notifSSE){_notifSSE.close();_notifSSE=null;_notifSSEConnected=false;}
|
|
3791
|
+
connectNotifSSE();
|
|
3792
|
+
loadNotifications();
|
|
3778
3793
|
}
|
|
3779
3794
|
_lastSharingConnStatus=curStatus;
|
|
3780
3795
|
if(curStatus==='pending'&&!_clientPendingPollTimer){
|
|
3781
|
-
_clientPendingPollTimer=setInterval(function(){loadSharingStatus(false);},
|
|
3796
|
+
_clientPendingPollTimer=setInterval(function(){loadSharingStatus(false);},5000);
|
|
3782
3797
|
}
|
|
3783
3798
|
if(curStatus!=='pending'&&_clientPendingPollTimer){
|
|
3784
3799
|
clearInterval(_clientPendingPollTimer);
|
|
@@ -3832,6 +3847,7 @@ function renderSharingSidebar(data){
|
|
|
3832
3847
|
html+='<span class="label">'+t('sharing.sidebar.identity')+'</span><span class="value">'+esc(conn.user.username||'-')+'</span>';
|
|
3833
3848
|
if(conn.teamName) html+='<span class="label">'+t('sharing.team')+'</span><span class="value">'+esc(conn.teamName)+'</span>';
|
|
3834
3849
|
html+='</div>';
|
|
3850
|
+
html+='<div style="margin-top:8px"><button class="btn btn-sm btn-primary" onclick="retryHubJoin()" style="font-size:11px">'+t('sharing.retryJoin')+'</button></div>';
|
|
3835
3851
|
statusEl.innerHTML=html;
|
|
3836
3852
|
hintEl.textContent=t('sharing.rejected.hint');
|
|
3837
3853
|
}else if(conn.removed&&conn.user){
|
|
@@ -3840,6 +3856,7 @@ function renderSharingSidebar(data){
|
|
|
3840
3856
|
html+='<span class="label">'+t('sharing.sidebar.identity')+'</span><span class="value">'+esc(conn.user.username||'-')+'</span>';
|
|
3841
3857
|
if(conn.teamName) html+='<span class="label">'+t('sharing.team')+'</span><span class="value">'+esc(conn.teamName)+'</span>';
|
|
3842
3858
|
html+='</div>';
|
|
3859
|
+
html+='<div style="margin-top:8px"><button class="btn btn-sm btn-primary" onclick="retryHubJoin()" style="font-size:11px">'+t('sharing.retryJoin')+'</button></div>';
|
|
3843
3860
|
statusEl.innerHTML=html;
|
|
3844
3861
|
hintEl.textContent=t('sharing.removed.hint');
|
|
3845
3862
|
}else if(conn.connected&&conn.user){
|
|
@@ -3876,6 +3893,9 @@ function renderSharingSettings(data){
|
|
|
3876
3893
|
if(!data||!data.enabled){
|
|
3877
3894
|
statusEl.innerHTML='';teamEl.innerHTML='';adminEl.innerHTML='';
|
|
3878
3895
|
if(panelsWrap) panelsWrap.style.display='none';
|
|
3896
|
+
var adminNavTab0=document.querySelector('.tab[data-view="admin"]');
|
|
3897
|
+
if(adminNavTab0) adminNavTab0.style.display='none';
|
|
3898
|
+
if(_activeView==='admin') switchView('memories');
|
|
3879
3899
|
return;
|
|
3880
3900
|
}
|
|
3881
3901
|
if(panelsWrap) panelsWrap.style.display='';
|
|
@@ -3883,9 +3903,20 @@ function renderSharingSettings(data){
|
|
|
3883
3903
|
var user=conn.user||{};
|
|
3884
3904
|
var actualRole=data.role||_sharingRole||'client';
|
|
3885
3905
|
if(data.role) _sharingRole=data.role;
|
|
3906
|
+
var prevIsAdmin=!!window._isHubAdmin;
|
|
3886
3907
|
var isAdmin=(data.admin&&data.admin.canManageUsers)||(conn.connected&&user.role==='admin')||(actualRole==='hub');
|
|
3887
3908
|
window._isHubAdmin=isAdmin;
|
|
3888
3909
|
if(isAdmin) startAdminPoll();
|
|
3910
|
+
var adminNavTab=document.querySelector('.tab[data-view="admin"]');
|
|
3911
|
+
if(adminNavTab){
|
|
3912
|
+
var showTab=(actualRole==='hub')||(conn.connected);
|
|
3913
|
+
adminNavTab.style.display=showTab?'':'none';
|
|
3914
|
+
if(!showTab&&_activeView==='admin') switchView('memories');
|
|
3915
|
+
}
|
|
3916
|
+
if(prevIsAdmin&&!isAdmin&&_activeView==='admin'){
|
|
3917
|
+
_lastAdminFingerprint='';
|
|
3918
|
+
loadAdminData();
|
|
3919
|
+
}
|
|
3889
3920
|
var hubAdminBtn=document.getElementById('hubAdminEntryBtn');
|
|
3890
3921
|
|
|
3891
3922
|
if(actualRole==='hub'){
|
|
@@ -4059,7 +4090,7 @@ async function approveSharingUser(userId,username){
|
|
|
4059
4090
|
try{
|
|
4060
4091
|
const r=await fetch('/api/sharing/approve-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
|
|
4061
4092
|
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');}
|
|
4093
|
+
if(d.ok){toast(t('toast.userApproved'),'success');loadSharingPendingUsers();loadSharingStatus(true);_lastAdminFingerprint='';loadAdminData();} else {toast(d.error||t('toast.approveFail'),'error');}
|
|
4063
4094
|
}catch(e){toast(t('toast.approveFail')+': '+e.message,'error');}
|
|
4064
4095
|
}
|
|
4065
4096
|
|
|
@@ -4067,24 +4098,17 @@ async function rejectSharingUser(userId,username){
|
|
|
4067
4098
|
try{
|
|
4068
4099
|
const r=await fetch('/api/sharing/reject-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
|
|
4069
4100
|
const d=await r.json();
|
|
4070
|
-
if(d.ok){toast(t('toast.userRejected'),'success');loadSharingPendingUsers();} else {toast(d.error||t('toast.rejectFail'),'error');}
|
|
4101
|
+
if(d.ok){toast(t('toast.userRejected'),'success');loadSharingPendingUsers();_lastAdminFingerprint='';loadAdminData();} else {toast(d.error||t('toast.rejectFail'),'error');}
|
|
4071
4102
|
}catch(e){toast(t('toast.rejectFail')+': '+e.message,'error');}
|
|
4072
4103
|
}
|
|
4073
4104
|
|
|
4074
4105
|
/* ─── Team Setup Guide ─── */
|
|
4075
|
-
var TEAM_GUIDE_DISMISSED_KEY='memos-team-guide-dismissed';
|
|
4076
4106
|
function updateTeamGuide(sharingData){
|
|
4077
4107
|
var el=document.getElementById('teamSetupGuide');
|
|
4078
4108
|
if(!el) return;
|
|
4079
|
-
if(localStorage.getItem(TEAM_GUIDE_DISMISSED_KEY)==='1'){el.style.display='none';return;}
|
|
4080
4109
|
var isConfigured=sharingData&&sharingData.enabled;
|
|
4081
4110
|
el.style.display=isConfigured?'none':'block';
|
|
4082
4111
|
}
|
|
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
4112
|
function guideGoToHub(role){
|
|
4089
4113
|
switchSettingsTab('hub',document.querySelector('.settings-tab-btn[data-tab="hub"]'));
|
|
4090
4114
|
var chk=document.getElementById('cfgSharingEnabled');
|
|
@@ -4200,7 +4224,7 @@ async function loadAdminData(){
|
|
|
4200
4224
|
var _newMemories=Array.isArray(memoriesR.memories)?memoriesR.memories:[];
|
|
4201
4225
|
var pending=isAdmin?(Array.isArray(pendingR.users)?pendingR.users:[]):[];
|
|
4202
4226
|
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(',')
|
|
4227
|
+
+':'+_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
4228
|
+':'+_newMemories.map(function(m){return m.id}).join(',')
|
|
4205
4229
|
+':'+_newTasks.map(function(t){return t.id+'|'+(t.status||'')}).join(',')
|
|
4206
4230
|
+':'+_newSkills.map(function(s){return s.id+'|'+(s.status||'')}).join(',')
|
|
@@ -4396,7 +4420,7 @@ async function adminApproveUser(userId,username){
|
|
|
4396
4420
|
try{
|
|
4397
4421
|
var r=await fetch('/api/sharing/approve-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,username:username})});
|
|
4398
4422
|
var d=await r.json();
|
|
4399
|
-
if(d.ok){toast(t('toast.userApproved'),'success');loadAdminData();}else{toast(d.error||t('toast.approveFail'),'error');}
|
|
4423
|
+
if(d.ok){toast(t('toast.userApproved'),'success');_lastAdminFingerprint='';loadAdminData();}else{toast(d.error||t('toast.approveFail'),'error');}
|
|
4400
4424
|
}catch(e){toast(t('toast.approveFail')+': '+e.message,'error');}
|
|
4401
4425
|
}
|
|
4402
4426
|
async function adminRejectUser(userId){
|
|
@@ -4404,7 +4428,7 @@ async function adminRejectUser(userId){
|
|
|
4404
4428
|
try{
|
|
4405
4429
|
var r=await fetch('/api/sharing/reject-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId})});
|
|
4406
4430
|
var d=await r.json();
|
|
4407
|
-
if(d.ok){toast(t('toast.userRejected'),'success');loadAdminData();}else{toast(d.error||t('toast.rejectFail'),'error');}
|
|
4431
|
+
if(d.ok){toast(t('toast.userRejected'),'success');_lastAdminFingerprint='';loadAdminData();}else{toast(d.error||t('toast.rejectFail'),'error');}
|
|
4408
4432
|
}catch(e){toast(t('toast.rejectFail')+': '+e.message,'error');}
|
|
4409
4433
|
}
|
|
4410
4434
|
async function adminToggleRole(userId,newRole){
|
|
@@ -4413,7 +4437,14 @@ async function adminToggleRole(userId,newRole){
|
|
|
4413
4437
|
try{
|
|
4414
4438
|
var r=await fetch('/api/sharing/change-role',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,role:newRole})});
|
|
4415
4439
|
var d=await r.json();
|
|
4416
|
-
if(d.ok){
|
|
4440
|
+
if(d.ok){
|
|
4441
|
+
toast(t('toast.roleChanged'),'success');
|
|
4442
|
+
_lastAdminFingerprint='';
|
|
4443
|
+
_lastSettingsFingerprint='';
|
|
4444
|
+
_lastSidebarFingerprint='';
|
|
4445
|
+
await loadSharingStatus(false);
|
|
4446
|
+
loadAdminData();
|
|
4447
|
+
}
|
|
4417
4448
|
else if(d.error==='cannot_demote_owner'){toast(t('admin.ownerHint'),'error');}
|
|
4418
4449
|
else{toast(d.error||t('toast.roleChangeFail'),'error');}
|
|
4419
4450
|
}catch(e){toast(t('toast.roleChangeFail')+': '+e.message,'error');}
|
|
@@ -4465,7 +4496,7 @@ async function adminRemoveUser(userId,username){
|
|
|
4465
4496
|
try{
|
|
4466
4497
|
var r=await fetch('/api/sharing/remove-user',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({userId:userId,cleanResources:clean})});
|
|
4467
4498
|
var d=await r.json();
|
|
4468
|
-
if(d.ok){toast(t('toast.userRemoved'),'success');loadAdminData();}
|
|
4499
|
+
if(d.ok){toast(t('toast.userRemoved'),'success');_lastAdminFingerprint='';loadAdminData();}
|
|
4469
4500
|
else if(d.error==='cannot_remove_owner'){toast(t('admin.ownerHint'),'error');}
|
|
4470
4501
|
else{toast(d.error||t('toast.removeFail'),'error');}
|
|
4471
4502
|
}catch(e){toast(t('toast.removeFail')+': '+e.message,'error');}
|
|
@@ -6597,16 +6628,16 @@ async function doSaveConfig(cfg, btnEl, savedId){
|
|
|
6597
6628
|
function done(){btnEl.disabled=false;btnEl.textContent=t('settings.save');}
|
|
6598
6629
|
try{
|
|
6599
6630
|
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
|
|
6631
|
+
if(r.status===401){done();toast(t('settings.session.expired'),'error');return null;}
|
|
6601
6632
|
if(!r.ok) throw new Error(await r.text());
|
|
6633
|
+
var data=await r.json().catch(function(){return {ok:true};});
|
|
6602
6634
|
flashSaved(savedId);
|
|
6603
|
-
toast(t('settings.saved'),'success');
|
|
6604
6635
|
done();
|
|
6605
|
-
return
|
|
6636
|
+
return data;
|
|
6606
6637
|
}catch(e){
|
|
6607
6638
|
toast(t('settings.save.fail')+': '+e.message,'error');
|
|
6608
6639
|
done();
|
|
6609
|
-
return
|
|
6640
|
+
return null;
|
|
6610
6641
|
}
|
|
6611
6642
|
}
|
|
6612
6643
|
|
|
@@ -6701,11 +6732,19 @@ async function saveModelsConfig(){
|
|
|
6701
6732
|
await doSaveConfig(cfg, saveBtn, 'modelsSaved');
|
|
6702
6733
|
}
|
|
6703
6734
|
|
|
6735
|
+
function _hubSaveBtnLabel(){
|
|
6736
|
+
var on=document.getElementById('cfgSharingEnabled');
|
|
6737
|
+
if(on&&on.checked&&_sharingRole==='client'){
|
|
6738
|
+
var prevClient=sharingStatusCache&&sharingStatusCache.enabled&&sharingStatusCache.role==='client';
|
|
6739
|
+
return prevClient?t('settings.save'):t('sharing.joinTeam');
|
|
6740
|
+
}
|
|
6741
|
+
return t('settings.save');
|
|
6742
|
+
}
|
|
6704
6743
|
async function saveHubConfig(){
|
|
6705
6744
|
var card=document.getElementById('settingsSharingConfig');
|
|
6706
6745
|
var saveBtn=card.querySelector('.settings-actions .btn-primary');
|
|
6707
6746
|
saveBtn.disabled=true;saveBtn.textContent=t('settings.test.loading');
|
|
6708
|
-
function done(){saveBtn.disabled=false;saveBtn.textContent=
|
|
6747
|
+
function done(){saveBtn.disabled=false;saveBtn.textContent=_hubSaveBtnLabel();}
|
|
6709
6748
|
|
|
6710
6749
|
const cfg={};
|
|
6711
6750
|
var sharingEnabled=document.getElementById('cfgSharingEnabled').checked;
|
|
@@ -6772,14 +6811,25 @@ async function saveHubConfig(){
|
|
|
6772
6811
|
if(!(await confirmModal(switchMsg,{danger:true}))){done();return;}
|
|
6773
6812
|
}
|
|
6774
6813
|
|
|
6775
|
-
var
|
|
6776
|
-
if(
|
|
6814
|
+
var result=await doSaveConfig(cfg, saveBtn, 'hubSaved');
|
|
6815
|
+
if(result){
|
|
6777
6816
|
if(sharingEnabled&&_sharingRole==='hub'){
|
|
6778
6817
|
var adminNameEl=document.getElementById('cfgHubAdminName');
|
|
6779
6818
|
if(adminNameEl&&adminNameEl.value.trim()){
|
|
6780
6819
|
try{await fetch('/api/sharing/update-username',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:adminNameEl.value.trim()})});}catch(e){}
|
|
6781
6820
|
}
|
|
6782
6821
|
}
|
|
6822
|
+
if(sharingEnabled&&_sharingRole==='client'&&result.joinStatus){
|
|
6823
|
+
if(result.joinStatus==='pending'){
|
|
6824
|
+
toast(t('sharing.joinSent.pending'),'success');
|
|
6825
|
+
}else if(result.joinStatus==='active'){
|
|
6826
|
+
toast(t('sharing.joinSent.active'),'success');
|
|
6827
|
+
}else{
|
|
6828
|
+
toast(t('settings.saved'),'success');
|
|
6829
|
+
}
|
|
6830
|
+
}else{
|
|
6831
|
+
toast(t('settings.saved'),'success');
|
|
6832
|
+
}
|
|
6783
6833
|
_lastSidebarFingerprint='';
|
|
6784
6834
|
_lastSettingsFingerprint='';
|
|
6785
6835
|
_lastSharingConnStatus='';
|
|
@@ -7354,6 +7404,12 @@ function notifTypeText(n){
|
|
|
7354
7404
|
if(n.type==='user_offline'){
|
|
7355
7405
|
return t('notif.userOffline');
|
|
7356
7406
|
}
|
|
7407
|
+
if(n.type==='membership_approved'){
|
|
7408
|
+
return t('notif.membershipApproved');
|
|
7409
|
+
}
|
|
7410
|
+
if(n.type==='membership_rejected'){
|
|
7411
|
+
return t('notif.membershipRejected');
|
|
7412
|
+
}
|
|
7357
7413
|
return n.message||n.type;
|
|
7358
7414
|
}
|
|
7359
7415
|
|
|
@@ -8810,6 +8866,14 @@ async function checkForUpdate(){
|
|
|
8810
8866
|
}
|
|
8811
8867
|
|
|
8812
8868
|
/* ─── Init ─── */
|
|
8869
|
+
try{
|
|
8870
|
+
var savedScope=localStorage.getItem('memos_memorySearchScope');
|
|
8871
|
+
if(savedScope&&(savedScope==='local'||savedScope==='allLocal'||savedScope==='hub')){
|
|
8872
|
+
memorySearchScope=savedScope;
|
|
8873
|
+
var scopeEl=document.getElementById('memorySearchScope');
|
|
8874
|
+
if(scopeEl) scopeEl.value=savedScope;
|
|
8875
|
+
}
|
|
8876
|
+
}catch(e){}
|
|
8813
8877
|
document.getElementById('modalOverlay').addEventListener('click',e=>{if(e.target.id==='modalOverlay')closeModal()});
|
|
8814
8878
|
document.getElementById('searchInput').addEventListener('keydown',e=>{if(e.key==='Escape'){e.target.value='';currentPage=1;if(memorySearchScope==='hub')loadHubMemories();else loadMemories();}});
|
|
8815
8879
|
applyI18n();
|
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> {
|