my-provider-dash-manager 0.5.0 → 0.6.0
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/README.md +9 -1
- package/package.json +1 -1
- package/src/main.js +66 -1
package/README.md
CHANGED
|
@@ -72,6 +72,11 @@ Each provider lists its accounts; hovering one reveals open / sleep / close / fi
|
|
|
72
72
|
|
|
73
73
|

|
|
74
74
|
|
|
75
|
+
Browser controls appear in the strip while a tab is open: back, forward, reload, dashboard, and an
|
|
76
|
+
address bar that navigates inside that account's own session.
|
|
77
|
+
|
|
78
|
+

|
|
79
|
+
|
|
75
80
|
**The top bar is only your open tabs** — filter them, or group them by provider with collapsible sections.
|
|
76
81
|
Slept tabs stay in the strip (dimmed) and resume on click.
|
|
77
82
|
|
|
@@ -126,7 +131,10 @@ sessions, one place.
|
|
|
126
131
|
hover actions: open, sleep, close, fill login, connect an API token, manage. Collapse any provider.
|
|
127
132
|
- **Top bar — open tabs only.** A filter box and a group-by-provider toggle with collapsible groups.
|
|
128
133
|
- **Home** is always one click (or `Esc`) away: a grid of every service and account with live status.
|
|
129
|
-
-
|
|
134
|
+
- **Browser controls** while a tab is open: back, forward, reload/stop, back-to-dashboard, and an
|
|
135
|
+
editable address bar that navigates inside that account's own session (http/https only).
|
|
136
|
+
- Keyboard: `Ctrl+W` sleep, `Ctrl+Shift+W` sleep all, `Ctrl+Tab` cycle, `Ctrl+1…9` jump,
|
|
137
|
+
`Alt+Left`/`Alt+Right` back/forward, `Ctrl+R`/`F5` reload, `Alt+Home` dashboard, `Ctrl+L` address.
|
|
130
138
|
|
|
131
139
|
### Saved logins
|
|
132
140
|
- **Remembers logins you type** inside an account tab, bound to *that* account, so two accounts on the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-provider-dash-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Manage many accounts per cloud service (Vercel, Netlify, Supabase, Cloudflare, Railway, Render, GitHub) in one desktop app \u2014 isolated persistent sessions, one tab per account.",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"scripts": {
|
package/src/main.js
CHANGED
|
@@ -282,6 +282,8 @@ class ViewManager {
|
|
|
282
282
|
};
|
|
283
283
|
view.webContents.on('did-navigate', onNav);
|
|
284
284
|
view.webContents.on('did-navigate-in-page', onNav); // dashboards route client-side (pushState) — must track too
|
|
285
|
+
view.webContents.on('did-start-loading', () => this.emitState());
|
|
286
|
+
view.webContents.on('did-stop-loading', () => this.emitState());
|
|
285
287
|
view.webContents.on('render-process-gone', () => {
|
|
286
288
|
console.error(`renderer gone: ${key}`);
|
|
287
289
|
this.hibernate(key); // drop the dead view; partition survives, reopening rehydrates
|
|
@@ -439,12 +441,43 @@ class ViewManager {
|
|
|
439
441
|
key: k, svc: s.svc, id: s.id, label: a.label || s.id, colorIdx: a.colorIdx || 0,
|
|
440
442
|
title: t ? t.title : '', url: t ? t.url : s.url,
|
|
441
443
|
live: !!t, visible: t ? t.visible : false,
|
|
444
|
+
canGoBack: !!(t && !t.view.webContents.isDestroyed() && t.view.webContents.navigationHistory.canGoBack()),
|
|
445
|
+
canGoForward: !!(t && !t.view.webContents.isDestroyed() && t.view.webContents.navigationHistory.canGoForward()),
|
|
446
|
+
loading: !!(t && !t.view.webContents.isDestroyed() && t.view.webContents.isLoading()),
|
|
442
447
|
lastState: t ? t.lastState : (a.lastState || 'unknown'),
|
|
443
448
|
};
|
|
444
449
|
}),
|
|
445
450
|
});
|
|
446
451
|
}
|
|
447
452
|
|
|
453
|
+
// Browser-style navigation for the ACTIVE tab. `navigate` accepts http(s) only — the address bar
|
|
454
|
+
// must never become a way to launch other schemes from the chrome.
|
|
455
|
+
nav(action, url) {
|
|
456
|
+
const t = this.tabs.get(this.activeKey);
|
|
457
|
+
if (!t || t.view.webContents.isDestroyed()) return false;
|
|
458
|
+
const wc = t.view.webContents;
|
|
459
|
+
switch (action) {
|
|
460
|
+
case 'back': if (wc.navigationHistory.canGoBack()) wc.navigationHistory.goBack(); break;
|
|
461
|
+
case 'forward': if (wc.navigationHistory.canGoForward()) wc.navigationHistory.goForward(); break;
|
|
462
|
+
case 'reload': wc.reload(); break;
|
|
463
|
+
case 'stop': wc.stop(); break;
|
|
464
|
+
case 'dashboard': { const svc = serviceByKey(t.svc); if (svc) wc.loadURL(svc.dashboardUrl).catch(() => {}); break; }
|
|
465
|
+
case 'navigate': {
|
|
466
|
+
let target = String(url || '').trim();
|
|
467
|
+
if (!target) return false;
|
|
468
|
+
if (!/^[a-z][a-z0-9+.-]*:/i.test(target)) target = `https://${target}`; // "example.com/path" → https
|
|
469
|
+
let parsed;
|
|
470
|
+
try { parsed = new URL(target); } catch { return false; }
|
|
471
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') return false;
|
|
472
|
+
wc.loadURL(parsed.href).catch(err => console.error(`navigate failed ${this.activeKey}:`, err.message));
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
default: return false;
|
|
476
|
+
}
|
|
477
|
+
this.emitState();
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
|
|
448
481
|
snapshotMemory() {
|
|
449
482
|
// Summing workingSetSize over-reports badly: Chromium maps shared pages (the framework binary and
|
|
450
483
|
// friends) into every process, so they are counted once per process — with 10 processes that read
|
|
@@ -575,6 +608,25 @@ async function runSmoke() {
|
|
|
575
608
|
const mem = vm.snapshotMemory();
|
|
576
609
|
check('live-tabs', mem.liveTabs === 3, `${mem.totalMB}MB`);
|
|
577
610
|
|
|
611
|
+
// browser-style navigation on the active tab (address bar, back, dashboard) — http(s) only
|
|
612
|
+
const key = accountKey('netlify', 'a1');
|
|
613
|
+
vm.activate(key);
|
|
614
|
+
const urlOf = () => vm.tabs.get(key).view.webContents.getURL();
|
|
615
|
+
const waitFor = async (pred, ms = 15000) => { const t0 = Date.now(); while (Date.now() - t0 < ms) { if (pred()) return true; await sleep(250); } return pred(); };
|
|
616
|
+
check('nav-rejects-javascript-scheme', vm.nav('navigate', 'javascript:alert(1)') === false && !urlOf().startsWith('javascript:'));
|
|
617
|
+
check('nav-rejects-file-scheme', vm.nav('navigate', 'file:///C:/Windows') === false && !urlOf().startsWith('file:'));
|
|
618
|
+
check('nav-rejects-empty', vm.nav('navigate', ' ') === false);
|
|
619
|
+
vm.nav('navigate', 'example.com'); // bare host → https
|
|
620
|
+
check('nav-address-goes-there', await waitFor(() => /^https:\/\/example\.com\/?$/.test(urlOf())), urlOf());
|
|
621
|
+
check('nav-can-go-back', vm.tabs.get(key).view.webContents.navigationHistory.canGoBack());
|
|
622
|
+
vm.nav('back');
|
|
623
|
+
check('nav-back-returns', await waitFor(() => /netlify\.com/.test(urlOf())), urlOf());
|
|
624
|
+
vm.nav('navigate', 'https://example.org/');
|
|
625
|
+
await waitFor(() => /example\.org/.test(urlOf()));
|
|
626
|
+
vm.nav('dashboard');
|
|
627
|
+
check('nav-dashboard-returns-to-service', await waitFor(() => /app\.netlify\.com/.test(urlOf())), urlOf());
|
|
628
|
+
check('nav-noop-without-active-tab', (() => { const k = vm.activeKey; vm.activeKey = null; const r = vm.nav('reload'); vm.activeKey = k; return r === false; })());
|
|
629
|
+
|
|
578
630
|
fs.writeFileSync(path.join(__dirname, '..', 'smoke-report.json'), JSON.stringify(results, null, 2));
|
|
579
631
|
app.exit(results.checks.every(c => c.ok) ? 0 : 1);
|
|
580
632
|
}
|
|
@@ -938,8 +990,20 @@ function runInteractive(captureMode = false) {
|
|
|
938
990
|
})),
|
|
939
991
|
],
|
|
940
992
|
},
|
|
993
|
+
{
|
|
994
|
+
label: 'Navigate',
|
|
995
|
+
submenu: [
|
|
996
|
+
{ label: 'Back', accelerator: 'Alt+Left', click: () => vm.nav('back') },
|
|
997
|
+
{ label: 'Forward', accelerator: 'Alt+Right', click: () => vm.nav('forward') },
|
|
998
|
+
{ label: 'Reload', accelerator: 'CmdOrCtrl+R', click: () => vm.nav('reload') },
|
|
999
|
+
{ label: 'Reload', accelerator: 'F5', click: () => vm.nav('reload') },
|
|
1000
|
+
{ label: 'Dashboard', accelerator: 'Alt+Home', click: () => vm.nav('dashboard') },
|
|
1001
|
+
{ type: 'separator' },
|
|
1002
|
+
{ label: 'Focus Address Bar', accelerator: 'CmdOrCtrl+L', click: () => { if (win.webContents && !win.webContents.isDestroyed()) win.webContents.send('focus-address'); } },
|
|
1003
|
+
],
|
|
1004
|
+
},
|
|
941
1005
|
{ label: 'Edit', submenu: [{ role: 'copy' }, { role: 'paste' }, { role: 'selectAll' }] },
|
|
942
|
-
{ label: 'View', submenu: [{ role: '
|
|
1006
|
+
{ label: 'View', submenu: [{ role: 'zoomIn' }, { role: 'zoomOut' }, { role: 'zoomReset' }, { type: 'separator' }, { role: 'toggleDevTools' }] },
|
|
943
1007
|
]);
|
|
944
1008
|
Menu.setApplicationMenu(menu);
|
|
945
1009
|
|
|
@@ -989,6 +1053,7 @@ function runInteractive(captureMode = false) {
|
|
|
989
1053
|
return key;
|
|
990
1054
|
});
|
|
991
1055
|
ipcMain.handle('activate-tab', (_e, key) => vm.activate(key));
|
|
1056
|
+
ipcMain.handle('nav', (_e, action, url) => vm.nav(String(action || ''), url));
|
|
992
1057
|
ipcMain.handle('sleep-tab', (_e, key) => vm.hibernate(key));
|
|
993
1058
|
ipcMain.handle('sleep-all', () => vm.hibernateAll());
|
|
994
1059
|
ipcMain.handle('update-account', (_e, svc, id, patch) => vm.updateAccount(svc, id, patch || {}));
|