From 1f716c7719ad556b1d32d51f6da4492032d983fd Mon Sep 17 00:00:00 2001 From: chyoo Date: Mon, 6 Jul 2026 18:20:35 +0900 Subject: [PATCH] show woodpecker common apps on dashboard --- .../applications/MigrationManagementPage.tsx | 50 ++++++- src/pages/dashboard/DashboardPage.tsx | 140 +++++++++++++++++- src/pages/operations/UserSessionsPage.tsx | 4 +- src/pages/operations/operation-utils.tsx | 2 +- 4 files changed, 179 insertions(+), 17 deletions(-) diff --git a/src/pages/applications/MigrationManagementPage.tsx b/src/pages/applications/MigrationManagementPage.tsx index 17f8114..b575e70 100644 --- a/src/pages/applications/MigrationManagementPage.tsx +++ b/src/pages/applications/MigrationManagementPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { ChevronLeft, ChevronRight, MoreHorizontal, Play } from 'lucide-react'; import { Button, Card, Modal } from '../../components/ui'; import { useToast } from '../../components/toast'; @@ -42,6 +42,18 @@ function migrationTime(job: RuntimeControlJob) { return Number.isNaN(value) ? 0 : value; } +function sanitizeK8sName(value: string) { + return value + .trim() + .toLowerCase() + .replace(/[^a-z0-9-]+/g, '-') + .replace(/^-+|-+$/g, ''); +} + +function uniqueValues(values: string[]) { + return [...new Set(values.filter(Boolean))]; +} + const migrationPageSize = 5; export function MigrationManagementPage() { @@ -64,6 +76,18 @@ export function MigrationManagementPage() { const selectedRelease = releases.find((release) => release.id === releaseId); const migrationComponents = selectedApp?.components?.filter((component) => component.type === 'MIGRATION') ?? []; const images = imageEntries(selectedRelease?.imageTags); + const namespaceOptions = useMemo( + () => uniqueValues([selectedApp?.namespace || '', namespace]), + [namespace, selectedApp?.namespace] + ); + const envSecretOptions = useMemo( + () => uniqueValues([ + ...images.map(([name]) => `${sanitizeK8sName(name)}-secret`), + `${sanitizeK8sName(selectedApp?.key ?? 'app')}-be-secret`, + envSecretName + ]), + [envSecretName, images, selectedApp?.key] + ); const selectedImage = selectedImageTag(selectedRelease, imageComponentName); const sortedJobs = [...jobs].sort((a, b) => migrationTime(b) - migrationTime(a)); const totalPages = Math.max(1, Math.ceil(sortedJobs.length / migrationPageSize)); @@ -83,7 +107,7 @@ export function MigrationManagementPage() { setMigrationComponentId((prev) => ( migrationComponents.some((component) => component.id === prev) ? prev : migrationComponents[0]?.id || '' )); - setEnvSecretName((prev) => prev || `${selectedApp?.key ?? 'app'}-be-secret`); + setEnvSecretName((prev) => prev || `${sanitizeK8sName(selectedApp?.key ?? 'app')}-be-secret`); } finally { setLoading(false); } @@ -98,10 +122,14 @@ export function MigrationManagementPage() { setImageComponentName((prev) => (nextImages.some(([name]) => name === prev) ? prev : nextImages[0]?.[0] || '')); }, [selectedRelease?.id]); + useEffect(() => { + if (imageComponentName) setEnvSecretName(`${sanitizeK8sName(imageComponentName)}-secret`); + }, [imageComponentName]); + useEffect(() => { setNamespace(selectedApp?.namespace || ''); setMigrationComponentId(migrationComponents[0]?.id || ''); - setEnvSecretName(`${selectedApp?.key ?? 'app'}-be-secret`); + setEnvSecretName(`${sanitizeK8sName(selectedApp?.key ?? 'app')}-be-secret`); setPage(1); }, [selectedAppId]); @@ -117,7 +145,7 @@ export function MigrationManagementPage() { } }; - const canRun = Boolean(releaseId && migrationComponentId && imageComponentName && namespace && command.trim()); + const canRun = Boolean(releaseId && migrationComponentId && imageComponentName && namespace && envSecretName && command.trim()); return ( <> @@ -182,19 +210,25 @@ export function MigrationManagementPage() {