mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-06 21:29:05 +00:00
Initial commit
This commit is contained in:
329
routes/api/settings/general/+server.ts
Normal file
329
routes/api/settings/general/+server.ts
Normal file
@@ -0,0 +1,329 @@
|
||||
import { json, type RequestHandler } from '@sveltejs/kit';
|
||||
import {
|
||||
getSetting,
|
||||
setSetting,
|
||||
getScheduleRetentionDays,
|
||||
setScheduleRetentionDays,
|
||||
getEventRetentionDays,
|
||||
setEventRetentionDays,
|
||||
getScheduleCleanupCron,
|
||||
setScheduleCleanupCron,
|
||||
getEventCleanupCron,
|
||||
setEventCleanupCron,
|
||||
getScheduleCleanupEnabled,
|
||||
setScheduleCleanupEnabled,
|
||||
getEventCleanupEnabled,
|
||||
setEventCleanupEnabled,
|
||||
getDefaultTimezone,
|
||||
setDefaultTimezone
|
||||
} from '$lib/server/db';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { refreshSystemJobs } from '$lib/server/scheduler';
|
||||
|
||||
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 GeneralSettings {
|
||||
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;
|
||||
// Theme settings (for when auth is disabled)
|
||||
lightTheme: string;
|
||||
darkTheme: string;
|
||||
font: string;
|
||||
fontSize: string;
|
||||
gridFontSize: string;
|
||||
terminalFont: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: Omit<GeneralSettings, 'scheduleRetentionDays' | 'eventRetentionDays' | 'scheduleCleanupCron' | 'eventCleanupCron' | 'scheduleCleanupEnabled' | 'eventCleanupEnabled'> = {
|
||||
confirmDestructive: true,
|
||||
showStoppedContainers: true,
|
||||
highlightUpdates: true,
|
||||
timeFormat: '24h',
|
||||
dateFormat: 'DD.MM.YYYY',
|
||||
downloadFormat: 'tar',
|
||||
defaultGrypeArgs: '-o json -v {image}',
|
||||
defaultTrivyArgs: 'image --format json {image}',
|
||||
logBufferSizeKb: 500,
|
||||
defaultTimezone: 'UTC',
|
||||
lightTheme: 'default',
|
||||
darkTheme: 'default',
|
||||
font: 'system',
|
||||
fontSize: 'normal',
|
||||
gridFontSize: 'normal',
|
||||
terminalFont: 'system-mono'
|
||||
};
|
||||
|
||||
const VALID_LIGHT_THEMES = ['default', 'catppuccin', 'rose-pine', 'nord', 'solarized', 'gruvbox', 'alucard', 'github', 'material', 'atom-one'];
|
||||
const VALID_DARK_THEMES = ['default', 'catppuccin', 'dracula', 'rose-pine', 'rose-pine-moon', 'tokyo-night', 'nord', 'one-dark', 'gruvbox', 'solarized', 'everforest', 'kanagawa', 'monokai', 'monokai-pro', 'material', 'palenight', 'github'];
|
||||
const VALID_FONTS = ['system', 'geist', 'inter', 'plus-jakarta', 'dm-sans', 'outfit', 'space-grotesk', 'sofia-sans', 'nunito', 'poppins', 'montserrat', 'raleway', 'manrope', 'roboto', 'open-sans', 'lato', 'source-sans', 'work-sans', 'fira-sans', 'jetbrains-mono', 'fira-code', 'quicksand', 'comfortaa'];
|
||||
const VALID_FONT_SIZES = ['xsmall', 'small', 'normal', 'medium', 'large', 'xlarge'];
|
||||
const VALID_TERMINAL_FONTS = ['system-mono', 'jetbrains-mono', 'fira-code', 'source-code-pro', 'cascadia-code', 'menlo', 'consolas', 'sf-mono'];
|
||||
|
||||
const VALID_DATE_FORMATS: DateFormat[] = ['MM/DD/YYYY', 'DD/MM/YYYY', 'YYYY-MM-DD', 'DD.MM.YYYY'];
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
// UI preferences (time format, date format) should be available to all authenticated users
|
||||
// This doesn't expose sensitive data and is needed for proper UI rendering
|
||||
if (auth.authEnabled && !auth.isAuthenticated) {
|
||||
return json({ error: 'Authentication required' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch all settings in parallel for better performance
|
||||
const [
|
||||
confirmDestructive,
|
||||
showStoppedContainers,
|
||||
highlightUpdates,
|
||||
timeFormat,
|
||||
dateFormat,
|
||||
downloadFormat,
|
||||
defaultGrypeArgs,
|
||||
defaultTrivyArgs,
|
||||
scheduleRetentionDays,
|
||||
eventRetentionDays,
|
||||
scheduleCleanupCron,
|
||||
eventCleanupCron,
|
||||
scheduleCleanupEnabled,
|
||||
eventCleanupEnabled,
|
||||
logBufferSizeKb,
|
||||
defaultTimezone,
|
||||
lightTheme,
|
||||
darkTheme,
|
||||
font,
|
||||
fontSize,
|
||||
gridFontSize,
|
||||
terminalFont
|
||||
] = await Promise.all([
|
||||
getSetting('confirm_destructive'),
|
||||
getSetting('show_stopped_containers'),
|
||||
getSetting('highlight_updates'),
|
||||
getSetting('time_format'),
|
||||
getSetting('date_format'),
|
||||
getSetting('download_format'),
|
||||
getSetting('default_grype_args'),
|
||||
getSetting('default_trivy_args'),
|
||||
getScheduleRetentionDays(),
|
||||
getEventRetentionDays(),
|
||||
getScheduleCleanupCron(),
|
||||
getEventCleanupCron(),
|
||||
getScheduleCleanupEnabled(),
|
||||
getEventCleanupEnabled(),
|
||||
getSetting('log_buffer_size_kb'),
|
||||
getDefaultTimezone(),
|
||||
getSetting('theme_light'),
|
||||
getSetting('theme_dark'),
|
||||
getSetting('theme_font'),
|
||||
getSetting('theme_font_size'),
|
||||
getSetting('theme_grid_font_size'),
|
||||
getSetting('theme_terminal_font')
|
||||
]);
|
||||
|
||||
const settings: GeneralSettings = {
|
||||
confirmDestructive: confirmDestructive ?? DEFAULT_SETTINGS.confirmDestructive,
|
||||
showStoppedContainers: showStoppedContainers ?? DEFAULT_SETTINGS.showStoppedContainers,
|
||||
highlightUpdates: highlightUpdates ?? DEFAULT_SETTINGS.highlightUpdates,
|
||||
timeFormat: timeFormat ?? DEFAULT_SETTINGS.timeFormat,
|
||||
dateFormat: dateFormat ?? DEFAULT_SETTINGS.dateFormat,
|
||||
downloadFormat: downloadFormat ?? DEFAULT_SETTINGS.downloadFormat,
|
||||
defaultGrypeArgs: defaultGrypeArgs ?? DEFAULT_SETTINGS.defaultGrypeArgs,
|
||||
defaultTrivyArgs: defaultTrivyArgs ?? DEFAULT_SETTINGS.defaultTrivyArgs,
|
||||
scheduleRetentionDays,
|
||||
eventRetentionDays,
|
||||
scheduleCleanupCron,
|
||||
eventCleanupCron,
|
||||
scheduleCleanupEnabled,
|
||||
eventCleanupEnabled,
|
||||
logBufferSizeKb: logBufferSizeKb ?? DEFAULT_SETTINGS.logBufferSizeKb,
|
||||
defaultTimezone: defaultTimezone ?? DEFAULT_SETTINGS.defaultTimezone,
|
||||
lightTheme: lightTheme ?? DEFAULT_SETTINGS.lightTheme,
|
||||
darkTheme: darkTheme ?? DEFAULT_SETTINGS.darkTheme,
|
||||
font: font ?? DEFAULT_SETTINGS.font,
|
||||
fontSize: fontSize ?? DEFAULT_SETTINGS.fontSize,
|
||||
gridFontSize: gridFontSize ?? DEFAULT_SETTINGS.gridFontSize,
|
||||
terminalFont: terminalFont ?? DEFAULT_SETTINGS.terminalFont
|
||||
};
|
||||
|
||||
return json(settings);
|
||||
} catch (error) {
|
||||
console.error('Failed to get general settings:', error);
|
||||
return json({ error: 'Failed to get general settings' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
if (auth.authEnabled && !await auth.can('settings', 'edit')) {
|
||||
return json({ error: 'Permission denied' }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { confirmDestructive, showStoppedContainers, highlightUpdates, timeFormat, dateFormat, downloadFormat, defaultGrypeArgs, defaultTrivyArgs, scheduleRetentionDays, eventRetentionDays, scheduleCleanupCron, eventCleanupCron, scheduleCleanupEnabled, eventCleanupEnabled, logBufferSizeKb, defaultTimezone, lightTheme, darkTheme, font, fontSize, gridFontSize, terminalFont } = body;
|
||||
|
||||
if (confirmDestructive !== undefined) {
|
||||
await setSetting('confirm_destructive', confirmDestructive);
|
||||
}
|
||||
if (showStoppedContainers !== undefined) {
|
||||
await setSetting('show_stopped_containers', showStoppedContainers);
|
||||
}
|
||||
if (highlightUpdates !== undefined) {
|
||||
await setSetting('highlight_updates', highlightUpdates);
|
||||
}
|
||||
if (timeFormat !== undefined && (timeFormat === '12h' || timeFormat === '24h')) {
|
||||
await setSetting('time_format', timeFormat);
|
||||
}
|
||||
if (dateFormat !== undefined && VALID_DATE_FORMATS.includes(dateFormat)) {
|
||||
await setSetting('date_format', dateFormat);
|
||||
}
|
||||
if (downloadFormat !== undefined && (downloadFormat === 'tar' || downloadFormat === 'tar.gz')) {
|
||||
await setSetting('download_format', downloadFormat);
|
||||
}
|
||||
if (defaultGrypeArgs !== undefined && typeof defaultGrypeArgs === 'string') {
|
||||
await setSetting('default_grype_args', defaultGrypeArgs);
|
||||
}
|
||||
if (defaultTrivyArgs !== undefined && typeof defaultTrivyArgs === 'string') {
|
||||
await setSetting('default_trivy_args', defaultTrivyArgs);
|
||||
}
|
||||
if (scheduleRetentionDays !== undefined && typeof scheduleRetentionDays === 'number') {
|
||||
await setScheduleRetentionDays(Math.max(1, Math.min(365, scheduleRetentionDays)));
|
||||
}
|
||||
if (eventRetentionDays !== undefined && typeof eventRetentionDays === 'number') {
|
||||
await setEventRetentionDays(Math.max(1, Math.min(365, eventRetentionDays)));
|
||||
}
|
||||
if (scheduleCleanupCron !== undefined && typeof scheduleCleanupCron === 'string') {
|
||||
await setScheduleCleanupCron(scheduleCleanupCron);
|
||||
}
|
||||
if (eventCleanupCron !== undefined && typeof eventCleanupCron === 'string') {
|
||||
await setEventCleanupCron(eventCleanupCron);
|
||||
}
|
||||
if (scheduleCleanupEnabled !== undefined && typeof scheduleCleanupEnabled === 'boolean') {
|
||||
await setScheduleCleanupEnabled(scheduleCleanupEnabled);
|
||||
}
|
||||
if (eventCleanupEnabled !== undefined && typeof eventCleanupEnabled === 'boolean') {
|
||||
await setEventCleanupEnabled(eventCleanupEnabled);
|
||||
}
|
||||
if (logBufferSizeKb !== undefined && typeof logBufferSizeKb === 'number') {
|
||||
// Clamp to reasonable range: 100KB - 5000KB (5MB)
|
||||
await setSetting('log_buffer_size_kb', Math.max(100, Math.min(5000, logBufferSizeKb)));
|
||||
}
|
||||
if (defaultTimezone !== undefined && typeof defaultTimezone === 'string') {
|
||||
await setDefaultTimezone(defaultTimezone);
|
||||
// Refresh system jobs to use the new timezone
|
||||
await refreshSystemJobs();
|
||||
}
|
||||
if (lightTheme !== undefined && VALID_LIGHT_THEMES.includes(lightTheme)) {
|
||||
await setSetting('theme_light', lightTheme);
|
||||
}
|
||||
if (darkTheme !== undefined && VALID_DARK_THEMES.includes(darkTheme)) {
|
||||
await setSetting('theme_dark', darkTheme);
|
||||
}
|
||||
if (font !== undefined && VALID_FONTS.includes(font)) {
|
||||
await setSetting('theme_font', font);
|
||||
}
|
||||
if (fontSize !== undefined && VALID_FONT_SIZES.includes(fontSize)) {
|
||||
await setSetting('theme_font_size', fontSize);
|
||||
}
|
||||
if (gridFontSize !== undefined && VALID_FONT_SIZES.includes(gridFontSize)) {
|
||||
await setSetting('theme_grid_font_size', gridFontSize);
|
||||
}
|
||||
if (terminalFont !== undefined && VALID_TERMINAL_FONTS.includes(terminalFont)) {
|
||||
await setSetting('theme_terminal_font', terminalFont);
|
||||
}
|
||||
|
||||
// Fetch all settings in parallel for the response
|
||||
const [
|
||||
confirmDestructiveVal,
|
||||
showStoppedContainersVal,
|
||||
highlightUpdatesVal,
|
||||
timeFormatVal,
|
||||
dateFormatVal,
|
||||
downloadFormatVal,
|
||||
defaultGrypeArgsVal,
|
||||
defaultTrivyArgsVal,
|
||||
scheduleRetentionDaysVal,
|
||||
eventRetentionDaysVal,
|
||||
scheduleCleanupCronVal,
|
||||
eventCleanupCronVal,
|
||||
scheduleCleanupEnabledVal,
|
||||
eventCleanupEnabledVal,
|
||||
logBufferSizeKbVal,
|
||||
defaultTimezoneVal,
|
||||
lightThemeVal,
|
||||
darkThemeVal,
|
||||
fontVal,
|
||||
fontSizeVal,
|
||||
gridFontSizeVal,
|
||||
terminalFontVal
|
||||
] = await Promise.all([
|
||||
getSetting('confirm_destructive'),
|
||||
getSetting('show_stopped_containers'),
|
||||
getSetting('highlight_updates'),
|
||||
getSetting('time_format'),
|
||||
getSetting('date_format'),
|
||||
getSetting('download_format'),
|
||||
getSetting('default_grype_args'),
|
||||
getSetting('default_trivy_args'),
|
||||
getScheduleRetentionDays(),
|
||||
getEventRetentionDays(),
|
||||
getScheduleCleanupCron(),
|
||||
getEventCleanupCron(),
|
||||
getScheduleCleanupEnabled(),
|
||||
getEventCleanupEnabled(),
|
||||
getSetting('log_buffer_size_kb'),
|
||||
getDefaultTimezone(),
|
||||
getSetting('theme_light'),
|
||||
getSetting('theme_dark'),
|
||||
getSetting('theme_font'),
|
||||
getSetting('theme_font_size'),
|
||||
getSetting('theme_grid_font_size'),
|
||||
getSetting('theme_terminal_font')
|
||||
]);
|
||||
|
||||
const settings: GeneralSettings = {
|
||||
confirmDestructive: confirmDestructiveVal ?? DEFAULT_SETTINGS.confirmDestructive,
|
||||
showStoppedContainers: showStoppedContainersVal ?? DEFAULT_SETTINGS.showStoppedContainers,
|
||||
highlightUpdates: highlightUpdatesVal ?? DEFAULT_SETTINGS.highlightUpdates,
|
||||
timeFormat: timeFormatVal ?? DEFAULT_SETTINGS.timeFormat,
|
||||
dateFormat: dateFormatVal ?? DEFAULT_SETTINGS.dateFormat,
|
||||
downloadFormat: downloadFormatVal ?? DEFAULT_SETTINGS.downloadFormat,
|
||||
defaultGrypeArgs: defaultGrypeArgsVal ?? DEFAULT_SETTINGS.defaultGrypeArgs,
|
||||
defaultTrivyArgs: defaultTrivyArgsVal ?? DEFAULT_SETTINGS.defaultTrivyArgs,
|
||||
scheduleRetentionDays: scheduleRetentionDaysVal,
|
||||
eventRetentionDays: eventRetentionDaysVal,
|
||||
scheduleCleanupCron: scheduleCleanupCronVal,
|
||||
eventCleanupCron: eventCleanupCronVal,
|
||||
scheduleCleanupEnabled: scheduleCleanupEnabledVal,
|
||||
eventCleanupEnabled: eventCleanupEnabledVal,
|
||||
logBufferSizeKb: logBufferSizeKbVal ?? DEFAULT_SETTINGS.logBufferSizeKb,
|
||||
defaultTimezone: defaultTimezoneVal ?? DEFAULT_SETTINGS.defaultTimezone,
|
||||
lightTheme: lightThemeVal ?? DEFAULT_SETTINGS.lightTheme,
|
||||
darkTheme: darkThemeVal ?? DEFAULT_SETTINGS.darkTheme,
|
||||
font: fontVal ?? DEFAULT_SETTINGS.font,
|
||||
fontSize: fontSizeVal ?? DEFAULT_SETTINGS.fontSize,
|
||||
gridFontSize: gridFontSizeVal ?? DEFAULT_SETTINGS.gridFontSize,
|
||||
terminalFont: terminalFontVal ?? DEFAULT_SETTINGS.terminalFont
|
||||
};
|
||||
|
||||
return json(settings);
|
||||
} catch (error) {
|
||||
console.error('Failed to save general settings:', error);
|
||||
return json({ error: 'Failed to save general settings' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
195
routes/api/settings/scanner/+server.ts
Normal file
195
routes/api/settings/scanner/+server.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { json, type RequestHandler } from '@sveltejs/kit';
|
||||
import { getEnvSetting, setEnvSetting, getEnvironment } from '$lib/server/db';
|
||||
import {
|
||||
checkScannerAvailability,
|
||||
getScannerVersions,
|
||||
checkScannerUpdates,
|
||||
cleanupScannerVolumes,
|
||||
getGlobalScannerDefaults,
|
||||
type ScannerType
|
||||
} from '$lib/server/scanner';
|
||||
import { removeImage, listImages } from '$lib/server/docker';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
|
||||
export interface ScannerSettings {
|
||||
scanner: ScannerType;
|
||||
grypeArgs: string;
|
||||
trivyArgs: string;
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
const envId = url.searchParams.get('env');
|
||||
const parsedEnvId = envId ? parseInt(envId) : undefined;
|
||||
const checkUpdates = url.searchParams.get('checkUpdates') === 'true';
|
||||
const settingsOnly = url.searchParams.get('settingsOnly') === 'true';
|
||||
|
||||
// Permission check with environment context
|
||||
if (auth.authEnabled && !await auth.can('settings', 'view', parsedEnvId)) {
|
||||
return json({ error: 'Permission denied' }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// Get global defaults from general settings (used for reset to defaults)
|
||||
const globalDefaults = await getGlobalScannerDefaults();
|
||||
|
||||
// Get environment-specific settings (falls back to global defaults if not set)
|
||||
const settings: ScannerSettings = {
|
||||
scanner: await getEnvSetting('vulnerability_scanner', parsedEnvId) || 'none',
|
||||
grypeArgs: await getEnvSetting('grype_cli_args', parsedEnvId) || globalDefaults.grypeArgs,
|
||||
trivyArgs: await getEnvSetting('trivy_cli_args', parsedEnvId) || globalDefaults.trivyArgs
|
||||
};
|
||||
|
||||
// Fast path: return just settings without Docker checks
|
||||
if (settingsOnly) {
|
||||
return json({
|
||||
settings,
|
||||
defaults: globalDefaults
|
||||
});
|
||||
}
|
||||
|
||||
// Check scanner availability and versions in parallel
|
||||
const [availability, versions] = await Promise.all([
|
||||
checkScannerAvailability(parsedEnvId),
|
||||
getScannerVersions(parsedEnvId)
|
||||
]);
|
||||
|
||||
// Optionally check for updates (slower operation)
|
||||
let updates = undefined;
|
||||
if (checkUpdates) {
|
||||
updates = await checkScannerUpdates(parsedEnvId);
|
||||
}
|
||||
|
||||
return json({
|
||||
settings,
|
||||
availability,
|
||||
versions,
|
||||
updates,
|
||||
defaults: globalDefaults
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get scanner settings:', error);
|
||||
return json({ error: 'Failed to get scanner settings' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export const POST: RequestHandler = async ({ request, url, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { scanner, grypeArgs, trivyArgs, envId } = body;
|
||||
const parsedEnvId = envId ? parseInt(envId) : undefined;
|
||||
|
||||
// Permission check with environment context
|
||||
if (auth.authEnabled && !await auth.can('settings', 'edit', parsedEnvId)) {
|
||||
return json({ error: 'Permission denied' }, { status: 403 });
|
||||
}
|
||||
|
||||
// Validate scanner type
|
||||
const validScanners: ScannerType[] = ['none', 'grype', 'trivy', 'both'];
|
||||
if (scanner && !validScanners.includes(scanner)) {
|
||||
return json({ error: 'Invalid scanner type' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Save environment-specific settings
|
||||
if (scanner !== undefined) {
|
||||
await setEnvSetting('vulnerability_scanner', scanner, parsedEnvId);
|
||||
}
|
||||
if (grypeArgs !== undefined) {
|
||||
await setEnvSetting('grype_cli_args', grypeArgs, parsedEnvId);
|
||||
}
|
||||
if (trivyArgs !== undefined) {
|
||||
await setEnvSetting('trivy_cli_args', trivyArgs, parsedEnvId);
|
||||
}
|
||||
|
||||
// Get global defaults for fallback
|
||||
const globalDefaults = await getGlobalScannerDefaults();
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
settings: {
|
||||
scanner: await getEnvSetting('vulnerability_scanner', parsedEnvId) || 'none',
|
||||
grypeArgs: await getEnvSetting('grype_cli_args', parsedEnvId) || globalDefaults.grypeArgs,
|
||||
trivyArgs: await getEnvSetting('trivy_cli_args', parsedEnvId) || globalDefaults.trivyArgs
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to save scanner settings:', error);
|
||||
return json({ error: 'Failed to save scanner settings' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export const DELETE: RequestHandler = async ({ url, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
const removeImagesFlag = url.searchParams.get('removeImages') === 'true';
|
||||
const scanner = url.searchParams.get('scanner'); // 'grype', 'trivy', or null for both
|
||||
const envId = url.searchParams.get('env');
|
||||
const parsedEnvId = envId ? parseInt(envId) : undefined;
|
||||
|
||||
// Permission check with environment context
|
||||
if (auth.authEnabled && !await auth.can('settings', 'edit', parsedEnvId)) {
|
||||
return json({ error: 'Permission denied' }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (!removeImagesFlag) {
|
||||
return json({ error: 'removeImages parameter required' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!parsedEnvId) {
|
||||
return json({ error: 'Environment ID required' }, { status: 400 });
|
||||
}
|
||||
const env = await getEnvironment(parsedEnvId);
|
||||
if (!env) {
|
||||
return json({ error: 'Environment not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const images = await listImages(parsedEnvId);
|
||||
|
||||
const removed: string[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
// Determine which images to remove
|
||||
const scannersToRemove: ('grype' | 'trivy')[] =
|
||||
scanner === 'grype' ? ['grype'] :
|
||||
scanner === 'trivy' ? ['trivy'] :
|
||||
['grype', 'trivy'];
|
||||
|
||||
for (const scannerType of scannersToRemove) {
|
||||
const imageName = scannerType === 'grype' ? 'anchore/grype' : 'aquasec/trivy';
|
||||
|
||||
// Find the image
|
||||
const image = images.find((img) =>
|
||||
img.tags?.some((tag: string) => tag.includes(imageName))
|
||||
);
|
||||
|
||||
if (image) {
|
||||
try {
|
||||
await removeImage(image.id, true, parsedEnvId);
|
||||
removed.push(scannerType);
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
console.error(`Failed to remove ${scannerType} image:`, err);
|
||||
errors.push(`${scannerType}: ${errMsg}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also cleanup scanner database volumes
|
||||
await cleanupScannerVolumes(parsedEnvId);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
removed,
|
||||
errors: errors.length > 0 ? errors : undefined
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to remove scanner images:', error);
|
||||
return json({ error: 'Failed to remove scanner images' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user