diff --git a/client/src/App.tsx b/client/src/App.tsx index 7a5e208..edf0f54 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,6 +1,6 @@ import { NavLink, Navigate, Route, Routes } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { Globe, KeyRound, Monitor, ClipboardList, Activity, KeySquare } from 'lucide-react'; +import { Globe, KeyRound, Monitor, ClipboardList, Activity, KeySquare, Braces } from 'lucide-react'; import type { LucideIcon } from 'lucide-react'; import styles from './App.module.css'; import { SidePanel, ThemeSwitcher } from './ui'; @@ -25,10 +25,15 @@ import { CredentialsPage } from './pages/credential/CredentialsPage'; import { CreateCredentialPage } from './pages/credential/CreateCredentialPage'; import { EditCredentialPage } from './pages/credential/EditCredentialPage'; import { CredentialDetailPage } from './pages/credential/CredentialDetailPage'; +import { SnippetsPage } from './pages/snippet/SnippetsPage'; +import { CreateSnippetPage } from './pages/snippet/CreateSnippetPage'; +import { EditSnippetPage } from './pages/snippet/EditSnippetPage'; +import { SnippetDetailPage } from './pages/snippet/SnippetDetailPage'; const NAV: { path: string; labelKey: string; Icon: LucideIcon }[] = [ { path: '/environments', labelKey: 'nav.environments', Icon: Globe }, { path: '/credentials', labelKey: 'nav.credentials', Icon: KeySquare }, + { path: '/snippets', labelKey: 'nav.snippets', Icon: Braces }, { path: '/keys', labelKey: 'nav.keys', Icon: KeyRound }, { path: '/sessions', labelKey: 'nav.sessions', Icon: Monitor }, { path: '/scenarios', labelKey: 'nav.scenarios', Icon: ClipboardList }, @@ -87,6 +92,10 @@ export default function App() { } /> } /> } /> + } /> + } /> + } /> + } /> } /> } /> diff --git a/client/src/api/client.ts b/client/src/api/client.ts index ce08573..e3f09bc 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -10,6 +10,7 @@ import type { ScenarioRunDetail, ScenarioRunStep, ScenarioStep, + Snippet, } from './types'; // In dev, Vite proxies /environments /sessions /scenarios /keys to localhost:3000. @@ -58,6 +59,32 @@ export const credentials = { }, }; +// ── Snippets ────────────────────────────────────────────────────────────────── + +export const snippets = { + list(page = 1, limit = 50): Promise> { + return request(`/snippets?page=${page}&limit=${limit}`); + }, + get(id: number): Promise { + return request(`/snippets/${id}`); + }, + create(payload: Pick & { description?: string }): Promise { + return request('/snippets', { + method: 'POST', + body: JSON.stringify(payload), + }); + }, + update(id: number, patch: Partial>): Promise { + return request(`/snippets/${id}`, { + method: 'PATCH', + body: JSON.stringify(patch), + }); + }, + remove(id: number): Promise { + return request(`/snippets/${id}`, { method: 'DELETE' }); + }, +}; + // ── Environments ────────────────────────────────────────────────────────────── export const environments = { @@ -184,7 +211,6 @@ export interface CreateStepPayload { title?: string; order: number; type: 'login' | 'exec' | 'sign'; - sessionName: string; execCode?: string; validateCode?: string; } @@ -193,7 +219,6 @@ export interface UpdateStepPayload { title?: string; order?: number; type?: 'login' | 'exec' | 'sign'; - sessionName?: string; execCode?: string; validateCode?: string; } diff --git a/client/src/api/types.ts b/client/src/api/types.ts index c247e1d..823cd71 100644 --- a/client/src/api/types.ts +++ b/client/src/api/types.ts @@ -35,6 +35,17 @@ export interface Credential { updatedAt: string; } +// ── Snippets ────────────────────────────────────────────────────────────────── + +export interface Snippet { + id: number; + name: string; + description: string | null; + code: string; + createdAt: string; + updatedAt: string; +} + // ── Keys ────────────────────────────────────────────────────────────────────── export interface KeysResponse { @@ -65,7 +76,7 @@ export interface ScenarioStep { order: number; type: StepType; title: string | null; - sessionName: string; + sessionName: string | null; execCode: string | null; validateCode: string | null; createdAt: string; diff --git a/client/src/i18n/locales/en.json b/client/src/i18n/locales/en.json index 39e03db..e4595ff 100644 --- a/client/src/i18n/locales/en.json +++ b/client/src/i18n/locales/en.json @@ -5,11 +5,13 @@ "not_found_environment": "Environment {{id}} does not exist or has been deleted.", "not_found_credential": "Credential {{id}} does not exist or has been deleted.", "not_found_scenario": "Scenario {{id}} does not exist or has been deleted.", + "not_found_snippet": "Snippet {{id}} does not exist or has been deleted.", "generic": "Something went wrong. Please try again." }, "nav": { "environments": "Environments", "credentials": "Credentials", + "snippets": "Snippets", "keys": "Keys", "sessions": "Sessions", "scenarios": "Scenarios", @@ -199,5 +201,33 @@ "log_level": "Level", "log_message": "Message", "log_time": "Time" + }, + "snippets": { + "title": "Snippets", + "empty": "No snippets yet.", + "loading": "Loading…", + "menu_label": "Snippet options", + "action_edit": "Edit", + "action_delete": "Delete", + "action_add": "Add snippet", + "action_save": "Create", + "action_update": "Save changes", + "action_cancel": "Cancel", + "create_title": "New Snippet", + "edit_title": "Edit Snippet", + "form_name": "Name", + "form_name_placeholder": "e.g. clickLoginButton", + "form_name_required": "Name is required", + "form_description": "Description", + "form_description_placeholder": "What does this snippet do?", + "form_code": "Code", + "form_code_placeholder": "await page.click('#login-btn');", + "form_code_required": "Code is required", + "field_id": "ID", + "field_name": "Name", + "field_description": "Description", + "field_created": "Created", + "field_updated": "Updated", + "section_code": "Code" } } diff --git a/client/src/pages/run/RunDetailPage.tsx b/client/src/pages/run/RunDetailPage.tsx index 8ab0c5a..7b2f1f6 100644 --- a/client/src/pages/run/RunDetailPage.tsx +++ b/client/src/pages/run/RunDetailPage.tsx @@ -119,11 +119,6 @@ export function RunDetailPage() { width: 80, render: (s) => {s.scenarioStep?.type ?? '–'}, }, - { - key: 'session', - header: t('runs.step_session'), - render: (s) => s.scenarioStep?.sessionName ?? '–', - }, { key: 'status', header: t('runs.step_status'), diff --git a/client/src/pages/scenario/CreateStepPage.tsx b/client/src/pages/scenario/CreateStepPage.tsx index aebf8ce..64d8f56 100644 --- a/client/src/pages/scenario/CreateStepPage.tsx +++ b/client/src/pages/scenario/CreateStepPage.tsx @@ -18,7 +18,6 @@ export function CreateStepPage() { const [order, setOrder] = useState('0'); const [title, setTitle] = useState(''); const [type, setType] = useState('exec'); - const [sessionName, setSessionName] = useState(''); const [execCode, setExecCode] = useState(''); const [validateCode, setValidateCode] = useState(''); const [errors, setErrors] = useState>>({}); @@ -32,7 +31,6 @@ export function CreateStepPage() { const validate = (): boolean => { const next: typeof errors = {}; - if (!sessionName.trim()) next.sessionName = t('steps.form_session_required'); if (isNaN(Number(order)) || Number(order) < 0) next.order = t('steps.form_order_invalid'); setErrors(next); return Object.keys(next).length === 0; @@ -48,7 +46,6 @@ export function CreateStepPage() { order: Number(order), title: title.trim() || undefined, type, - sessionName: sessionName.trim(), execCode: execCode.trim() || undefined, validateCode: validateCode.trim() || undefined, }); @@ -100,17 +97,6 @@ export function CreateStepPage() { onChange={(e) => setType(e.target.value as StepType)} options={STEP_TYPES.map((v) => ({ value: v, label: v }))} /> - { - setSessionName(e.target.value); - setErrors((prev) => ({ ...prev, sessionName: undefined })); - }} - error={errors.sessionName} - required - />