mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-07 13:22:54 +00:00
Initial commit
This commit is contained in:
365
lib/stores/settings.ts
Normal file
365
lib/stores/settings.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
import { writable, derived, get } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export type TimeFormat = '12h' | '24h';
|
||||
export type DateFormat = 'MM/DD/YYYY' | 'DD/MM/YYYY' | 'YYYY-MM-DD' | 'DD.MM.YYYY';
|
||||
export type DownloadFormat = 'tar' | 'tar.gz';
|
||||
|
||||
export interface AppSettings {
|
||||
confirmDestructive: boolean;
|
||||
showStoppedContainers: boolean;
|
||||
highlightUpdates: boolean;
|
||||
timeFormat: TimeFormat;
|
||||
dateFormat: DateFormat;
|
||||
downloadFormat: DownloadFormat;
|
||||
defaultGrypeArgs: string;
|
||||
defaultTrivyArgs: string;
|
||||
scheduleRetentionDays: number;
|
||||
eventRetentionDays: number;
|
||||
scheduleCleanupCron: string;
|
||||
eventCleanupCron: string;
|
||||
scheduleCleanupEnabled: boolean;
|
||||
eventCleanupEnabled: boolean;
|
||||
logBufferSizeKb: number;
|
||||
defaultTimezone: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: AppSettings = {
|
||||
confirmDestructive: true,
|
||||
showStoppedContainers: true,
|
||||
highlightUpdates: true,
|
||||
timeFormat: '24h',
|
||||
dateFormat: 'DD.MM.YYYY',
|
||||
downloadFormat: 'tar',
|
||||
defaultGrypeArgs: '-o json -v {image}',
|
||||
defaultTrivyArgs: 'image --format json {image}',
|
||||
scheduleRetentionDays: 30,
|
||||
eventRetentionDays: 30,
|
||||
scheduleCleanupCron: '0 3 * * *',
|
||||
eventCleanupCron: '30 3 * * *',
|
||||
scheduleCleanupEnabled: true,
|
||||
eventCleanupEnabled: true,
|
||||
logBufferSizeKb: 500,
|
||||
defaultTimezone: 'UTC'
|
||||
};
|
||||
|
||||
// Create a writable store for app settings
|
||||
function createSettingsStore() {
|
||||
const { subscribe, set, update } = writable<AppSettings>(DEFAULT_SETTINGS);
|
||||
let initialized = false;
|
||||
|
||||
// Load settings from database on initialization
|
||||
async function loadSettings() {
|
||||
if (!browser || initialized) return;
|
||||
initialized = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/settings/general');
|
||||
if (response.ok) {
|
||||
const settings = await response.json();
|
||||
set({
|
||||
confirmDestructive: settings.confirmDestructive ?? DEFAULT_SETTINGS.confirmDestructive,
|
||||
showStoppedContainers: settings.showStoppedContainers ?? DEFAULT_SETTINGS.showStoppedContainers,
|
||||
highlightUpdates: settings.highlightUpdates ?? DEFAULT_SETTINGS.highlightUpdates,
|
||||
timeFormat: settings.timeFormat ?? DEFAULT_SETTINGS.timeFormat,
|
||||
dateFormat: settings.dateFormat ?? DEFAULT_SETTINGS.dateFormat,
|
||||
downloadFormat: settings.downloadFormat ?? DEFAULT_SETTINGS.downloadFormat,
|
||||
defaultGrypeArgs: settings.defaultGrypeArgs ?? DEFAULT_SETTINGS.defaultGrypeArgs,
|
||||
defaultTrivyArgs: settings.defaultTrivyArgs ?? DEFAULT_SETTINGS.defaultTrivyArgs,
|
||||
scheduleRetentionDays: settings.scheduleRetentionDays ?? DEFAULT_SETTINGS.scheduleRetentionDays,
|
||||
eventRetentionDays: settings.eventRetentionDays ?? DEFAULT_SETTINGS.eventRetentionDays,
|
||||
scheduleCleanupCron: settings.scheduleCleanupCron ?? DEFAULT_SETTINGS.scheduleCleanupCron,
|
||||
eventCleanupCron: settings.eventCleanupCron ?? DEFAULT_SETTINGS.eventCleanupCron,
|
||||
scheduleCleanupEnabled: settings.scheduleCleanupEnabled ?? DEFAULT_SETTINGS.scheduleCleanupEnabled,
|
||||
eventCleanupEnabled: settings.eventCleanupEnabled ?? DEFAULT_SETTINGS.eventCleanupEnabled,
|
||||
logBufferSizeKb: settings.logBufferSizeKb ?? DEFAULT_SETTINGS.logBufferSizeKb,
|
||||
defaultTimezone: settings.defaultTimezone ?? DEFAULT_SETTINGS.defaultTimezone
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Silently use defaults if settings can't be loaded
|
||||
}
|
||||
}
|
||||
|
||||
// Save settings to database
|
||||
async function saveSettings(settings: Partial<AppSettings>) {
|
||||
if (!browser) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/settings/general', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(settings)
|
||||
});
|
||||
if (response.ok) {
|
||||
const updatedSettings = await response.json();
|
||||
set({
|
||||
confirmDestructive: updatedSettings.confirmDestructive ?? DEFAULT_SETTINGS.confirmDestructive,
|
||||
showStoppedContainers: updatedSettings.showStoppedContainers ?? DEFAULT_SETTINGS.showStoppedContainers,
|
||||
highlightUpdates: updatedSettings.highlightUpdates ?? DEFAULT_SETTINGS.highlightUpdates,
|
||||
timeFormat: updatedSettings.timeFormat ?? DEFAULT_SETTINGS.timeFormat,
|
||||
dateFormat: updatedSettings.dateFormat ?? DEFAULT_SETTINGS.dateFormat,
|
||||
downloadFormat: updatedSettings.downloadFormat ?? DEFAULT_SETTINGS.downloadFormat,
|
||||
defaultGrypeArgs: updatedSettings.defaultGrypeArgs ?? DEFAULT_SETTINGS.defaultGrypeArgs,
|
||||
defaultTrivyArgs: updatedSettings.defaultTrivyArgs ?? DEFAULT_SETTINGS.defaultTrivyArgs,
|
||||
scheduleRetentionDays: updatedSettings.scheduleRetentionDays ?? DEFAULT_SETTINGS.scheduleRetentionDays,
|
||||
eventRetentionDays: updatedSettings.eventRetentionDays ?? DEFAULT_SETTINGS.eventRetentionDays,
|
||||
scheduleCleanupCron: updatedSettings.scheduleCleanupCron ?? DEFAULT_SETTINGS.scheduleCleanupCron,
|
||||
eventCleanupCron: updatedSettings.eventCleanupCron ?? DEFAULT_SETTINGS.eventCleanupCron,
|
||||
scheduleCleanupEnabled: updatedSettings.scheduleCleanupEnabled ?? DEFAULT_SETTINGS.scheduleCleanupEnabled,
|
||||
eventCleanupEnabled: updatedSettings.eventCleanupEnabled ?? DEFAULT_SETTINGS.eventCleanupEnabled,
|
||||
logBufferSizeKb: updatedSettings.logBufferSizeKb ?? DEFAULT_SETTINGS.logBufferSizeKb,
|
||||
defaultTimezone: updatedSettings.defaultTimezone ?? DEFAULT_SETTINGS.defaultTimezone
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to save settings:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load settings on store creation
|
||||
if (browser) {
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
set: (value: AppSettings) => {
|
||||
set(value);
|
||||
saveSettings(value);
|
||||
},
|
||||
update: (fn: (settings: AppSettings) => AppSettings) => {
|
||||
update((current) => {
|
||||
const newSettings = fn(current);
|
||||
saveSettings(newSettings);
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
// Convenience methods for individual settings
|
||||
setConfirmDestructive: (value: boolean) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, confirmDestructive: value };
|
||||
saveSettings({ confirmDestructive: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setShowStoppedContainers: (value: boolean) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, showStoppedContainers: value };
|
||||
saveSettings({ showStoppedContainers: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setHighlightUpdates: (value: boolean) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, highlightUpdates: value };
|
||||
saveSettings({ highlightUpdates: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setTimeFormat: (value: TimeFormat) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, timeFormat: value };
|
||||
saveSettings({ timeFormat: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setDateFormat: (value: DateFormat) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, dateFormat: value };
|
||||
saveSettings({ dateFormat: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setDownloadFormat: (value: DownloadFormat) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, downloadFormat: value };
|
||||
saveSettings({ downloadFormat: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setDefaultGrypeArgs: (value: string) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, defaultGrypeArgs: value };
|
||||
saveSettings({ defaultGrypeArgs: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setDefaultTrivyArgs: (value: string) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, defaultTrivyArgs: value };
|
||||
saveSettings({ defaultTrivyArgs: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setScheduleRetentionDays: (value: number) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, scheduleRetentionDays: value };
|
||||
saveSettings({ scheduleRetentionDays: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setEventRetentionDays: (value: number) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, eventRetentionDays: value };
|
||||
saveSettings({ eventRetentionDays: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setScheduleCleanupCron: (value: string) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, scheduleCleanupCron: value };
|
||||
saveSettings({ scheduleCleanupCron: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setEventCleanupCron: (value: string) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, eventCleanupCron: value };
|
||||
saveSettings({ eventCleanupCron: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setScheduleCleanupEnabled: (value: boolean) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, scheduleCleanupEnabled: value };
|
||||
saveSettings({ scheduleCleanupEnabled: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setEventCleanupEnabled: (value: boolean) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, eventCleanupEnabled: value };
|
||||
saveSettings({ eventCleanupEnabled: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setLogBufferSizeKb: (value: number) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, logBufferSizeKb: value };
|
||||
saveSettings({ logBufferSizeKb: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
setDefaultTimezone: (value: string) => {
|
||||
update((current) => {
|
||||
const newSettings = { ...current, defaultTimezone: value };
|
||||
saveSettings({ defaultTimezone: value });
|
||||
return newSettings;
|
||||
});
|
||||
},
|
||||
// Manual refresh from database
|
||||
refresh: loadSettings
|
||||
};
|
||||
}
|
||||
|
||||
export const appSettings = createSettingsStore();
|
||||
|
||||
// Cache current settings for synchronous access (updated reactively)
|
||||
let cachedTimeFormat: TimeFormat = DEFAULT_SETTINGS.timeFormat;
|
||||
let cachedDateFormat: DateFormat = DEFAULT_SETTINGS.dateFormat;
|
||||
|
||||
// Subscribe once to keep cache updated
|
||||
if (browser) {
|
||||
appSettings.subscribe((s) => {
|
||||
cachedTimeFormat = s.timeFormat;
|
||||
cachedDateFormat = s.dateFormat;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date part according to user's date format preference.
|
||||
* This is a low-level helper - prefer formatDateTime for most uses.
|
||||
*/
|
||||
function formatDatePart(d: Date): string {
|
||||
const day = d.getDate().toString().padStart(2, '0');
|
||||
const month = (d.getMonth() + 1).toString().padStart(2, '0');
|
||||
const year = d.getFullYear();
|
||||
|
||||
switch (cachedDateFormat) {
|
||||
case 'MM/DD/YYYY':
|
||||
return `${month}/${day}/${year}`;
|
||||
case 'DD/MM/YYYY':
|
||||
return `${day}/${month}/${year}`;
|
||||
case 'YYYY-MM-DD':
|
||||
return `${year}-${month}-${day}`;
|
||||
case 'DD.MM.YYYY':
|
||||
default:
|
||||
return `${day}.${month}.${year}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a time part according to user's time format preference.
|
||||
* This is a low-level helper - prefer formatDateTime for most uses.
|
||||
*/
|
||||
function formatTimePart(d: Date, includeSeconds = false): string {
|
||||
const hours = d.getHours();
|
||||
const minutes = d.getMinutes().toString().padStart(2, '0');
|
||||
const seconds = d.getSeconds().toString().padStart(2, '0');
|
||||
|
||||
if (cachedTimeFormat === '12h') {
|
||||
const hour12 = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
||||
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||
return includeSeconds
|
||||
? `${hour12}:${minutes}:${seconds} ${ampm}`
|
||||
: `${hour12}:${minutes} ${ampm}`;
|
||||
} else {
|
||||
const hour24 = hours.toString().padStart(2, '0');
|
||||
return includeSeconds
|
||||
? `${hour24}:${minutes}:${seconds}`
|
||||
: `${hour24}:${minutes}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a timestamp according to user's time and date format preferences.
|
||||
* Performant: uses cached settings, no store subscription per call.
|
||||
*
|
||||
* @param date - Date object, ISO string, or timestamp
|
||||
* @param options - Formatting options
|
||||
* @returns Formatted string
|
||||
*/
|
||||
export function formatTime(
|
||||
date: Date | string | number,
|
||||
options: { includeDate?: boolean; includeSeconds?: boolean } = {}
|
||||
): string {
|
||||
const d = date instanceof Date ? date : new Date(date);
|
||||
const { includeDate = false, includeSeconds = false } = options;
|
||||
|
||||
if (includeDate) {
|
||||
return `${formatDatePart(d)} ${formatTimePart(d, includeSeconds)}`;
|
||||
}
|
||||
|
||||
return formatTimePart(d, includeSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a timestamp with date according to user's preferences.
|
||||
* Convenience wrapper around formatTime.
|
||||
*/
|
||||
export function formatDateTime(date: Date | string | number, includeSeconds = false): string {
|
||||
return formatTime(date, { includeDate: true, includeSeconds });
|
||||
}
|
||||
|
||||
/**
|
||||
* Format just the date part according to user's preferences.
|
||||
*/
|
||||
export function formatDate(date: Date | string | number): string {
|
||||
const d = date instanceof Date ? date : new Date(date);
|
||||
return formatDatePart(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current time format setting (for components that need it).
|
||||
*/
|
||||
export function getTimeFormat(): TimeFormat {
|
||||
return cachedTimeFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current date format setting (for components that need it).
|
||||
*/
|
||||
export function getDateFormat(): DateFormat {
|
||||
return cachedDateFormat;
|
||||
}
|
||||
Reference in New Issue
Block a user