feat(docker-watcher): phase 9 - SvelteKit dashboard & project views
SvelteKit project with Svelte 5, TypeScript, Tailwind CSS v4. Dashboard with project cards, project detail with stage/instance management, deploy history, instance controls. Shared API client and reusable components (StatusBadge, InstanceCard, ProjectCard, ConfirmDialog). Add Phase 14 (Volumes & Environment) to plan.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { api } from '$lib/api';
|
||||
import type { InspectResult, QuickDeployRequest } from '$lib/types';
|
||||
import { inspectImage, quickDeploy } from '$lib/api';
|
||||
import type { InspectResult } from '$lib/types';
|
||||
import FormField from '$lib/components/FormField.svelte';
|
||||
import { toasts } from '$lib/stores/toast';
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
function validateProjectName(value: string): string {
|
||||
if (!value.trim()) return 'Project name is required';
|
||||
if (!/^[a-z0-9][a-z0-9\-]*[a-z0-9]$/.test(value.trim()) && value.trim().length > 1) {
|
||||
if (value.trim().length > 1 && !/^[a-z0-9][a-z0-9\-]*[a-z0-9]$/.test(value.trim())) {
|
||||
return 'Must be lowercase alphanumeric with hyphens (e.g., my-app)';
|
||||
}
|
||||
return '';
|
||||
@@ -55,6 +55,13 @@
|
||||
return Object.keys(newErrors).length === 0;
|
||||
}
|
||||
|
||||
/** Derive a project name from the image URL (last path segment before the colon). */
|
||||
function deriveProjectName(image: string): string {
|
||||
const withoutTag = image.split(':')[0] ?? image;
|
||||
const segments = withoutTag.split('/');
|
||||
return (segments[segments.length - 1] ?? 'unknown').toLowerCase().replace(/[^a-z0-9\-]/g, '-');
|
||||
}
|
||||
|
||||
async function handleInspect() {
|
||||
const urlError = validateImageUrl(imageUrl);
|
||||
if (urlError) {
|
||||
@@ -65,18 +72,16 @@
|
||||
|
||||
inspecting = true;
|
||||
try {
|
||||
const result = await api.post<InspectResult>('/api/deploy/inspect', {
|
||||
image: imageUrl.trim()
|
||||
});
|
||||
const result = await inspectImage(imageUrl.trim());
|
||||
inspectResult = result;
|
||||
|
||||
// Auto-fill form with inspection results
|
||||
projectName = result.project_name ?? '';
|
||||
projectName = deriveProjectName(result.image);
|
||||
port = result.port?.toString() ?? '';
|
||||
healthcheck = result.healthcheck ?? '';
|
||||
stage = 'dev';
|
||||
subdomain = result.suggested_subdomain ?? '';
|
||||
envVars = result.env_vars ? result.env_vars.map((e) => `${e.key}=${e.value}`).join('\n') : '';
|
||||
subdomain = '';
|
||||
envVars = '';
|
||||
inspected = true;
|
||||
toasts.success('Image inspected successfully');
|
||||
} catch (err) {
|
||||
@@ -92,27 +97,11 @@
|
||||
|
||||
deploying = true;
|
||||
try {
|
||||
const envMap: Record<string, string> = {};
|
||||
if (envVars.trim()) {
|
||||
for (const line of envVars.split('\n')) {
|
||||
const eqIndex = line.indexOf('=');
|
||||
if (eqIndex > 0) {
|
||||
envMap[line.substring(0, eqIndex).trim()] = line.substring(eqIndex + 1).trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const payload: QuickDeployRequest = {
|
||||
await quickDeploy({
|
||||
image: imageUrl.trim(),
|
||||
project_name: projectName.trim(),
|
||||
port: parseInt(port, 10),
|
||||
healthcheck: healthcheck.trim() || undefined,
|
||||
stage: stage,
|
||||
subdomain: subdomain.trim() || undefined,
|
||||
env: Object.keys(envMap).length > 0 ? envMap : undefined
|
||||
};
|
||||
|
||||
await api.post('/api/deploy/quick', payload);
|
||||
name: projectName.trim(),
|
||||
port: parseInt(port, 10)
|
||||
});
|
||||
toasts.success(`Deployed ${projectName} successfully!`);
|
||||
|
||||
// Reset form
|
||||
|
||||
Reference in New Issue
Block a user