@aion0/forge 0.2.18 → 0.2.20
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/app/api/upgrade/route.ts +19 -18
- package/components/Dashboard.tsx +19 -5
- package/components/SessionView.tsx +1 -70
- package/package.json +1 -1
package/app/api/upgrade/route.ts
CHANGED
|
@@ -1,39 +1,40 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { lstatSync } from 'node:fs';
|
|
4
3
|
import { execSync } from 'node:child_process';
|
|
5
4
|
|
|
6
5
|
export async function POST() {
|
|
7
6
|
try {
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
// Run upgrade with cache bypass
|
|
8
|
+
const output = execSync(
|
|
9
|
+
'cd /tmp && npm install -g @aion0/forge@latest --prefer-online 2>&1',
|
|
10
|
+
{ encoding: 'utf-8', timeout: 120000 }
|
|
11
|
+
);
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
return NextResponse.json({
|
|
14
|
-
ok: false,
|
|
15
|
-
error: 'Local dev install (npm link). Run: git pull && pnpm install && pnpm build',
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Upgrade from npm
|
|
20
|
-
execSync('cd /tmp && npm install -g @aion0/forge', { timeout: 120000 });
|
|
21
|
-
|
|
22
|
-
// Install devDependencies for build (npm -g doesn't install them)
|
|
13
|
+
// Verify the installed version
|
|
23
14
|
const pkgRoot = execSync('npm root -g', { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
24
15
|
const forgeRoot = join(pkgRoot, '@aion0', 'forge');
|
|
16
|
+
|
|
17
|
+
// Install devDependencies for build (npm -g doesn't install them)
|
|
18
|
+
try {
|
|
19
|
+
execSync('npm install --include=dev 2>&1', { cwd: forgeRoot, timeout: 120000 });
|
|
20
|
+
} catch {}
|
|
21
|
+
|
|
22
|
+
// Read installed version
|
|
23
|
+
let installedVersion = '';
|
|
25
24
|
try {
|
|
26
|
-
|
|
25
|
+
const pkg = JSON.parse(require('fs').readFileSync(join(forgeRoot, 'package.json'), 'utf-8'));
|
|
26
|
+
installedVersion = pkg.version;
|
|
27
27
|
} catch {}
|
|
28
28
|
|
|
29
29
|
return NextResponse.json({
|
|
30
30
|
ok: true,
|
|
31
|
-
message:
|
|
31
|
+
message: `Upgraded to v${installedVersion}. Restart server to apply.`,
|
|
32
32
|
});
|
|
33
33
|
} catch (e) {
|
|
34
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
34
35
|
return NextResponse.json({
|
|
35
36
|
ok: false,
|
|
36
|
-
error: `Upgrade failed: ${
|
|
37
|
+
error: `Upgrade failed: ${msg.slice(0, 200)}`,
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
40
|
}
|
package/components/Dashboard.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import SessionView from './SessionView';
|
|
|
8
8
|
import NewTaskModal from './NewTaskModal';
|
|
9
9
|
import SettingsModal from './SettingsModal';
|
|
10
10
|
import TunnelToggle from './TunnelToggle';
|
|
11
|
+
import MonitorPanel from './MonitorPanel';
|
|
11
12
|
import type { Task } from '@/src/types';
|
|
12
13
|
import type { WebTerminalHandle } from './WebTerminal';
|
|
13
14
|
|
|
@@ -44,6 +45,7 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
44
45
|
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
|
45
46
|
const [showNewTask, setShowNewTask] = useState(false);
|
|
46
47
|
const [showSettings, setShowSettings] = useState(false);
|
|
48
|
+
const [showMonitor, setShowMonitor] = useState(false);
|
|
47
49
|
const [usage, setUsage] = useState<UsageSummary[]>([]);
|
|
48
50
|
const [providers, setProviders] = useState<ProviderInfo[]>([]);
|
|
49
51
|
const [projects, setProjects] = useState<ProjectInfo[]>([]);
|
|
@@ -122,7 +124,7 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
122
124
|
setUpgrading(false);
|
|
123
125
|
}}
|
|
124
126
|
className="text-[9px] px-1.5 py-0.5 bg-[var(--accent)] text-white rounded hover:opacity-90 disabled:opacity-50"
|
|
125
|
-
title={`Update to v${versionInfo.latest}`}
|
|
127
|
+
title={`Update to v${versionInfo.latest}\nOr run: forge upgrade`}
|
|
126
128
|
>
|
|
127
129
|
{upgrading ? 'Upgrading...' : `Update v${versionInfo.latest}`}
|
|
128
130
|
</button>
|
|
@@ -134,7 +136,7 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
134
136
|
const data = await res.json();
|
|
135
137
|
setVersionInfo(data);
|
|
136
138
|
}}
|
|
137
|
-
className="text-[9px] px-1
|
|
139
|
+
className="text-[9px] px-1 py-0.5 text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
|
138
140
|
title="Check for updates"
|
|
139
141
|
>
|
|
140
142
|
↻
|
|
@@ -200,6 +202,16 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
200
202
|
>
|
|
201
203
|
Pipelines
|
|
202
204
|
</button>
|
|
205
|
+
<button
|
|
206
|
+
onClick={() => setViewMode('sessions')}
|
|
207
|
+
className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
|
|
208
|
+
viewMode === 'sessions'
|
|
209
|
+
? 'bg-[var(--bg-secondary)] text-[var(--text-primary)] shadow-sm'
|
|
210
|
+
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)]'
|
|
211
|
+
}`}
|
|
212
|
+
>
|
|
213
|
+
Sessions
|
|
214
|
+
</button>
|
|
203
215
|
<button
|
|
204
216
|
onClick={() => setViewMode('preview')}
|
|
205
217
|
className={`text-[11px] px-2.5 py-0.5 rounded transition-colors ${
|
|
@@ -238,10 +250,10 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
238
250
|
</span>
|
|
239
251
|
)}
|
|
240
252
|
<button
|
|
241
|
-
onClick={() =>
|
|
242
|
-
className=
|
|
253
|
+
onClick={() => setShowMonitor(true)}
|
|
254
|
+
className="text-xs text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
|
243
255
|
>
|
|
244
|
-
|
|
256
|
+
Monitor
|
|
245
257
|
</button>
|
|
246
258
|
<button
|
|
247
259
|
onClick={() => setShowSettings(true)}
|
|
@@ -414,6 +426,8 @@ export default function Dashboard({ user }: { user: any }) {
|
|
|
414
426
|
/>
|
|
415
427
|
)}
|
|
416
428
|
|
|
429
|
+
{showMonitor && <MonitorPanel onClose={() => setShowMonitor(false)} />}
|
|
430
|
+
|
|
417
431
|
{showSettings && (
|
|
418
432
|
<SettingsModal onClose={() => { setShowSettings(false); fetchData(); }} />
|
|
419
433
|
)}
|
|
@@ -32,16 +32,6 @@ interface Watcher {
|
|
|
32
32
|
createdAt: string;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
interface MonitorData {
|
|
36
|
-
processes: {
|
|
37
|
-
nextjs: { running: boolean; pid: string };
|
|
38
|
-
terminal: { running: boolean; pid: string };
|
|
39
|
-
telegram: { running: boolean; pid: string };
|
|
40
|
-
tunnel: { running: boolean; pid: string; url: string };
|
|
41
|
-
};
|
|
42
|
-
sessions: { name: string; created: string; attached: boolean; windows: number }[];
|
|
43
|
-
uptime: string;
|
|
44
|
-
}
|
|
45
35
|
|
|
46
36
|
export default function SessionView({
|
|
47
37
|
projectName,
|
|
@@ -63,8 +53,6 @@ export default function SessionView({
|
|
|
63
53
|
const [watchers, setWatchers] = useState<Watcher[]>([]);
|
|
64
54
|
const [batchMode, setBatchMode] = useState(false);
|
|
65
55
|
const [selectedIds, setSelectedIds] = useState<Map<string, Set<string>>>(new Map());
|
|
66
|
-
const [monitor, setMonitor] = useState<MonitorData | null>(null);
|
|
67
|
-
const [monitorOpen, setMonitorOpen] = useState(true);
|
|
68
56
|
const bottomRef = useRef<HTMLDivElement>(null);
|
|
69
57
|
|
|
70
58
|
// Load cached sessions tree
|
|
@@ -92,17 +80,10 @@ export default function SessionView({
|
|
|
92
80
|
} catch {}
|
|
93
81
|
}, []);
|
|
94
82
|
|
|
95
|
-
const refreshMonitor = useCallback(() => {
|
|
96
|
-
fetch('/api/monitor').then(r => r.json()).then(setMonitor).catch(() => {});
|
|
97
|
-
}, []);
|
|
98
|
-
|
|
99
83
|
useEffect(() => {
|
|
100
84
|
loadTree(true);
|
|
101
85
|
loadWatchers();
|
|
102
|
-
|
|
103
|
-
const timer = setInterval(refreshMonitor, 5000);
|
|
104
|
-
return () => clearInterval(timer);
|
|
105
|
-
}, [loadTree, loadWatchers, refreshMonitor]);
|
|
86
|
+
}, [loadTree, loadWatchers]);
|
|
106
87
|
|
|
107
88
|
// Auto-expand project if only one or if pre-selected
|
|
108
89
|
useEffect(() => {
|
|
@@ -348,56 +329,6 @@ export default function SessionView({
|
|
|
348
329
|
</div>
|
|
349
330
|
)}
|
|
350
331
|
|
|
351
|
-
{/* Monitor */}
|
|
352
|
-
{monitor && (
|
|
353
|
-
<div className="border-b border-[var(--border)]">
|
|
354
|
-
<button
|
|
355
|
-
onClick={() => setMonitorOpen(v => !v)}
|
|
356
|
-
className="w-full flex items-center gap-1.5 px-2 py-1.5 hover:bg-[var(--bg-tertiary)] transition-colors"
|
|
357
|
-
>
|
|
358
|
-
<span className="text-[10px] text-[var(--text-secondary)]">{monitorOpen ? '▼' : '▶'}</span>
|
|
359
|
-
<span className="text-[9px] font-semibold text-[var(--text-secondary)] uppercase">Monitor</span>
|
|
360
|
-
{monitor.uptime && (
|
|
361
|
-
<span className="text-[8px] text-[var(--text-secondary)] ml-auto">{monitor.uptime}</span>
|
|
362
|
-
)}
|
|
363
|
-
</button>
|
|
364
|
-
{monitorOpen && (
|
|
365
|
-
<div className="px-2 pb-2 space-y-1.5">
|
|
366
|
-
{/* Processes */}
|
|
367
|
-
{[
|
|
368
|
-
{ label: 'Next.js', ...monitor.processes.nextjs },
|
|
369
|
-
{ label: 'Terminal', ...monitor.processes.terminal },
|
|
370
|
-
{ label: 'Telegram', ...monitor.processes.telegram },
|
|
371
|
-
{ label: 'Tunnel', ...monitor.processes.tunnel },
|
|
372
|
-
].map(p => (
|
|
373
|
-
<div key={p.label} className="flex items-center gap-1.5 text-[10px]">
|
|
374
|
-
<span className={p.running ? 'text-green-400' : 'text-gray-500'}>●</span>
|
|
375
|
-
<span className="text-[var(--text-primary)]">{p.label}</span>
|
|
376
|
-
<span className="text-[var(--text-secondary)] font-mono ml-auto">{p.running ? `pid:${p.pid}` : 'stopped'}</span>
|
|
377
|
-
</div>
|
|
378
|
-
))}
|
|
379
|
-
{monitor.processes.tunnel.running && monitor.processes.tunnel.url && (
|
|
380
|
-
<div className="text-[9px] text-[var(--accent)] truncate pl-4">{monitor.processes.tunnel.url}</div>
|
|
381
|
-
)}
|
|
382
|
-
|
|
383
|
-
{/* Tmux sessions */}
|
|
384
|
-
{monitor.sessions.length > 0 && (
|
|
385
|
-
<div className="pt-1">
|
|
386
|
-
<span className="text-[8px] font-semibold text-[var(--text-secondary)] uppercase">Tmux ({monitor.sessions.length})</span>
|
|
387
|
-
{monitor.sessions.map(s => (
|
|
388
|
-
<div key={s.name} className="flex items-center gap-1.5 text-[10px] mt-0.5">
|
|
389
|
-
<span className={s.attached ? 'text-green-400' : 'text-yellow-500'}>●</span>
|
|
390
|
-
<span className="font-mono text-[var(--text-primary)] truncate flex-1">{s.name}</span>
|
|
391
|
-
<span className="text-[8px] text-[var(--text-secondary)]">{s.attached ? 'attached' : 'detached'}</span>
|
|
392
|
-
</div>
|
|
393
|
-
))}
|
|
394
|
-
</div>
|
|
395
|
-
)}
|
|
396
|
-
</div>
|
|
397
|
-
)}
|
|
398
|
-
</div>
|
|
399
|
-
)}
|
|
400
|
-
|
|
401
332
|
{/* Tree */}
|
|
402
333
|
<div className="flex-1 overflow-y-auto">
|
|
403
334
|
{Object.keys(sessionTree).length === 0 && (
|
package/package.json
CHANGED