pinokiod 8.0.58 → 8.0.61
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/kernel/vault/automatic_scans.js +45 -5
- package/kernel/vault/index.js +33 -1
- package/package.json +1 -1
- package/server/index.js +3 -0
- package/server/public/app-vault-mode.js +14 -5
- package/server/public/layout.js +7 -0
- package/server/public/vault.css +25 -0
- package/server/public/vault.js +112 -24
- package/server/views/app.ejs +10 -3
- package/test/vault-automatic-scan-ui.test.js +46 -3
- package/test/vault-automatic-scans.test.js +28 -2
- package/test/vault-engine.test.js +73 -0
- package/test/vault-ui.test.js +107 -1
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
} = require("./operation_errors")
|
|
11
11
|
|
|
12
12
|
const COMPLETE_PHASES = new Set(["complete", "completed_with_exclusions"])
|
|
13
|
-
const STOP_SETTLE_MS =
|
|
13
|
+
const STOP_SETTLE_MS = 1000
|
|
14
14
|
const PROGRESS_INTERVAL_MS = 5000
|
|
15
15
|
const isMissing = (error) => !!(error &&
|
|
16
16
|
(error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
@@ -41,6 +41,7 @@ class AutomaticScans {
|
|
|
41
41
|
this.waitingFor = null
|
|
42
42
|
this.listeners = new Set()
|
|
43
43
|
this.appTransitions = new Map()
|
|
44
|
+
this.globalScanReady = false
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
log(event, details = {}) {
|
|
@@ -134,7 +135,8 @@ class AutomaticScans {
|
|
|
134
135
|
snapshot() {
|
|
135
136
|
return {
|
|
136
137
|
enabled: !!this.vault.enabled,
|
|
137
|
-
|
|
138
|
+
global_scan_ready: this.globalScanReady,
|
|
139
|
+
rows: (this.globalScanReady ? [...this.entries.values()] : [])
|
|
138
140
|
.filter((entry) => !entry.hidden)
|
|
139
141
|
.sort((left, right) =>
|
|
140
142
|
(Number(left.updated_at) || 0) -
|
|
@@ -147,6 +149,14 @@ class AutomaticScans {
|
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
151
|
|
|
152
|
+
async refreshGlobalScanReady() {
|
|
153
|
+
const ready = typeof this.vault.globalScanReady === "function" &&
|
|
154
|
+
!!await this.vault.globalScanReady()
|
|
155
|
+
const changed = ready !== this.globalScanReady
|
|
156
|
+
this.globalScanReady = ready
|
|
157
|
+
return changed
|
|
158
|
+
}
|
|
159
|
+
|
|
150
160
|
modeFor(app) {
|
|
151
161
|
const setting = this.settings.get(app)
|
|
152
162
|
return setting && setting.mode === "manual" ? "manual" : "automatic"
|
|
@@ -236,6 +246,10 @@ class AutomaticScans {
|
|
|
236
246
|
}
|
|
237
247
|
|
|
238
248
|
queueApp(app) {
|
|
249
|
+
if (!this.globalScanReady) {
|
|
250
|
+
this.log("queue-skipped", { app, reason: "global-scan-required" })
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
239
253
|
if (this.modeFor(app) === "manual") {
|
|
240
254
|
this.log("queue-skipped", { app, reason: "manual" })
|
|
241
255
|
return false
|
|
@@ -324,6 +338,12 @@ class AutomaticScans {
|
|
|
324
338
|
this.log("preparing", { app })
|
|
325
339
|
await this.vault.ensureRegistryInitialized()
|
|
326
340
|
if (this.pendingStops.get(app) !== pending) return
|
|
341
|
+
await this.refreshGlobalScanReady()
|
|
342
|
+
if (!this.globalScanReady) {
|
|
343
|
+
this.pendingStops.delete(app)
|
|
344
|
+
this.log("check-skipped", { app, reason: "global-scan-required" })
|
|
345
|
+
return
|
|
346
|
+
}
|
|
327
347
|
await this.hydrate()
|
|
328
348
|
await this.withAppTransition(app, async () => {
|
|
329
349
|
if (this.pendingStops.get(app) !== pending) return
|
|
@@ -395,6 +415,11 @@ class AutomaticScans {
|
|
|
395
415
|
if (!this.hydrated && typeof this.vault.automaticScanStatus === "function") {
|
|
396
416
|
await this.vault.automaticScanStatus()
|
|
397
417
|
}
|
|
418
|
+
await this.refreshGlobalScanReady()
|
|
419
|
+
if (!this.globalScanReady) {
|
|
420
|
+
this.log("check-skipped", { app, reason: "global-scan-required" })
|
|
421
|
+
return
|
|
422
|
+
}
|
|
398
423
|
if (this.modeFor(app) === "manual") {
|
|
399
424
|
this.log("check-skipped", { app, reason: "manual" })
|
|
400
425
|
return
|
|
@@ -547,14 +572,16 @@ class AutomaticScans {
|
|
|
547
572
|
|
|
548
573
|
async drain() {
|
|
549
574
|
if (!this.vault.enabled || !this.vault.registry || this.active) return
|
|
575
|
+
const entry = [...this.entries.values()].find((item) =>
|
|
576
|
+
item.state === "checking")
|
|
577
|
+
if (!entry) return
|
|
578
|
+
await this.refreshGlobalScanReady()
|
|
579
|
+
if (!this.globalScanReady) return
|
|
550
580
|
if (this.manualDepth > 0 || this.currentBusyPromise() ||
|
|
551
581
|
this.vault.fileActionProgress) {
|
|
552
582
|
this.waitForBusyWork()
|
|
553
583
|
return
|
|
554
584
|
}
|
|
555
|
-
const entry = [...this.entries.values()].find((item) =>
|
|
556
|
-
item.state === "checking")
|
|
557
|
-
if (!entry) return
|
|
558
585
|
let appAvailable
|
|
559
586
|
try {
|
|
560
587
|
appAvailable = await this.appRootIsAvailable(entry.app)
|
|
@@ -842,6 +869,7 @@ class AutomaticScans {
|
|
|
842
869
|
|
|
843
870
|
async scanFinished({ scopeId, result, error }) {
|
|
844
871
|
const complete = !error && result && COMPLETE_PHASES.has(result.outcome)
|
|
872
|
+
let readinessChanged = false
|
|
845
873
|
try {
|
|
846
874
|
if (complete) {
|
|
847
875
|
await this.hydrate()
|
|
@@ -852,7 +880,9 @@ class AutomaticScans {
|
|
|
852
880
|
{ cancelPending: true, states: ["checking", "result"] }
|
|
853
881
|
)
|
|
854
882
|
}
|
|
883
|
+
if (!scopeId) readinessChanged = await this.refreshGlobalScanReady()
|
|
855
884
|
} finally {
|
|
885
|
+
if (readinessChanged) this.broadcast()
|
|
856
886
|
this.schedule()
|
|
857
887
|
}
|
|
858
888
|
}
|
|
@@ -897,6 +927,16 @@ class AutomaticScans {
|
|
|
897
927
|
await this.persistMode(app, mode)
|
|
898
928
|
const entry = this.entries.get(app)
|
|
899
929
|
if (entry && entry.state === "paused") this.entries.delete(app)
|
|
930
|
+
await this.refreshGlobalScanReady()
|
|
931
|
+
if (!this.globalScanReady) {
|
|
932
|
+
this.log("mode-changed", {
|
|
933
|
+
app,
|
|
934
|
+
mode,
|
|
935
|
+
check: "global-scan-required"
|
|
936
|
+
})
|
|
937
|
+
this.broadcast()
|
|
938
|
+
return { app, mode }
|
|
939
|
+
}
|
|
900
940
|
if (!this.appIsRunning(app)) {
|
|
901
941
|
this.log("mode-changed", { app, mode })
|
|
902
942
|
this.queueApp(app)
|
package/kernel/vault/index.js
CHANGED
|
@@ -73,6 +73,9 @@ const STATUS_FILTERS = new Set([
|
|
|
73
73
|
const FOLDER_DISCOVERY_COMPLETE_PHASES = new Set([
|
|
74
74
|
"complete", "completed_with_exclusions"
|
|
75
75
|
])
|
|
76
|
+
const GLOBAL_SCAN_READY_OUTCOMES = new Set([
|
|
77
|
+
"complete", "completed_with_exclusions"
|
|
78
|
+
])
|
|
76
79
|
|
|
77
80
|
const isMissingError = (error) => !!(error &&
|
|
78
81
|
(error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
@@ -672,9 +675,23 @@ class Vault {
|
|
|
672
675
|
await this.ensureRegistryInitialized()
|
|
673
676
|
}
|
|
674
677
|
await this.automaticScans.hydrate()
|
|
678
|
+
await this.automaticScans.refreshGlobalScanReady()
|
|
675
679
|
return this.automaticScans.snapshot()
|
|
676
680
|
}
|
|
677
681
|
|
|
682
|
+
async globalScanReady() {
|
|
683
|
+
if (!this.registry) return false
|
|
684
|
+
const scan = await this.registry.scanFor()
|
|
685
|
+
if (scan) this.lastScanCache.set("", scan)
|
|
686
|
+
else this.lastScanCache.delete("")
|
|
687
|
+
return this.globalScanIsReady(scan)
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
globalScanIsReady(scan = this.lastScanCache.get("")) {
|
|
691
|
+
return !!(scan && scan.ts &&
|
|
692
|
+
GLOBAL_SCAN_READY_OUTCOMES.has(scan.outcome))
|
|
693
|
+
}
|
|
694
|
+
|
|
678
695
|
async refreshAnchorStores() {
|
|
679
696
|
let config = this.readConfig()
|
|
680
697
|
const candidates = [
|
|
@@ -1790,9 +1807,19 @@ class Vault {
|
|
|
1790
1807
|
return this.runMutation(() =>
|
|
1791
1808
|
this.removeExternalSource(payload.source_id))
|
|
1792
1809
|
case "scan": {
|
|
1793
|
-
|
|
1810
|
+
const scanSource = payload.scope_id
|
|
1811
|
+
? this.scanSource(payload.scope_id)
|
|
1812
|
+
: null
|
|
1813
|
+
if (payload.scope_id && !scanSource) {
|
|
1794
1814
|
return { error: "That scan location is no longer available." }
|
|
1795
1815
|
}
|
|
1816
|
+
if (scanSource && scanSource.kind === "app" &&
|
|
1817
|
+
!await this.globalScanReady()) {
|
|
1818
|
+
return {
|
|
1819
|
+
error: "Run an initial scan before scanning individual apps.",
|
|
1820
|
+
code: "global_scan_required"
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1796
1823
|
let threshold = this.sizeThreshold
|
|
1797
1824
|
if (payload.candidate_size != null) {
|
|
1798
1825
|
if (!CANDIDATE_SIZE_OPTIONS.includes(payload.candidate_size)) {
|
|
@@ -3266,6 +3293,9 @@ class Vault {
|
|
|
3266
3293
|
}
|
|
3267
3294
|
|
|
3268
3295
|
const lastScan = await this.scanForScope(scopeId)
|
|
3296
|
+
const globalScanReady = scopeId
|
|
3297
|
+
? await this.globalScanReady()
|
|
3298
|
+
: this.globalScanIsReady(lastScan)
|
|
3269
3299
|
const before = lastScan && Number.isFinite(lastScan.bytes_total)
|
|
3270
3300
|
? lastScan.bytes_total
|
|
3271
3301
|
: 0
|
|
@@ -3302,6 +3332,7 @@ class Vault {
|
|
|
3302
3332
|
const sourceCounts = this.sourceCountMaps(snapshot.scopeRows)
|
|
3303
3333
|
const result = {
|
|
3304
3334
|
enabled: true,
|
|
3335
|
+
global_scan_ready: globalScanReady,
|
|
3305
3336
|
mode: this.mode,
|
|
3306
3337
|
scan: this.scanStatus(),
|
|
3307
3338
|
last_scan: lastScan,
|
|
@@ -3402,6 +3433,7 @@ class Vault {
|
|
|
3402
3433
|
async progressStatus(scopeId = null) {
|
|
3403
3434
|
const result = {
|
|
3404
3435
|
enabled: !!this.enabled,
|
|
3436
|
+
global_scan_ready: this.globalScanIsReady(),
|
|
3405
3437
|
scan: this.scanStatus(),
|
|
3406
3438
|
file_action: this.fileActionStatus(scopeId),
|
|
3407
3439
|
last_scan: this.lastScanCache.get(scopeId || "") || null
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -1994,9 +1994,12 @@ class Server {
|
|
|
1994
1994
|
if (vault && vault.ready) await vault.ready
|
|
1995
1995
|
result.vault_enabled = !!(vault && vault.enabled)
|
|
1996
1996
|
result.vault_automatic_mode = "automatic"
|
|
1997
|
+
result.vault_global_scan_ready = false
|
|
1997
1998
|
if (result.vault_enabled) {
|
|
1998
1999
|
try {
|
|
1999
2000
|
const snapshot = await vault.automaticScanStatus()
|
|
2001
|
+
result.vault_global_scan_ready =
|
|
2002
|
+
snapshot && snapshot.global_scan_ready === true
|
|
2000
2003
|
const setting = Array.isArray(snapshot && snapshot.settings)
|
|
2001
2004
|
? snapshot.settings.find((item) =>
|
|
2002
2005
|
item && item.app === name)
|
|
@@ -5,14 +5,22 @@
|
|
|
5
5
|
const app = status.dataset.app || ""
|
|
6
6
|
const tab = status.closest("#save-space-tab")
|
|
7
7
|
const label = status.querySelector("[data-app-vault-mode-label]")
|
|
8
|
-
const
|
|
8
|
+
const setState = (value, ready) => {
|
|
9
9
|
const mode = value === "manual" ? "manual" : "automatic"
|
|
10
|
+
const globalScanReady = ready === true
|
|
10
11
|
status.dataset.mode = mode
|
|
12
|
+
status.dataset.ready = String(globalScanReady)
|
|
11
13
|
status.hidden = false
|
|
12
|
-
if (label)
|
|
14
|
+
if (label) {
|
|
15
|
+
label.textContent = globalScanReady
|
|
16
|
+
? (mode === "automatic" ? "Auto" : "Manual")
|
|
17
|
+
: "Set up"
|
|
18
|
+
}
|
|
13
19
|
if (tab) {
|
|
14
20
|
tab.setAttribute("aria-label",
|
|
15
|
-
|
|
21
|
+
globalScanReady
|
|
22
|
+
? `Disk Saver — ${mode === "automatic" ? "Automatic" : "Manual"} checking`
|
|
23
|
+
: "Disk Saver — Set up required")
|
|
16
24
|
}
|
|
17
25
|
}
|
|
18
26
|
const applySnapshot = (snapshot) => {
|
|
@@ -20,10 +28,11 @@
|
|
|
20
28
|
? snapshot.settings
|
|
21
29
|
: []
|
|
22
30
|
const setting = settings.find((item) => item && item.app === app)
|
|
23
|
-
|
|
31
|
+
setState(setting && setting.mode,
|
|
32
|
+
snapshot && snapshot.global_scan_ready === true)
|
|
24
33
|
}
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
setState(status.dataset.mode, status.dataset.ready === "true")
|
|
27
36
|
if (!app || typeof window.EventSource !== "function") return
|
|
28
37
|
|
|
29
38
|
const source = new window.EventSource(
|
package/server/public/layout.js
CHANGED
|
@@ -1068,6 +1068,13 @@
|
|
|
1068
1068
|
}
|
|
1069
1069
|
|
|
1070
1070
|
function render(snapshot, options = {}) {
|
|
1071
|
+
if (!snapshot || snapshot.global_scan_ready !== true) {
|
|
1072
|
+
[...completions.keys()].forEach(removeCompletion);
|
|
1073
|
+
visibleCheckingApps = new Set();
|
|
1074
|
+
tray.replaceChildren();
|
|
1075
|
+
tray.hidden = true;
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1071
1078
|
const rows = snapshot && Array.isArray(snapshot.rows)
|
|
1072
1079
|
? snapshot.rows
|
|
1073
1080
|
: [];
|
package/server/public/vault.css
CHANGED
|
@@ -218,6 +218,31 @@ body[data-vault-mode="app"] .vault-overview {
|
|
|
218
218
|
letter-spacing: -.025em;
|
|
219
219
|
line-height: 1.15;
|
|
220
220
|
}
|
|
221
|
+
.vault-setup-copy {
|
|
222
|
+
max-width: 560px;
|
|
223
|
+
margin: 8px 0 0;
|
|
224
|
+
color: var(--task-muted);
|
|
225
|
+
font-size: 12px;
|
|
226
|
+
line-height: 1.5;
|
|
227
|
+
}
|
|
228
|
+
.vault-body.setup-required .vault-overview {
|
|
229
|
+
min-height: 260px;
|
|
230
|
+
flex: 1 1 auto;
|
|
231
|
+
justify-content: center;
|
|
232
|
+
gap: 32px;
|
|
233
|
+
padding: 48px;
|
|
234
|
+
border-bottom: 0;
|
|
235
|
+
}
|
|
236
|
+
.vault-body.setup-required .vault-metrics.summary {
|
|
237
|
+
max-width: 620px;
|
|
238
|
+
flex: 0 1 620px;
|
|
239
|
+
grid-template-columns: minmax(0, 1fr);
|
|
240
|
+
}
|
|
241
|
+
.vault-body.setup-required .vault-summary-value {
|
|
242
|
+
margin-top: 8px;
|
|
243
|
+
font-size: 24px;
|
|
244
|
+
}
|
|
245
|
+
.vault-body.setup-required .vault-summary-side { display: none; }
|
|
221
246
|
.vault-storage-chart {
|
|
222
247
|
width: min(100%, 540px);
|
|
223
248
|
min-width: 0;
|
package/server/public/vault.js
CHANGED
|
@@ -105,6 +105,9 @@ const COPY = {
|
|
|
105
105
|
never: "Never",
|
|
106
106
|
scan: "Scan",
|
|
107
107
|
scan_app: "Scan this app",
|
|
108
|
+
setup_disk_saver: "Set up Disk Saver",
|
|
109
|
+
setup_title: "Run an initial scan to enable app scans",
|
|
110
|
+
setup_description: "This creates the file index used to compare apps and enables automatic checks.",
|
|
108
111
|
scan_again: "Scan again",
|
|
109
112
|
cancel_scan: "Cancel scan",
|
|
110
113
|
scanning: "Scanning…",
|
|
@@ -533,6 +536,9 @@ const post = async (payload) => {
|
|
|
533
536
|
if (!result) throw new Error(COPY.action_request_failed.replace("{status}", response.status))
|
|
534
537
|
return result
|
|
535
538
|
}
|
|
539
|
+
const openGlobalWorkspace = () => {
|
|
540
|
+
window.parent.location.assign("/vault")
|
|
541
|
+
}
|
|
536
542
|
let automaticModeEventSource = null
|
|
537
543
|
const applyAutomaticScanSnapshot = (snapshot) => {
|
|
538
544
|
if (!IS_APP_MODE) return
|
|
@@ -543,9 +549,17 @@ const applyAutomaticScanSnapshot = (snapshot) => {
|
|
|
543
549
|
const mode = setting && setting.mode === "manual"
|
|
544
550
|
? "manual"
|
|
545
551
|
: "automatic"
|
|
546
|
-
|
|
552
|
+
const globalScanReady = !!(snapshot &&
|
|
553
|
+
snapshot.global_scan_ready === true)
|
|
554
|
+
const readinessChanged = state.data &&
|
|
555
|
+
state.data.global_scan_ready !== globalScanReady
|
|
556
|
+
if (state.data) state.data.global_scan_ready = globalScanReady
|
|
557
|
+
if (state.automaticMode === mode && !readinessChanged) return
|
|
547
558
|
state.automaticMode = mode
|
|
548
|
-
if (state.data)
|
|
559
|
+
if (state.data) {
|
|
560
|
+
if (readinessChanged) refresh(true)
|
|
561
|
+
else renderOverview()
|
|
562
|
+
}
|
|
549
563
|
}
|
|
550
564
|
const loadAutomaticMode = async () => {
|
|
551
565
|
if (!IS_APP_MODE) return
|
|
@@ -2176,7 +2190,51 @@ const automaticModeMarkup = () => {
|
|
|
2176
2190
|
</details>`
|
|
2177
2191
|
}
|
|
2178
2192
|
|
|
2179
|
-
const
|
|
2193
|
+
const appSetupRequired = () => IS_APP_MODE && state.data &&
|
|
2194
|
+
state.data.global_scan_ready !== true
|
|
2195
|
+
|
|
2196
|
+
const restoreAutomaticModeMenu = (open) => {
|
|
2197
|
+
const modeMenu = el("vault-auto-mode")
|
|
2198
|
+
if (modeMenu && open) modeMenu.open = true
|
|
2199
|
+
if (!modeMenu || !state.automaticModeMenuRequested) return
|
|
2200
|
+
state.automaticModeMenuRequested = false
|
|
2201
|
+
modeMenu.classList.add("targeted")
|
|
2202
|
+
const trigger = modeMenu.querySelector("summary")
|
|
2203
|
+
requestAnimationFrame(() => {
|
|
2204
|
+
if (trigger && trigger.isConnected) trigger.focus()
|
|
2205
|
+
})
|
|
2206
|
+
setTimeout(() => {
|
|
2207
|
+
if (modeMenu.isConnected) modeMenu.classList.remove("targeted")
|
|
2208
|
+
}, 1600)
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
const renderSetupOverview = () => {
|
|
2212
|
+
const metrics = el("vault-metrics")
|
|
2213
|
+
const existingModeMenu = el("vault-auto-mode")
|
|
2214
|
+
const modeMenuOpen = !!(existingModeMenu && existingModeMenu.open) ||
|
|
2215
|
+
state.automaticModeMenuRequested
|
|
2216
|
+
metrics.classList.add("summary")
|
|
2217
|
+
metrics.innerHTML = `
|
|
2218
|
+
<div class="vault-summary-main">
|
|
2219
|
+
<div class="vault-summary-label"><i class="fa-solid fa-hard-drive"></i><span>${esc(COPY.save_space)}</span>${automaticModeMarkup()}</div>
|
|
2220
|
+
<div class="vault-summary-value">${esc(COPY.setup_title)}</div>
|
|
2221
|
+
<p class="vault-setup-copy">${esc(COPY.setup_description)}</p>
|
|
2222
|
+
</div>
|
|
2223
|
+
<div class="vault-summary-side"></div>`
|
|
2224
|
+
restoreAutomaticModeMenu(modeMenuOpen)
|
|
2225
|
+
|
|
2226
|
+
const scanButton = el("btn-scan")
|
|
2227
|
+
scanButton.innerHTML = `<i class="fa-solid fa-arrow-right" aria-hidden="true"></i>${esc(COPY.setup_disk_saver)}`
|
|
2228
|
+
scanButton.classList.add("primary")
|
|
2229
|
+
scanButton.disabled = false
|
|
2230
|
+
const candidateSizeSelect = el("vault-candidate-size")
|
|
2231
|
+
if (candidateSizeSelect) candidateSizeSelect.hidden = true
|
|
2232
|
+
const scanState = el("vault-scan-state")
|
|
2233
|
+
scanState.classList.remove("show")
|
|
2234
|
+
scanState.innerHTML = ""
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
const renderNormalOverview = () => {
|
|
2180
2238
|
const data = state.data
|
|
2181
2239
|
const last = data.last_scan
|
|
2182
2240
|
const activeScan = scanActive(data.scan)
|
|
@@ -2251,19 +2309,7 @@ const renderOverview = () => {
|
|
|
2251
2309
|
${summary}
|
|
2252
2310
|
</div>
|
|
2253
2311
|
<div class="vault-summary-side">${summarySide}</div>`
|
|
2254
|
-
|
|
2255
|
-
if (modeMenu && modeMenuOpen) modeMenu.open = true
|
|
2256
|
-
if (modeMenu && state.automaticModeMenuRequested) {
|
|
2257
|
-
state.automaticModeMenuRequested = false
|
|
2258
|
-
modeMenu.classList.add("targeted")
|
|
2259
|
-
const trigger = modeMenu.querySelector("summary")
|
|
2260
|
-
requestAnimationFrame(() => {
|
|
2261
|
-
if (trigger && trigger.isConnected) trigger.focus()
|
|
2262
|
-
})
|
|
2263
|
-
setTimeout(() => {
|
|
2264
|
-
if (modeMenu.isConnected) modeMenu.classList.remove("targeted")
|
|
2265
|
-
}, 1600)
|
|
2266
|
-
}
|
|
2312
|
+
restoreAutomaticModeMenu(modeMenuOpen)
|
|
2267
2313
|
}
|
|
2268
2314
|
const idleScanLabel = IS_APP_MODE
|
|
2269
2315
|
? (last ? COPY.scan_again : COPY.scan_app)
|
|
@@ -2289,7 +2335,10 @@ const renderOverview = () => {
|
|
|
2289
2335
|
})
|
|
2290
2336
|
}
|
|
2291
2337
|
const candidateSizeSelect = el("vault-candidate-size")
|
|
2292
|
-
if (candidateSizeSelect)
|
|
2338
|
+
if (candidateSizeSelect) {
|
|
2339
|
+
candidateSizeSelect.hidden = false
|
|
2340
|
+
candidateSizeSelect.disabled = activeScan
|
|
2341
|
+
}
|
|
2293
2342
|
const scanControl = el("vault-scan-control")
|
|
2294
2343
|
if (scanControl) scanControl.classList.toggle("single", activeScan)
|
|
2295
2344
|
const scanSizeMenu = el("vault-scan-size-menu")
|
|
@@ -2369,6 +2418,14 @@ const renderOverview = () => {
|
|
|
2369
2418
|
}
|
|
2370
2419
|
}
|
|
2371
2420
|
|
|
2421
|
+
const renderOverview = () => {
|
|
2422
|
+
if (appSetupRequired()) {
|
|
2423
|
+
renderSetupOverview()
|
|
2424
|
+
return
|
|
2425
|
+
}
|
|
2426
|
+
renderNormalOverview()
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2372
2429
|
const renderResult = () => {
|
|
2373
2430
|
const result = el("vault-result")
|
|
2374
2431
|
if (!state.scanResult) {
|
|
@@ -2555,11 +2612,34 @@ const renderCleanupNotice = () => {
|
|
|
2555
2612
|
notice.innerHTML = `<i class="fa-solid fa-broom" aria-hidden="true"></i><strong>${esc(title)}</strong><span class="vault-cleanup-notice-detail">${esc(detail)}</span><button class="vault-button" id="btn-review-cleanup" type="button">${esc(COPY.review_cleanup)}<i class="fa-solid fa-chevron-right" aria-hidden="true"></i></button>`
|
|
2556
2613
|
}
|
|
2557
2614
|
|
|
2615
|
+
const clearPanel = (id, className) => {
|
|
2616
|
+
const panel = el(id)
|
|
2617
|
+
panel.className = className
|
|
2618
|
+
panel.innerHTML = ""
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
const renderSetupWorkspace = () => {
|
|
2622
|
+
renderSetupOverview()
|
|
2623
|
+
renderFeedback()
|
|
2624
|
+
el("vault-explorer").style.display = "none"
|
|
2625
|
+
clearPanel("vault-action-state", "vault-action-state")
|
|
2626
|
+
clearPanel("vault-result", "vault-result")
|
|
2627
|
+
clearPanel("vault-cleanup-notice", "vault-cleanup-notice")
|
|
2628
|
+
renderedActionProgress = null
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2558
2631
|
const render = () => {
|
|
2559
2632
|
if (!state.data) return
|
|
2633
|
+
const setupRequired = appSetupRequired()
|
|
2634
|
+
const body = document.querySelector(".vault-body")
|
|
2635
|
+
if (body) body.classList.toggle("setup-required", setupRequired)
|
|
2636
|
+
if (setupRequired) {
|
|
2637
|
+
renderSetupWorkspace()
|
|
2638
|
+
return
|
|
2639
|
+
}
|
|
2560
2640
|
renderOverview()
|
|
2561
|
-
renderResult()
|
|
2562
2641
|
renderFeedback()
|
|
2642
|
+
renderResult()
|
|
2563
2643
|
renderCleanupNotice()
|
|
2564
2644
|
if (!state.data.enabled) {
|
|
2565
2645
|
el("vault-explorer").style.display = "none"
|
|
@@ -2738,12 +2818,16 @@ const refresh = async (forceFull = false) => {
|
|
|
2738
2818
|
folderDiscoveryActive(progress.folder_discovery) ||
|
|
2739
2819
|
fileAction) {
|
|
2740
2820
|
delay = 1500
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2821
|
+
if (appSetupRequired()) {
|
|
2822
|
+
renderSetupWorkspace()
|
|
2823
|
+
} else {
|
|
2824
|
+
renderOverview()
|
|
2825
|
+
renderResult()
|
|
2826
|
+
renderActionProgress()
|
|
2827
|
+
renderFeedback()
|
|
2828
|
+
renderExternalPrompt()
|
|
2829
|
+
renderFolderDiscovery()
|
|
2830
|
+
}
|
|
2747
2831
|
} else {
|
|
2748
2832
|
const data = await fetchJson(statusUrl())
|
|
2749
2833
|
if (sequence !== refreshSequence) return
|
|
@@ -3553,6 +3637,10 @@ document.addEventListener("click", async (event) => {
|
|
|
3553
3637
|
}
|
|
3554
3638
|
return
|
|
3555
3639
|
}
|
|
3640
|
+
if (appSetupRequired()) {
|
|
3641
|
+
openGlobalWorkspace()
|
|
3642
|
+
return
|
|
3643
|
+
}
|
|
3556
3644
|
state.scanRequested = true
|
|
3557
3645
|
state.scanBaseline = state.data.last_scan ? state.data.last_scan.ts : null
|
|
3558
3646
|
state.scanResult = null
|
package/server/views/app.ejs
CHANGED
|
@@ -4121,12 +4121,18 @@ body.dark .disk-usage {
|
|
|
4121
4121
|
.app-vault-mode[data-mode="automatic"] {
|
|
4122
4122
|
color: #b45309;
|
|
4123
4123
|
}
|
|
4124
|
+
.app-vault-mode[data-ready="false"] {
|
|
4125
|
+
color: #b45309;
|
|
4126
|
+
}
|
|
4124
4127
|
body.dark .app-vault-mode {
|
|
4125
4128
|
color: rgba(203, 213, 225, 0.74);
|
|
4126
4129
|
}
|
|
4127
4130
|
body.dark .app-vault-mode[data-mode="automatic"] {
|
|
4128
4131
|
color: #e0a54b;
|
|
4129
4132
|
}
|
|
4133
|
+
body.dark .app-vault-mode[data-ready="false"] {
|
|
4134
|
+
color: #e0a54b;
|
|
4135
|
+
}
|
|
4130
4136
|
.app-vault-mode-chevron,
|
|
4131
4137
|
.app-autolaunch-chevron {
|
|
4132
4138
|
display: inline-flex;
|
|
@@ -8501,13 +8507,14 @@ body.dark .pinokio-custom-terminal-header {
|
|
|
8501
8507
|
</button>
|
|
8502
8508
|
<% if (typeof vault_enabled !== 'undefined' && vault_enabled) { %>
|
|
8503
8509
|
<% const vaultAutomaticMode = typeof vault_automatic_mode === 'string' && vault_automatic_mode === 'manual' ? 'manual' : 'automatic' %>
|
|
8504
|
-
|
|
8510
|
+
<% const vaultGlobalScanReady = typeof vault_global_scan_ready !== 'undefined' && vault_global_scan_ready === true %>
|
|
8511
|
+
<a id='save-space-tab' data-mode="refresh" target="app-vault" href="/vault/app/<%=encodeURIComponent(name)%>" class="btn header-item frame-link" data-index="vault" data-static="retain" data-tab-link-popover="false" aria-label="<%=vaultGlobalScanReady ? `Disk Saver — ${vaultAutomaticMode === 'automatic' ? 'Automatic' : 'Manual'} checking` : 'Disk Saver — Set up required'%>">
|
|
8505
8512
|
<div class='tab'>
|
|
8506
8513
|
<i class="fa-solid fa-hard-drive menu-action-leading-icon"></i>
|
|
8507
8514
|
<div class='display'>Disk Saver</div>
|
|
8508
8515
|
<div class='flexible'></div>
|
|
8509
|
-
<span class="app-vault-mode" data-app-vault-mode data-app="<%=name%>" data-mode="<%=vaultAutomaticMode%>" aria-hidden="true">
|
|
8510
|
-
<span data-app-vault-mode-label><%=vaultAutomaticMode === 'automatic' ? 'Auto' : 'Manual'%></span>
|
|
8516
|
+
<span class="app-vault-mode" data-app-vault-mode data-app="<%=name%>" data-mode="<%=vaultAutomaticMode%>" data-ready="<%=vaultGlobalScanReady ? 'true' : 'false'%>" aria-hidden="true">
|
|
8517
|
+
<span data-app-vault-mode-label><%=vaultGlobalScanReady ? (vaultAutomaticMode === 'automatic' ? 'Auto' : 'Manual') : 'Set up'%></span>
|
|
8511
8518
|
</span>
|
|
8512
8519
|
<i class="fa-solid fa-angle-down app-vault-mode-chevron" aria-hidden="true"></i>
|
|
8513
8520
|
</div>
|
|
@@ -50,6 +50,7 @@ test("the shared layout renders and reviews automatic possible-match notices", a
|
|
|
50
50
|
status: 200,
|
|
51
51
|
json: async () => ({
|
|
52
52
|
enabled: true,
|
|
53
|
+
global_scan_ready: true,
|
|
53
54
|
rows: []
|
|
54
55
|
})
|
|
55
56
|
}
|
|
@@ -72,6 +73,22 @@ test("the shared layout renders and reviews automatic possible-match notices", a
|
|
|
72
73
|
eventSources[0].onmessage({
|
|
73
74
|
data: JSON.stringify({
|
|
74
75
|
enabled: true,
|
|
76
|
+
global_scan_ready: false,
|
|
77
|
+
rows: [{
|
|
78
|
+
app: "ComfyUI",
|
|
79
|
+
state: "checking",
|
|
80
|
+
notice_id: "checking:0:"
|
|
81
|
+
}]
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
assert.equal(dom.window.document.getElementById(
|
|
85
|
+
"vault-auto-scan-tray").hidden, true)
|
|
86
|
+
assert.equal(dom.window.document.querySelector(
|
|
87
|
+
".vault-auto-scan-row"), null)
|
|
88
|
+
eventSources[0].onmessage({
|
|
89
|
+
data: JSON.stringify({
|
|
90
|
+
enabled: true,
|
|
91
|
+
global_scan_ready: true,
|
|
75
92
|
rows: [{
|
|
76
93
|
app: "ComfyUI",
|
|
77
94
|
state: "result",
|
|
@@ -171,6 +188,7 @@ test("checking notices expose settings, Pause, and dismissal", async () => {
|
|
|
171
188
|
eventSources[0].onmessage({
|
|
172
189
|
data: JSON.stringify({
|
|
173
190
|
enabled: true,
|
|
191
|
+
global_scan_ready: true,
|
|
174
192
|
rows: [{
|
|
175
193
|
app: "ComfyUI",
|
|
176
194
|
state: "checking",
|
|
@@ -286,6 +304,7 @@ test("an empty automatic check briefly confirms completion", async () => {
|
|
|
286
304
|
status: 200,
|
|
287
305
|
json: async () => ({
|
|
288
306
|
enabled: true,
|
|
307
|
+
global_scan_ready: true,
|
|
289
308
|
rows: [],
|
|
290
309
|
settings: [{ app: "ComfyUI", mode: "automatic" }]
|
|
291
310
|
})
|
|
@@ -304,7 +323,7 @@ test("an empty automatic check briefly confirms completion", async () => {
|
|
|
304
323
|
dom.window.eval(script)
|
|
305
324
|
await waitFor(() => eventSources.length === 1)
|
|
306
325
|
const send = (payload) => eventSources[0].onmessage({
|
|
307
|
-
data: JSON.stringify(payload)
|
|
326
|
+
data: JSON.stringify(Object.assign({ global_scan_ready: true }, payload))
|
|
308
327
|
})
|
|
309
328
|
|
|
310
329
|
send({
|
|
@@ -527,7 +546,7 @@ test("the app sidebar mirrors Automatic and Manual Disk Saver modes", async () =
|
|
|
527
546
|
const script = await fs.promises.readFile(
|
|
528
547
|
path.join(root, "server", "public", "app-vault-mode.js"), "utf8")
|
|
529
548
|
const dom = new JSDOM(`<a id="save-space-tab">
|
|
530
|
-
<span data-app-vault-mode data-app="ComfyUI" data-mode="automatic">
|
|
549
|
+
<span data-app-vault-mode data-app="ComfyUI" data-mode="automatic" data-ready="true">
|
|
531
550
|
<span data-app-vault-mode-label>Auto</span>
|
|
532
551
|
</span>
|
|
533
552
|
</a>`, {
|
|
@@ -557,6 +576,7 @@ test("the app sidebar mirrors Automatic and Manual Disk Saver modes", async () =
|
|
|
557
576
|
|
|
558
577
|
eventSources[0].onmessage({
|
|
559
578
|
data: JSON.stringify({
|
|
579
|
+
global_scan_ready: true,
|
|
560
580
|
settings: [{ app: "ComfyUI", mode: "manual" }]
|
|
561
581
|
})
|
|
562
582
|
})
|
|
@@ -566,10 +586,32 @@ test("the app sidebar mirrors Automatic and Manual Disk Saver modes", async () =
|
|
|
566
586
|
assert.equal(dom.window.document.getElementById("save-space-tab")
|
|
567
587
|
.getAttribute("aria-label"), "Disk Saver — Manual checking")
|
|
568
588
|
|
|
569
|
-
eventSources[0].onmessage({
|
|
589
|
+
eventSources[0].onmessage({
|
|
590
|
+
data: JSON.stringify({ global_scan_ready: true, settings: [] })
|
|
591
|
+
})
|
|
570
592
|
assert.equal(status.dataset.mode, "automatic")
|
|
571
593
|
assert.equal(status.hidden, false)
|
|
572
594
|
assert.equal(label.textContent, "Auto")
|
|
595
|
+
|
|
596
|
+
eventSources[0].onmessage({
|
|
597
|
+
data: JSON.stringify({
|
|
598
|
+
global_scan_ready: false,
|
|
599
|
+
settings: [{ app: "ComfyUI", mode: "automatic" }]
|
|
600
|
+
})
|
|
601
|
+
})
|
|
602
|
+
assert.equal(status.dataset.ready, "false")
|
|
603
|
+
assert.equal(label.textContent, "Set up")
|
|
604
|
+
assert.equal(dom.window.document.getElementById("save-space-tab")
|
|
605
|
+
.getAttribute("aria-label"), "Disk Saver — Set up required")
|
|
606
|
+
|
|
607
|
+
eventSources[0].onmessage({
|
|
608
|
+
data: JSON.stringify({
|
|
609
|
+
global_scan_ready: true,
|
|
610
|
+
settings: [{ app: "ComfyUI", mode: "automatic" }]
|
|
611
|
+
})
|
|
612
|
+
})
|
|
613
|
+
assert.equal(status.dataset.ready, "true")
|
|
614
|
+
assert.equal(label.textContent, "Auto")
|
|
573
615
|
assert.match(template, /data-app-vault-mode/)
|
|
574
616
|
assert.match(template, /app-vault-mode\.js/)
|
|
575
617
|
assert.match(template,
|
|
@@ -592,6 +634,7 @@ test("the app sidebar mirrors Automatic and Manual Disk Saver modes", async () =
|
|
|
592
634
|
assert.doesNotMatch(template, /app-vault-mode-dot/)
|
|
593
635
|
assert.match(server,
|
|
594
636
|
/result\.vault_automatic_mode = setting && setting\.mode === "manual"/)
|
|
637
|
+
assert.match(server, /result\.vault_global_scan_ready/)
|
|
595
638
|
|
|
596
639
|
dom.window.close()
|
|
597
640
|
})
|
|
@@ -39,6 +39,11 @@ const makeVault = async (home, options = {}) => {
|
|
|
39
39
|
} else {
|
|
40
40
|
await vault.init()
|
|
41
41
|
}
|
|
42
|
+
const globalScanReady = options.globalScanReady !== undefined
|
|
43
|
+
? !!options.globalScanReady
|
|
44
|
+
: true
|
|
45
|
+
vault.globalScanReady = async () => globalScanReady
|
|
46
|
+
vault.automaticScans.globalScanReady = globalScanReady
|
|
42
47
|
return vault
|
|
43
48
|
}
|
|
44
49
|
|
|
@@ -75,14 +80,32 @@ describe("automatic app checks", () => {
|
|
|
75
80
|
|
|
76
81
|
test("reading notice state does not create Vault storage", async () => {
|
|
77
82
|
const home = await makeHome()
|
|
78
|
-
const vault = await makeVault(home, {
|
|
83
|
+
const vault = await makeVault(home, {
|
|
84
|
+
deferStorage: true,
|
|
85
|
+
globalScanReady: false
|
|
86
|
+
})
|
|
79
87
|
|
|
80
|
-
|
|
88
|
+
const status = await vault.automaticScanStatus()
|
|
89
|
+
assert.equal(status.global_scan_ready, false)
|
|
90
|
+
assert.deepEqual(status.rows, [])
|
|
81
91
|
assert.equal(vault.initialized, false)
|
|
82
92
|
assert.equal(fs.existsSync(path.join(home, "vault")), false)
|
|
83
93
|
await close(vault)
|
|
84
94
|
})
|
|
85
95
|
|
|
96
|
+
test("missing readiness state keeps automatic checks locked", async () => {
|
|
97
|
+
const automatic = new AutomaticScans({ enabled: true })
|
|
98
|
+
|
|
99
|
+
await automatic.refreshGlobalScanReady()
|
|
100
|
+
|
|
101
|
+
assert.equal(automatic.globalScanReady, false)
|
|
102
|
+
assert.deepEqual(automatic.snapshot().rows, [])
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test("automatic checks wait one second after an app stops", () => {
|
|
106
|
+
assert.equal(AutomaticScans.STOP_SETTLE_MS, 1000)
|
|
107
|
+
})
|
|
108
|
+
|
|
86
109
|
test("disabled Vault ignores app-stop events", async () => {
|
|
87
110
|
const home = await makeHome()
|
|
88
111
|
const appRoot = path.join(home, "api", "disabled-app")
|
|
@@ -111,6 +134,7 @@ describe("automatic app checks", () => {
|
|
|
111
134
|
api: { running_paths: {} }
|
|
112
135
|
},
|
|
113
136
|
sources: () => [],
|
|
137
|
+
globalScanReady: async () => true,
|
|
114
138
|
automaticScanStatus: async () => ({ rows: [] })
|
|
115
139
|
}
|
|
116
140
|
const automatic = new AutomaticScans(vault)
|
|
@@ -840,6 +864,7 @@ describe("automatic app checks", () => {
|
|
|
840
864
|
}
|
|
841
865
|
}
|
|
842
866
|
})
|
|
867
|
+
automatic.globalScanReady = true
|
|
843
868
|
automatic.hydrated = true
|
|
844
869
|
automatic.log = () => {}
|
|
845
870
|
automatic.active = {
|
|
@@ -904,6 +929,7 @@ describe("automatic app checks", () => {
|
|
|
904
929
|
}
|
|
905
930
|
}
|
|
906
931
|
})
|
|
932
|
+
automatic.globalScanReady = true
|
|
907
933
|
automatic.hydrated = true
|
|
908
934
|
automatic.log = () => {}
|
|
909
935
|
automatic.entries.set("demo", {
|
|
@@ -134,6 +134,77 @@ describe("Save Space engine", () => {
|
|
|
134
134
|
await close(vault)
|
|
135
135
|
})
|
|
136
136
|
|
|
137
|
+
test("global readiness accepts only successful publication outcomes", async () => {
|
|
138
|
+
const { vault } = await makeVault()
|
|
139
|
+
const scan = (outcome) => ({ ts: 1, outcome })
|
|
140
|
+
|
|
141
|
+
assert.equal(vault.globalScanIsReady(scan("complete")), true)
|
|
142
|
+
assert.equal(vault.globalScanIsReady(
|
|
143
|
+
scan("completed_with_exclusions")), true)
|
|
144
|
+
assert.equal(vault.globalScanIsReady(scan("cancelled")), false)
|
|
145
|
+
assert.equal(vault.globalScanIsReady(scan("failed")), false)
|
|
146
|
+
assert.equal(vault.globalScanIsReady(null), false)
|
|
147
|
+
|
|
148
|
+
await close(vault)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test("app scans stay locked until the first global scan is published", async () => {
|
|
152
|
+
const { home, vault } = await makeVault()
|
|
153
|
+
await write(path.join(home, "api", "demo", "model.bin"), "demo")
|
|
154
|
+
await vault.openWorkspace()
|
|
155
|
+
const source = vault.sources().find((item) =>
|
|
156
|
+
item.kind === "app" && item.app === "demo")
|
|
157
|
+
|
|
158
|
+
assert.equal(await vault.globalScanReady(), false)
|
|
159
|
+
assert.equal((await vault.status(source.id)).global_scan_ready, false)
|
|
160
|
+
assert.deepEqual(await vault.perform("scan", {
|
|
161
|
+
scope_id: source.id,
|
|
162
|
+
candidate_size: 0
|
|
163
|
+
}), {
|
|
164
|
+
error: "Run an initial scan before scanning individual apps.",
|
|
165
|
+
code: "global_scan_required"
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
assert.deepEqual(await vault.perform("automatic_set_mode", {
|
|
169
|
+
app: "demo",
|
|
170
|
+
mode: "automatic"
|
|
171
|
+
}), { app: "demo", mode: "automatic" })
|
|
172
|
+
const launchPath = path.join(home, "api", "demo", "start.js")
|
|
173
|
+
vault.automaticScans.handleStarted(launchPath)
|
|
174
|
+
await vault.automaticScans.handleStopped(launchPath)
|
|
175
|
+
assert.equal(vault.automaticScans.pendingStops.size, 0)
|
|
176
|
+
assert.deepEqual(vault.automaticScans.snapshot().rows, [])
|
|
177
|
+
|
|
178
|
+
assert.equal((await vault.perform("scan", {
|
|
179
|
+
candidate_size: 0
|
|
180
|
+
})).started, true)
|
|
181
|
+
await waitForEngine(() =>
|
|
182
|
+
!vault.scanPromise && !vault.scanCompletionPromise)
|
|
183
|
+
|
|
184
|
+
assert.equal(await vault.globalScanReady(), true)
|
|
185
|
+
await vault.automaticScans.scanFinished({
|
|
186
|
+
scopeId: null,
|
|
187
|
+
result: { outcome: "cancelled" },
|
|
188
|
+
error: null
|
|
189
|
+
})
|
|
190
|
+
assert.equal(await vault.globalScanReady(), true)
|
|
191
|
+
await vault.automaticScans.scanFinished({
|
|
192
|
+
scopeId: null,
|
|
193
|
+
result: null,
|
|
194
|
+
error: new Error("scan failed")
|
|
195
|
+
})
|
|
196
|
+
assert.equal(await vault.globalScanReady(), true)
|
|
197
|
+
assert.equal(vault.automaticScans.active, null)
|
|
198
|
+
assert.deepEqual(vault.automaticScans.snapshot().rows, [])
|
|
199
|
+
assert.equal((await vault.perform("scan", {
|
|
200
|
+
scope_id: source.id,
|
|
201
|
+
candidate_size: 0
|
|
202
|
+
})).started, true)
|
|
203
|
+
await waitForEngine(() =>
|
|
204
|
+
!vault.scanPromise && !vault.scanCompletionPromise)
|
|
205
|
+
await close(vault)
|
|
206
|
+
})
|
|
207
|
+
|
|
137
208
|
test("locations and anchor stores persist in the Disk Saver config", async () => {
|
|
138
209
|
const base = await makeHome()
|
|
139
210
|
const home = path.join(base, "pinokio")
|
|
@@ -903,6 +974,7 @@ describe("Save Space engine", () => {
|
|
|
903
974
|
const outside = await makeOutside()
|
|
904
975
|
const pair = await duplicatePair(home)
|
|
905
976
|
await vault.sweeper.scan()
|
|
977
|
+
assert.equal(await vault.globalScanReady(), true)
|
|
906
978
|
const duplicate = [...await vault.registry.files({
|
|
907
979
|
statuses: ["duplicate"]
|
|
908
980
|
})][0]
|
|
@@ -922,6 +994,7 @@ describe("Save Space engine", () => {
|
|
|
922
994
|
replacement.sizeThreshold = pair.contents.length * 2
|
|
923
995
|
|
|
924
996
|
assert.deepEqual(replacement.configuredLocations(), [])
|
|
997
|
+
assert.equal(await replacement.globalScanReady(), false)
|
|
925
998
|
assert.equal(await replacement.registry.countFiles(), 0)
|
|
926
999
|
assert.equal(fs.existsSync(anchorPath), true)
|
|
927
1000
|
await replacement.sweeper.scan()
|
package/test/vault-ui.test.js
CHANGED
|
@@ -49,6 +49,7 @@ const fixture = (items = [], overrides = {}) => {
|
|
|
49
49
|
const shared = items.filter((entry) => entry.status === "shared")
|
|
50
50
|
return Object.assign({
|
|
51
51
|
enabled: true,
|
|
52
|
+
global_scan_ready: true,
|
|
52
53
|
mode: "link",
|
|
53
54
|
scan: {
|
|
54
55
|
active: false,
|
|
@@ -162,6 +163,7 @@ const makePage = async (status, options = {}) => {
|
|
|
162
163
|
}
|
|
163
164
|
)
|
|
164
165
|
const requests = []
|
|
166
|
+
const parentNavigations = []
|
|
165
167
|
const selectedSources = new Set(options.folderDiscoverySelected || [])
|
|
166
168
|
const recommendedSources = new Set()
|
|
167
169
|
const confirmations = []
|
|
@@ -249,6 +251,18 @@ const makePage = async (status, options = {}) => {
|
|
|
249
251
|
}
|
|
250
252
|
dom.window.requestAnimationFrame = (callback) =>
|
|
251
253
|
dom.window.setTimeout(callback, 0)
|
|
254
|
+
if (options.embeddedApp) {
|
|
255
|
+
Object.defineProperty(dom.window, "parent", {
|
|
256
|
+
configurable: true,
|
|
257
|
+
value: {
|
|
258
|
+
location: {
|
|
259
|
+
assign(target) {
|
|
260
|
+
parentNavigations.push(target)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
}
|
|
252
266
|
if (options.fastStatusRetry) {
|
|
253
267
|
const setTimeout = dom.window.setTimeout.bind(dom.window)
|
|
254
268
|
dom.window.setTimeout = (callback, delay, ...args) =>
|
|
@@ -370,7 +384,8 @@ const makePage = async (status, options = {}) => {
|
|
|
370
384
|
}
|
|
371
385
|
dom.window.eval(await source(path.join(publicRoot, "storage-size.js")))
|
|
372
386
|
dom.window.eval(await source(path.join(publicRoot, "vault.js")))
|
|
373
|
-
await waitFor(() => dom.window.document.querySelector(
|
|
387
|
+
await waitFor(() => dom.window.document.querySelector(
|
|
388
|
+
".vault-summary-value"))
|
|
374
389
|
const choosePickedPath = (folderPath) => {
|
|
375
390
|
const pending = pendingPickers.shift()
|
|
376
391
|
if (!pending) throw new Error("No folder picker is waiting for a selection.")
|
|
@@ -398,6 +413,7 @@ const makePage = async (status, options = {}) => {
|
|
|
398
413
|
return {
|
|
399
414
|
dom,
|
|
400
415
|
requests,
|
|
416
|
+
parentNavigations,
|
|
401
417
|
confirmations,
|
|
402
418
|
pickerRequests,
|
|
403
419
|
choosePickedPath,
|
|
@@ -444,6 +460,7 @@ describe("Save Space interface", () => {
|
|
|
444
460
|
assert.doesNotMatch(combined, /Previous completed results were kept\./)
|
|
445
461
|
assert.match(combined, /Scan completed with exclusions/)
|
|
446
462
|
assert.match(combined, /Provisional until the scan completes/)
|
|
463
|
+
assert.match(combined, /Set up Disk Saver/)
|
|
447
464
|
assert.doesNotMatch(combined, /Scan all locations/)
|
|
448
465
|
assert.match(combined, /data-find-home-folder/)
|
|
449
466
|
assert.match(combined, /data-find-other-folder/)
|
|
@@ -2204,6 +2221,95 @@ describe("Save Space interface", () => {
|
|
|
2204
2221
|
dom.window.close()
|
|
2205
2222
|
})
|
|
2206
2223
|
|
|
2224
|
+
test("an app without a global baseline shows setup and opens global Disk Saver", async () => {
|
|
2225
|
+
const status = fixture([], {
|
|
2226
|
+
global_scan_ready: false,
|
|
2227
|
+
last_scan: null,
|
|
2228
|
+
logical_bytes: 0,
|
|
2229
|
+
saved_by_sharing: 0,
|
|
2230
|
+
pending_bytes: 0
|
|
2231
|
+
})
|
|
2232
|
+
const { dom, requests, parentNavigations } = await makePage(status, {
|
|
2233
|
+
appMode: true,
|
|
2234
|
+
embeddedApp: true,
|
|
2235
|
+
scopeId: "app:app",
|
|
2236
|
+
actionResults: {
|
|
2237
|
+
automatic_set_mode: { app: "app", mode: "manual" }
|
|
2238
|
+
}
|
|
2239
|
+
})
|
|
2240
|
+
const document = dom.window.document
|
|
2241
|
+
const scanButton = document.getElementById("btn-scan")
|
|
2242
|
+
|
|
2243
|
+
assert.match(document.querySelector(".vault-summary-value").textContent,
|
|
2244
|
+
/Run an initial scan to enable app scans/)
|
|
2245
|
+
assert.match(document.querySelector(".vault-setup-copy").textContent,
|
|
2246
|
+
/creates the file index used to compare apps/)
|
|
2247
|
+
assert.match(scanButton.textContent, /Set up Disk Saver/)
|
|
2248
|
+
assert.equal(scanButton.classList.contains("primary"), true)
|
|
2249
|
+
assert.equal(document.getElementById("vault-candidate-size").hidden, true)
|
|
2250
|
+
assert.equal(document.getElementById("vault-explorer").style.display,
|
|
2251
|
+
"none")
|
|
2252
|
+
assert.doesNotMatch(document.getElementById("vault-metrics").textContent,
|
|
2253
|
+
/Nothing else to save/)
|
|
2254
|
+
|
|
2255
|
+
await waitFor(() => document.querySelector(
|
|
2256
|
+
'[data-automatic-mode="manual"]'))
|
|
2257
|
+
document.querySelector('[data-automatic-mode="manual"]').click()
|
|
2258
|
+
await waitFor(() => requests.some((request) =>
|
|
2259
|
+
request.action === "automatic_set_mode"))
|
|
2260
|
+
await waitFor(() => document.getElementById(
|
|
2261
|
+
"vault-auto-mode").classList.contains("manual"))
|
|
2262
|
+
assert.match(document.querySelector(".vault-summary-value").textContent,
|
|
2263
|
+
/Run an initial scan to enable app scans/)
|
|
2264
|
+
assert.equal(document.getElementById("vault-explorer").style.display,
|
|
2265
|
+
"none")
|
|
2266
|
+
|
|
2267
|
+
scanButton.click()
|
|
2268
|
+
assert.deepEqual(parentNavigations, ["/vault"])
|
|
2269
|
+
assert.equal(requests.some((request) => request.action === "scan"), false)
|
|
2270
|
+
|
|
2271
|
+
dom.window.close()
|
|
2272
|
+
})
|
|
2273
|
+
|
|
2274
|
+
test("app setup remains visible during global scan progress", async () => {
|
|
2275
|
+
let progressRequests = 0
|
|
2276
|
+
const status = fixture([], {
|
|
2277
|
+
global_scan_ready: false,
|
|
2278
|
+
scan: {
|
|
2279
|
+
active: true,
|
|
2280
|
+
pending: false,
|
|
2281
|
+
phase: "walking",
|
|
2282
|
+
queued: 0,
|
|
2283
|
+
scope_id: null
|
|
2284
|
+
},
|
|
2285
|
+
last_scan: null,
|
|
2286
|
+
logical_bytes: 0,
|
|
2287
|
+
saved_by_sharing: 0,
|
|
2288
|
+
pending_bytes: 0
|
|
2289
|
+
})
|
|
2290
|
+
const { dom } = await makePage((url) => {
|
|
2291
|
+
if (url.includes("progress=1")) progressRequests += 1
|
|
2292
|
+
return status
|
|
2293
|
+
}, {
|
|
2294
|
+
appMode: true,
|
|
2295
|
+
scopeId: "app:app"
|
|
2296
|
+
})
|
|
2297
|
+
const document = dom.window.document
|
|
2298
|
+
|
|
2299
|
+
for (let attempt = 0; attempt < 400 && !progressRequests; attempt++) {
|
|
2300
|
+
await new Promise((resolve) => setTimeout(resolve, 5))
|
|
2301
|
+
}
|
|
2302
|
+
assert.ok(progressRequests)
|
|
2303
|
+
assert.match(document.querySelector(".vault-summary-value").textContent,
|
|
2304
|
+
/Run an initial scan to enable app scans/)
|
|
2305
|
+
assert.equal(document.getElementById("vault-explorer").style.display,
|
|
2306
|
+
"none")
|
|
2307
|
+
assert.equal(document.getElementById("vault-result").textContent, "")
|
|
2308
|
+
assert.equal(document.getElementById("vault-action-state").textContent, "")
|
|
2309
|
+
|
|
2310
|
+
dom.window.close()
|
|
2311
|
+
})
|
|
2312
|
+
|
|
2207
2313
|
test("app mode uses full-width results without redundant locations", async () => {
|
|
2208
2314
|
const status = fixture([item()])
|
|
2209
2315
|
const { dom, requests } = await makePage(status, {
|