diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 07fa183..abf4e7a 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -83,7 +83,10 @@ export const snippets = { body: JSON.stringify(payload), }); }, - update(id: string, patch: Partial>): Promise { + update( + id: string, + patch: Partial>, + ): Promise { return request(`/snippets/${id}`, { method: 'PATCH', body: JSON.stringify(patch), @@ -184,7 +187,11 @@ export const scenarios = { waitForRun(scenarioId: string, runId: string): Promise { return request(`/scenarios/${scenarioId}/run/${runId}/wait`, { method: 'POST' }); }, - listRuns(scenarioId: string, page = 1, limit = 20): Promise> { + listRuns( + scenarioId: string, + page = 1, + limit = 20, + ): Promise> { return request(`/scenarios/${scenarioId}/runs?page=${page}&limit=${limit}`); }, exportScenario(id: string): Promise { @@ -201,12 +208,24 @@ export const scenarios = { // ── Runs ───────────────────────────────────────────────────────────────────── export const runs = { - listAll(page = 1, limit = 20, status?: string): Promise> { + listAll( + page = 1, + limit = 20, + status?: string, + ): Promise< + PaginatedResponse< + ScenarioRun & { stepRuns: ScenarioRunStep[]; scenario: { id: string; name: string } } + > + > { const q = new URLSearchParams({ page: String(page), limit: String(limit) }); if (status) q.set('status', status); return request(`/scenarios/runs?${q}`); }, - list(scenarioId: string, page = 1, limit = 20): Promise> { + list( + scenarioId: string, + page = 1, + limit = 20, + ): Promise> { return request(`/scenarios/${scenarioId}/runs?page=${page}&limit=${limit}`); }, get(scenarioId: string, runId: string, q?: string): Promise { @@ -238,7 +257,6 @@ export const scenarioCredentials = { export interface CreateStepPayload { title?: string; order: number; - type: 'login' | 'exec' | 'sign'; execCode?: string; validateCode?: string; } @@ -246,7 +264,6 @@ export interface CreateStepPayload { export interface UpdateStepPayload { title?: string; order?: number; - type?: 'login' | 'exec' | 'sign'; execCode?: string; validateCode?: string; } diff --git a/client/src/api/types.ts b/client/src/api/types.ts index 04f8ff4..f2edcde 100644 --- a/client/src/api/types.ts +++ b/client/src/api/types.ts @@ -68,13 +68,10 @@ export interface Session { // ── Scenarios ───────────────────────────────────────────────────────────────── -export type StepType = 'login' | 'exec' | 'sign'; - export interface ScenarioStep { id: string; scenarioId: string; order: number; - type: StepType; title: string | null; sessionName: string | null; execCode: string | null; diff --git a/client/src/lib/hljs.ts b/client/src/lib/hljs.ts index d8a1cde..e2c4a21 100644 --- a/client/src/lib/hljs.ts +++ b/client/src/lib/hljs.ts @@ -6,7 +6,3 @@ hljs.registerLanguage('javascript', javascript); hljs.registerLanguage('json', json); export default hljs; - -export function escapeHtml(str: string): string { - return str.replace(/&/g, '&').replace(//g, '>'); -} diff --git a/client/src/pages/credential/CredentialDetailPage.tsx b/client/src/pages/credential/CredentialDetailPage.tsx index 9881c68..09b7033 100644 --- a/client/src/pages/credential/CredentialDetailPage.tsx +++ b/client/src/pages/credential/CredentialDetailPage.tsx @@ -6,7 +6,15 @@ import { CodeBlock } from '../../ui'; import { stringify as yamlStringify } from 'yaml'; import { credentials } from '../../api'; import type { Credential } from '../../api'; -import { Breadcrumbs, Button, Card, DescriptionList, Notification, Timestamp, UuidBadge } from '../../ui'; +import { + Breadcrumbs, + Button, + Card, + DescriptionList, + Notification, + Timestamp, + UuidBadge, +} from '../../ui'; import styles from '../Page.module.css'; export function CredentialDetailPage() { @@ -95,11 +103,7 @@ export function CredentialDetailPage() { { term: t('credentials.field_id'), detail: }, { term: t('credentials.field_last_used'), - detail: credential.lastUsedAt ? ( - - ) : ( - '—' - ), + detail: credential.lastUsedAt ? : '—', }, { term: t('credentials.field_created'), diff --git a/client/src/pages/credential/CredentialsPage.tsx b/client/src/pages/credential/CredentialsPage.tsx index 27bb284..72b4f96 100644 --- a/client/src/pages/credential/CredentialsPage.tsx +++ b/client/src/pages/credential/CredentialsPage.tsx @@ -5,7 +5,15 @@ import { Settings, Pencil, Trash2, Plus, Download } from 'lucide-react'; import { parse as yamlParse } from 'yaml'; import { credentials } from '../../api'; import type { Credential } from '../../api'; -import { Breadcrumbs, Button, Card, ContextMenu, DescriptionList, Timestamp, UuidBadge } from '../../ui'; +import { + Breadcrumbs, + Button, + Card, + ContextMenu, + DescriptionList, + Timestamp, + UuidBadge, +} from '../../ui'; import styles from '../Page.module.css'; function CredentialCard({ diff --git a/client/src/pages/environment/EnvironmentsPage.tsx b/client/src/pages/environment/EnvironmentsPage.tsx index 1f12f7f..ab0e7a6 100644 --- a/client/src/pages/environment/EnvironmentsPage.tsx +++ b/client/src/pages/environment/EnvironmentsPage.tsx @@ -4,7 +4,15 @@ import { useTranslation } from 'react-i18next'; import { Settings, ExternalLink, Pencil, Trash2, Plus } from 'lucide-react'; import { environments } from '../../api'; import type { Environment } from '../../api'; -import { Breadcrumbs, Button, Card, ContextMenu, DescriptionList, Timestamp, UuidBadge } from '../../ui'; +import { + Breadcrumbs, + Button, + Card, + ContextMenu, + DescriptionList, + Timestamp, + UuidBadge, +} from '../../ui'; import styles from '../Page.module.css'; function EnvironmentCard({ env, onDelete }: { env: Environment; onDelete: (id: string) => void }) { diff --git a/client/src/pages/run/AllRunsPage.tsx b/client/src/pages/run/AllRunsPage.tsx index d1a5c3a..892c0f3 100644 --- a/client/src/pages/run/AllRunsPage.tsx +++ b/client/src/pages/run/AllRunsPage.tsx @@ -3,7 +3,14 @@ import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { runs } from '../../api'; import type { ScenarioRun, ScenarioRunStep, ScenarioRunStatus } from '../../api'; -import { AutoRefreshIndicator, Badge, Table, Timestamp, UuidBadge, type TableColumn } from '../../ui'; +import { + AutoRefreshIndicator, + Badge, + Table, + Timestamp, + UuidBadge, + type TableColumn, +} from '../../ui'; import type { BadgeVariant } from '../../ui'; import styles from '../Page.module.css'; @@ -37,6 +44,7 @@ export function AllRunsPage() { const pollRef = useRef | null>(null); const load = () => { + setLoading(true); runs .listAll() .then((res) => { @@ -48,7 +56,7 @@ export function AllRunsPage() { }; useEffect(() => { - setLoading(true); + // eslint-disable-next-line react-hooks/set-state-in-effect load(); pollRef.current = setInterval(load, 10_000); return () => { @@ -77,9 +85,7 @@ export function AllRunsPage() { key: 'status', header: t('runs.col_status'), width: 110, - render: (r) => ( - {STATUS_LABEL[r.status]} - ), + render: (r) => {STATUS_LABEL[r.status]}, }, { key: 'steps', diff --git a/client/src/pages/run/RunDetailPage.tsx b/client/src/pages/run/RunDetailPage.tsx index 426ca10..b77a241 100644 --- a/client/src/pages/run/RunDetailPage.tsx +++ b/client/src/pages/run/RunDetailPage.tsx @@ -58,6 +58,8 @@ const LOG_LEVEL_VARIANT: Record = { error: 'error', }; +const FINAL: ScenarioRunStatus[] = ['pass', 'fail']; + export function RunDetailPage() { const { t } = useTranslation(); const { id, runId } = useParams<{ id: string; runId: string }>(); @@ -76,8 +78,6 @@ export function RunDetailPage() { const LOG_PAGE_SIZE = 25; const pollRef = useRef | null>(null); - const FINAL: ScenarioRunStatus[] = ['pass', 'fail']; - const stopPolling = () => { if (pollRef.current) { clearInterval(pollRef.current); @@ -97,8 +97,11 @@ export function RunDetailPage() { useEffect(() => { if (!id || !runId || polling) return; - runs.get(id, runId, debouncedSearch).then(setRun).catch(() => undefined); - }, [debouncedSearch]); + runs + .get(id, runId, debouncedSearch) + .then(setRun) + .catch(() => undefined); + }, [id, runId, polling, debouncedSearch]); useEffect(() => { if (!id || !runId) return; @@ -124,8 +127,12 @@ export function RunDetailPage() { }, 1000); } }) - .catch((err: Error) => { if (!cancelled) setError(err.message); }) - .finally(() => { if (!cancelled) setLoading(false); }); + .catch((err: Error) => { + if (!cancelled) setError(err.message); + }) + .finally(() => { + if (!cancelled) setLoading(false); + }); return () => { cancelled = true; diff --git a/client/src/pages/run/RunsPage.tsx b/client/src/pages/run/RunsPage.tsx index dd3da1d..ca375b8 100644 --- a/client/src/pages/run/RunsPage.tsx +++ b/client/src/pages/run/RunsPage.tsx @@ -4,7 +4,16 @@ import { useTranslation } from 'react-i18next'; import { Play } from 'lucide-react'; import { scenarios, runs } from '../../api'; import type { Scenario, ScenarioRun, ScenarioRunStep, ScenarioRunStatus } from '../../api'; -import { AutoRefreshIndicator, Badge, Breadcrumbs, Button, Table, Timestamp, UuidBadge, type TableColumn } from '../../ui'; +import { + AutoRefreshIndicator, + Badge, + Breadcrumbs, + Button, + Table, + Timestamp, + UuidBadge, + type TableColumn, +} from '../../ui'; import type { BadgeVariant } from '../../ui'; import styles from '../Page.module.css'; @@ -50,6 +59,7 @@ export function RunsPage() { }, [id]); useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect load(); pollRef.current = setInterval(load, 10_000); return () => { @@ -68,9 +78,7 @@ export function RunsPage() { key: 'status', header: t('runs.col_status'), width: 100, - render: (r) => ( - {STATUS_LABEL[r.status]} - ), + render: (r) => {STATUS_LABEL[r.status]}, }, { key: 'steps', diff --git a/client/src/pages/scenario/CreateStepPage.tsx b/client/src/pages/scenario/CreateStepPage.tsx index 12971df..c5b06df 100644 --- a/client/src/pages/scenario/CreateStepPage.tsx +++ b/client/src/pages/scenario/CreateStepPage.tsx @@ -2,13 +2,11 @@ import { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { scenarios, steps } from '../../api'; -import type { Scenario, StepType } from '../../api'; +import type { Scenario } from '../../api'; import type { CreateStepPayload } from '../../api/client'; -import { Breadcrumbs, Button, Card, Input, Select, CodeEditor } from '../../ui'; +import { Breadcrumbs, Button, Card, Input, CodeEditor } from '../../ui'; import styles from '../Page.module.css'; -const STEP_TYPES: StepType[] = ['login', 'exec', 'sign']; - export function CreateStepPage() { const { t } = useTranslation(); const { id } = useParams<{ id: string }>(); @@ -17,7 +15,6 @@ export function CreateStepPage() { const [scenario, setScenario] = useState(null); const [order, setOrder] = useState('0'); const [title, setTitle] = useState(''); - const [type, setType] = useState('exec'); const [execCode, setExecCode] = useState(''); const [validateCode, setValidateCode] = useState(''); const [errors, setErrors] = useState>>({}); @@ -26,7 +23,10 @@ export function CreateStepPage() { useEffect(() => { if (!id) return; - scenarios.get(id).then(setScenario).catch(() => null); + scenarios + .get(id) + .then(setScenario) + .catch(() => null); }, [id]); const validate = (): boolean => { @@ -45,7 +45,6 @@ export function CreateStepPage() { await steps.create(id!, { order: Number(order), title: title.trim() || undefined, - type, execCode: execCode.trim() || undefined, validateCode: validateCode.trim() || undefined, }); @@ -91,32 +90,22 @@ export function CreateStepPage() { value={title} onChange={(e) => setTitle(e.target.value)} /> -