helixevo 0.2.26 → 0.2.27
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.
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useState, useEffect } from 'react'
|
|
4
4
|
|
|
5
5
|
const REGISTRY_URL = 'https://registry.npmjs.org/helixevo/latest'
|
|
6
|
-
const CHECK_INTERVAL_MS = 60 * 60 * 1000
|
|
6
|
+
const CHECK_INTERVAL_MS = 60 * 60 * 1000
|
|
7
7
|
|
|
8
8
|
function compareVersions(current: string, latest: string): boolean {
|
|
9
9
|
const c = current.split('.').map(Number)
|
|
@@ -15,7 +15,7 @@ function compareVersions(current: string, latest: string): boolean {
|
|
|
15
15
|
return false
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
type UpdateState = 'idle' | 'updating' | '
|
|
18
|
+
type UpdateState = 'idle' | 'updating' | 'installed' | 'error'
|
|
19
19
|
|
|
20
20
|
export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
21
21
|
const [latestVersion, setLatestVersion] = useState<string | null>(null)
|
|
@@ -23,10 +23,10 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
23
23
|
const [state, setState] = useState<UpdateState>('idle')
|
|
24
24
|
const [newVersion, setNewVersion] = useState<string | null>(null)
|
|
25
25
|
const [errorMsg, setErrorMsg] = useState<string | null>(null)
|
|
26
|
+
const [copied, setCopied] = useState(false)
|
|
26
27
|
|
|
27
28
|
useEffect(() => {
|
|
28
29
|
let mounted = true
|
|
29
|
-
|
|
30
30
|
async function check() {
|
|
31
31
|
try {
|
|
32
32
|
const res = await fetch(REGISTRY_URL)
|
|
@@ -37,7 +37,6 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
37
37
|
}
|
|
38
38
|
} catch {}
|
|
39
39
|
}
|
|
40
|
-
|
|
41
40
|
check()
|
|
42
41
|
const interval = setInterval(check, CHECK_INTERVAL_MS)
|
|
43
42
|
return () => { mounted = false; clearInterval(interval) }
|
|
@@ -54,49 +53,35 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
54
53
|
const data = await res.json()
|
|
55
54
|
|
|
56
55
|
if (data.success) {
|
|
57
|
-
setState('
|
|
56
|
+
setState('installed')
|
|
58
57
|
setNewVersion(data.version)
|
|
59
|
-
|
|
60
|
-
// Tell server to restart, then wait and reload
|
|
61
|
-
try {
|
|
62
|
-
await fetch('/api/restart', { method: 'POST' })
|
|
63
|
-
} catch {}
|
|
64
|
-
|
|
65
|
-
// Wait for: shutdown (2s) + port release (2s) + restart + compile (~8s)
|
|
66
|
-
// Then try to reload, retrying if server isn't ready
|
|
67
|
-
await new Promise(r => setTimeout(r, 6000))
|
|
68
|
-
|
|
69
|
-
for (let attempt = 0; attempt < 20; attempt++) {
|
|
70
|
-
try {
|
|
71
|
-
const check = await fetch('/?_bust=' + Date.now(), { cache: 'no-store' })
|
|
72
|
-
if (check.ok) {
|
|
73
|
-
window.location.href = window.location.pathname + '?updated=' + Date.now()
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
} catch {}
|
|
77
|
-
await new Promise(r => setTimeout(r, 2000))
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Final fallback
|
|
81
|
-
window.location.href = window.location.pathname + '?updated=' + Date.now()
|
|
82
58
|
} else {
|
|
83
59
|
setState('error')
|
|
84
60
|
setErrorMsg(data.error ?? 'Update failed')
|
|
85
61
|
}
|
|
86
|
-
} catch
|
|
62
|
+
} catch {
|
|
87
63
|
setState('error')
|
|
88
64
|
setErrorMsg('Network error — try running: npm install -g helixevo@latest')
|
|
89
65
|
}
|
|
90
66
|
}
|
|
91
67
|
|
|
68
|
+
const restartCmd = 'helixevo dashboard'
|
|
69
|
+
const handleCopy = async () => {
|
|
70
|
+
try {
|
|
71
|
+
await navigator.clipboard.writeText(restartCmd)
|
|
72
|
+
setCopied(true)
|
|
73
|
+
setTimeout(() => setCopied(false), 2000)
|
|
74
|
+
} catch {}
|
|
75
|
+
}
|
|
76
|
+
|
|
92
77
|
return (
|
|
93
78
|
<div style={{
|
|
94
79
|
position: 'fixed',
|
|
95
80
|
bottom: 20,
|
|
96
81
|
right: 20,
|
|
97
|
-
width:
|
|
82
|
+
width: 340,
|
|
98
83
|
background: 'var(--bg-card)',
|
|
99
|
-
border: `1px solid ${state === '
|
|
84
|
+
border: `1px solid ${state === 'installed' ? 'var(--green-border)' : state === 'error' ? 'var(--red-border)' : 'var(--purple-border)'}`,
|
|
100
85
|
borderRadius: 'var(--radius-lg)',
|
|
101
86
|
boxShadow: 'var(--shadow-xl)',
|
|
102
87
|
padding: '16px 18px',
|
|
@@ -123,7 +108,7 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
123
108
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
|
124
109
|
<div style={{
|
|
125
110
|
width: 28, height: 28, borderRadius: '50%',
|
|
126
|
-
background: state === '
|
|
111
|
+
background: state === 'installed' ? 'var(--green-light)'
|
|
127
112
|
: state === 'error' ? 'var(--red-light)'
|
|
128
113
|
: 'var(--purple-light)',
|
|
129
114
|
display: 'flex',
|
|
@@ -135,7 +120,7 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
135
120
|
borderTopColor: 'var(--purple)', borderRadius: '50%',
|
|
136
121
|
animation: 'updateSpin 0.8s linear infinite',
|
|
137
122
|
}} />
|
|
138
|
-
) : state === '
|
|
123
|
+
) : state === 'installed' ? (
|
|
139
124
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--green)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
140
125
|
<path d="M20 6L9 17l-5-5" />
|
|
141
126
|
</svg>
|
|
@@ -151,21 +136,21 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
151
136
|
</div>
|
|
152
137
|
<div>
|
|
153
138
|
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', letterSpacing: -0.2 }}>
|
|
154
|
-
{state === 'updating' ? '
|
|
155
|
-
: state === '
|
|
139
|
+
{state === 'updating' ? 'Installing...'
|
|
140
|
+
: state === 'installed' ? 'Installed!'
|
|
156
141
|
: state === 'error' ? 'Update Failed'
|
|
157
142
|
: 'Update Available'}
|
|
158
143
|
</div>
|
|
159
144
|
<div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
|
|
160
|
-
{state === '
|
|
161
|
-
? <>
|
|
145
|
+
{state === 'installed'
|
|
146
|
+
? <>v{newVersion} is ready</>
|
|
162
147
|
: <>v{currentVersion} → <span style={{ color: 'var(--green)', fontWeight: 600 }}>v{latestVersion}</span></>
|
|
163
148
|
}
|
|
164
149
|
</div>
|
|
165
150
|
</div>
|
|
166
151
|
</div>
|
|
167
152
|
|
|
168
|
-
{/*
|
|
153
|
+
{/* Idle — Update Now button */}
|
|
169
154
|
{state === 'idle' && (
|
|
170
155
|
<button
|
|
171
156
|
onClick={handleUpdate}
|
|
@@ -183,18 +168,16 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
183
168
|
alignItems: 'center',
|
|
184
169
|
justifyContent: 'center',
|
|
185
170
|
gap: 6,
|
|
186
|
-
transition: 'opacity 0.15s',
|
|
187
171
|
}}
|
|
188
|
-
onMouseOver={e => (e.currentTarget.style.opacity = '0.9')}
|
|
189
|
-
onMouseOut={e => (e.currentTarget.style.opacity = '1')}
|
|
190
172
|
>
|
|
191
173
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
192
|
-
<path d="
|
|
174
|
+
<path d="M12 19V5m-7 7l7-7 7 7" />
|
|
193
175
|
</svg>
|
|
194
176
|
Update Now
|
|
195
177
|
</button>
|
|
196
178
|
)}
|
|
197
179
|
|
|
180
|
+
{/* Updating spinner */}
|
|
198
181
|
{state === 'updating' && (
|
|
199
182
|
<div style={{
|
|
200
183
|
width: '100%',
|
|
@@ -209,21 +192,52 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
209
192
|
</div>
|
|
210
193
|
)}
|
|
211
194
|
|
|
212
|
-
{
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
195
|
+
{/* Installed — restart instructions */}
|
|
196
|
+
{state === 'installed' && (
|
|
197
|
+
<div>
|
|
198
|
+
<div style={{
|
|
199
|
+
padding: '10px 14px',
|
|
200
|
+
background: 'var(--green-light)',
|
|
201
|
+
borderRadius: 'var(--radius)',
|
|
202
|
+
fontSize: 12,
|
|
203
|
+
color: 'var(--green)',
|
|
204
|
+
fontWeight: 600,
|
|
205
|
+
marginBottom: 10,
|
|
206
|
+
textAlign: 'center',
|
|
207
|
+
}}>
|
|
208
|
+
v{newVersion} installed successfully
|
|
209
|
+
</div>
|
|
210
|
+
<div style={{ fontSize: 11, color: 'var(--text-dim)', marginBottom: 8, lineHeight: 1.5 }}>
|
|
211
|
+
Restart the dashboard to load the new version:
|
|
212
|
+
</div>
|
|
213
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
214
|
+
<div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
|
|
215
|
+
1. Press <kbd style={{ padding: '1px 5px', background: 'var(--bg-section)', borderRadius: 3, fontFamily: 'var(--font-mono)', fontSize: 10, border: '1px solid var(--border)' }}>Ctrl+C</kbd> in the terminal running the dashboard
|
|
216
|
+
</div>
|
|
217
|
+
<div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
|
|
218
|
+
2. Run:
|
|
219
|
+
</div>
|
|
220
|
+
<div
|
|
221
|
+
onClick={handleCopy}
|
|
222
|
+
style={{
|
|
223
|
+
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
224
|
+
padding: '7px 12px',
|
|
225
|
+
background: 'var(--bg-section)', border: '1px solid var(--border)',
|
|
226
|
+
borderRadius: 'var(--radius)',
|
|
227
|
+
fontFamily: 'var(--font-mono)', fontSize: 11,
|
|
228
|
+
color: 'var(--text)', cursor: 'pointer',
|
|
229
|
+
}}
|
|
230
|
+
>
|
|
231
|
+
<span>$ {restartCmd}</span>
|
|
232
|
+
<span style={{ fontSize: 10, color: copied ? 'var(--green)' : 'var(--purple)', fontWeight: 600, fontFamily: 'var(--font)' }}>
|
|
233
|
+
{copied ? 'Copied!' : 'Copy'}
|
|
234
|
+
</span>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
224
237
|
</div>
|
|
225
238
|
)}
|
|
226
239
|
|
|
240
|
+
{/* Error */}
|
|
227
241
|
{state === 'error' && (
|
|
228
242
|
<>
|
|
229
243
|
<div style={{
|
package/package.json
CHANGED