choose which stack operations show the full compose log

This commit is contained in:
Jarek Krochmalski committed 2026-09-13 14:50:02 +02:00
1 parent cd811d5117
commit fb37d931ae
6 files changed
+249 -20

No files matched your search

+18 -2
View File
@@ -7,6 +7,11 @@ import {
parseTimestamp,
type DateTimeFormatters
} from '$lib/utils/date-format';
import {
DEFAULT_STACK_LOG_OPERATIONS,
sanitizeStackLogOperations,
type StackLogOperation
} from '$lib/utils/stack-log-operations';
export type TimeFormat = '12h' | '24h';
export type DateFormat = 'MM/DD/YYYY' | 'DD/MM/YYYY' | 'YYYY-MM-DD' | 'DD.MM.YYYY';
@@ -59,6 +64,7 @@ export interface AppSettings {
// Scanner Advanced settings (#1219). Empty values = use auto-detection.
defaultScannerNetworkMode: string; // '' | 'host' | 'bridge' | 'none' | <custom-network>
defaultScannerDns: string[]; // ['1.1.1.1', '8.8.8.8']; empty = inherit
stackLogOperations: StackLogOperation[]; // stack ops that show the log popover (#1558)
}
const DEFAULT_SETTINGS: AppSettings = {
@@ -102,6 +108,7 @@ const DEFAULT_SETTINGS: AppSettings = {
protectScannerImages: true,
defaultScannerNetworkMode: '',
defaultScannerDns: [],
stackLogOperations: DEFAULT_STACK_LOG_OPERATIONS,
defaultComposeTemplate: `version: "3.8"
services:
@@ -195,7 +202,8 @@ function createSettingsStore() {
showWhatsNew: settings.showWhatsNew ?? DEFAULT_SETTINGS.showWhatsNew,
protectScannerImages: settings.protectScannerImages ?? DEFAULT_SETTINGS.protectScannerImages,
defaultScannerNetworkMode: settings.defaultScannerNetworkMode ?? DEFAULT_SETTINGS.defaultScannerNetworkMode,
defaultScannerDns: Array.isArray(settings.defaultScannerDns) ? settings.defaultScannerDns : DEFAULT_SETTINGS.defaultScannerDns
defaultScannerDns: Array.isArray(settings.defaultScannerDns) ? settings.defaultScannerDns : DEFAULT_SETTINGS.defaultScannerDns,
stackLogOperations: sanitizeStackLogOperations(settings.stackLogOperations)
});
}
} catch {
@@ -257,7 +265,8 @@ function createSettingsStore() {
showWhatsNew: updatedSettings.showWhatsNew ?? DEFAULT_SETTINGS.showWhatsNew,
protectScannerImages: updatedSettings.protectScannerImages ?? DEFAULT_SETTINGS.protectScannerImages,
defaultScannerNetworkMode: updatedSettings.defaultScannerNetworkMode ?? DEFAULT_SETTINGS.defaultScannerNetworkMode,
defaultScannerDns: Array.isArray(updatedSettings.defaultScannerDns) ? updatedSettings.defaultScannerDns : DEFAULT_SETTINGS.defaultScannerDns
defaultScannerDns: Array.isArray(updatedSettings.defaultScannerDns) ? updatedSettings.defaultScannerDns : DEFAULT_SETTINGS.defaultScannerDns,
stackLogOperations: sanitizeStackLogOperations(updatedSettings.stackLogOperations)
});
}
} catch (error) {
@@ -291,6 +300,13 @@ function createSettingsStore() {
return newSettings;
});
},
setStackLogOperations: (value: StackLogOperation[]) => {
const clean = sanitizeStackLogOperations(value);
update((current) => {
saveSettings({ stackLogOperations: clean });
return { ...current, stackLogOperations: clean };
});
},
setShowStoppedContainers: (value: boolean) => {
update((current) => {
const newSettings = { ...current, showStoppedContainers: value };
+65
View File
@@ -0,0 +1,65 @@
/**
* Which stack operations show the full compose-log popover (#1558).
*
* Some users find the log popover on every start/stop noisy and just want the
* buttons + a toast. This setting lists the operations for which the popover IS
* shown; operations not in the list run "quiet" (toast only), and their log is
* opened automatically only if the operation FAILS, so errors are never hidden.
*
* Stored as a JSON array of operation keys in the settings KV. Pure + shared so
* the default, the key set, and the "should show?" decision are one source of
* truth for the server, the store, and the stacks page.
*/
// Only operations that actually stream a compose log are listed. Remove is deliberately
// absent: it never opens the log popover (plain toast + error dialog), so a checkbox for it
// would control nothing.
export type StackLogOperation = 'start' | 'stop' | 'restart' | 'deploy' | 'down';
/** All operations, in display order, with a human label for the settings list. */
export const STACK_LOG_OPERATIONS: { key: StackLogOperation; label: string }[] = [
{ key: 'start', label: 'Start' },
{ key: 'stop', label: 'Stop' },
{ key: 'restart', label: 'Restart / recreate' },
{ key: 'deploy', label: 'Deploy / redeploy' },
{ key: 'down', label: 'Down' }
];
const ALL_KEYS = STACK_LOG_OPERATIONS.map((o) => o.key);
/**
* Default: show the log for everything EXCEPT the simple start/stop, matching the
* 1.0.47 behavior for the heavier operations while giving start/stop the quiet
* treatment the request asked for.
*/
export const DEFAULT_STACK_LOG_OPERATIONS: StackLogOperation[] = ['restart', 'deploy', 'down'];
/** Keep only valid, de-duplicated operation keys (defends the KV value + API body). */
export function sanitizeStackLogOperations(value: unknown): StackLogOperation[] {
if (!Array.isArray(value)) return [...DEFAULT_STACK_LOG_OPERATIONS];
const seen = new Set<StackLogOperation>();
for (const v of value) {
if (typeof v === 'string' && (ALL_KEYS as string[]).includes(v)) seen.add(v as StackLogOperation);
}
return ALL_KEYS.filter((k) => seen.has(k)); // canonical order
}
/** Whether the log popover should be shown for this operation. */
export function shouldShowStackLog(ops: StackLogOperation[], op: StackLogOperation): boolean {
return ops.includes(op);
}
/**
* Decode the stored KV value (a JSON array string) into the operation list. The
* unset-vs-empty distinction is load-bearing: a null/absent value means the user never
* touched the setting -> use the default; a stored "[]" means the user turned every op
* off -> honor it as "log nothing" (NOT the default). Malformed JSON falls back to the
* default rather than throwing.
*/
export function parseStackLogOperationsStorage(raw: string | null | undefined): StackLogOperation[] {
if (raw == null) return [...DEFAULT_STACK_LOG_OPERATIONS];
try {
return sanitizeStackLogOperations(JSON.parse(raw));
} catch {
return [...DEFAULT_STACK_LOG_OPERATIONS];
}
}