@burdenoff/microfe-vibecontrols 2026.716.1 → 2026.725.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -158,9 +158,9 @@ function m({ title: m, initialData: h, agents: g, onSubmit: _, onCancel: v, isLo
158
158
  value: "WEBHOOK",
159
159
  children: "Webhook"
160
160
  }),
161
- /* @__PURE__ */ d("option", {
161
+ x.actionType === "WORKFLOW" && /* @__PURE__ */ d("option", {
162
162
  value: "WORKFLOW",
163
- children: "Workflow"
163
+ children: "Workflow (coming soon)"
164
164
  })
165
165
  ]
166
166
  })] }), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("label", {
@@ -1 +1 @@
1
- {"version":3,"file":"ActionForm.js","names":[],"sources":["../../../src/components/actions/ActionForm.tsx"],"sourcesContent":["import { useState, useEffect, useRef, type ReactNode } from 'react';\nimport { X } from 'lucide-react';\nimport { motion, AnimatePresence, useReducedMotion } from 'framer-motion';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { RemoteExecutionEnableNotice } from './RemoteExecutionEnableNotice';\nimport { CommandEditor } from '../common/CommandEditor';\nimport { languageForInterpreter } from '../common/commandHighlight';\n\nexport interface ActionFormData {\n name: string;\n displayName: string;\n description: string;\n actionType: string;\n triggerType: string;\n schedule: string;\n category: string;\n tags: string[];\n command: string;\n workingDirectory: string;\n targetAgentId: string;\n environmentVariables: Record<string, string>;\n script: string;\n scriptInterpreter: string;\n webhookUrl: string;\n webhookMethod: string;\n webhookHeaders: Record<string, string>;\n webhookBody: string;\n webhookAuthType: string;\n timeoutMs: number;\n retryCount: number;\n confirmationRequired: boolean;\n}\n\nconst DEFAULT_FORM: ActionFormData = {\n name: '',\n displayName: '',\n description: '',\n actionType: 'COMMAND',\n triggerType: 'MANUAL',\n schedule: '',\n category: '',\n tags: [],\n command: '',\n workingDirectory: '',\n targetAgentId: '',\n environmentVariables: {},\n script: '',\n scriptInterpreter: 'bash',\n webhookUrl: '',\n webhookMethod: 'POST',\n webhookHeaders: {},\n webhookBody: '',\n webhookAuthType: '',\n timeoutMs: 30000,\n retryCount: 0,\n confirmationRequired: false,\n};\n\ninterface ActionFormProps {\n title: string;\n initialData?: Partial<ActionFormData>;\n agents: Array<{ id: string; name: string; isActive: boolean }>;\n onSubmit: (data: ActionFormData) => void;\n onCancel: () => void;\n isLoading?: boolean;\n /** Optional extra content rendered below the form fields (e.g. tag picker in edit mode) */\n extraSection?: ReactNode;\n}\n\nexport function ActionForm({\n title,\n initialData,\n agents,\n onSubmit,\n onCancel,\n isLoading = false,\n extraSection,\n}: ActionFormProps) {\n const [form, setForm] = useState<ActionFormData>({ ...DEFAULT_FORM, ...initialData });\n const [tagsInput, setTagsInput] = useState(initialData?.tags?.join(', ') ?? '');\n const dialogRef = useRef<HTMLDialogElement>(null);\n const shouldReduceMotion = useReducedMotion();\n const tr = useTr();\n\n useEffect(() => {\n dialogRef.current?.showModal();\n }, []);\n\n const update = <K extends keyof ActionFormData>(key: K, value: ActionFormData[K]) =>\n setForm((prev) => ({ ...prev, [key]: value }));\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const tags = tagsInput\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n onSubmit({ ...form, tags });\n };\n\n const inputClass =\n 'w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg focus:border-transparent transition-all';\n const labelClass = 'block text-sm font-medium text-text-secondary mb-1';\n\n return (\n <dialog\n ref={dialogRef}\n className=\"fixed inset-0 z-50 m-auto p-0 bg-transparent backdrop:bg-transparent\"\n onClose={onCancel}\n >\n <AnimatePresence>\n <motion.div\n className=\"fixed inset-0 bg-bg-overlay\"\n initial={shouldReduceMotion ? undefined : { opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n onClick={onCancel}\n />\n <motion.div\n className=\"relative w-full max-w-2xl max-h-[85vh] overflow-y-auto bg-bg-surface rounded-lg shadow-xl\"\n initial={shouldReduceMotion ? undefined : { opacity: 0, scale: 0.95, y: 8 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: 8 }}\n transition={{ duration: 0.2, ease: [0.2, 0, 0, 1] }}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between p-6 border-b border-border-default sticky top-0 bg-bg-surface z-10\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-5 text-text-secondary\" />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-6 space-y-4\">\n {/* Basic fields */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.nameSlug', 'Name (slug)')}\n </label>\n <input\n aria-label=\"my-action\"\n className={inputClass}\n value={form.name}\n onChange={(e) => update('name', e.target.value)}\n placeholder=\"my-action\"\n required\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.displayName', 'Display Name')}\n </label>\n <input\n aria-label=\"My Action\"\n className={inputClass}\n value={form.displayName}\n onChange={(e) => update('displayName', e.target.value)}\n placeholder=\"My Action\"\n required\n />\n </div>\n </div>\n\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.description', 'Description')}\n </label>\n <textarea\n aria-label=\"What does this action do?\"\n className={`${inputClass} resize-none`}\n rows={2}\n value={form.description}\n onChange={(e) => update('description', e.target.value)}\n placeholder=\"What does this action do?\"\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.actionType', 'Action Type')}\n </label>\n <select\n className={inputClass}\n value={form.actionType}\n onChange={(e) => update('actionType', e.target.value)}\n >\n <option value=\"COMMAND\">Command</option>\n <option value=\"SCRIPT\">Script</option>\n <option value=\"WEBHOOK\">Webhook</option>\n <option value=\"WORKFLOW\">Workflow</option>\n </select>\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.triggerType', 'Trigger Type')}\n </label>\n <select\n className={inputClass}\n value={form.triggerType}\n onChange={(e) => update('triggerType', e.target.value)}\n >\n <option value=\"MANUAL\">Manual</option>\n <option value=\"SCHEDULED\">Scheduled</option>\n <option value=\"EVENT\">Event</option>\n <option value=\"ON_DEMAND\">On Demand</option>\n </select>\n </div>\n </div>\n\n {form.triggerType === 'SCHEDULED' && (\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.schedule', 'Schedule (cron expression)')}\n </label>\n <input\n aria-label=\"*/5 * * * *\"\n className={inputClass}\n value={form.schedule}\n onChange={(e) => update('schedule', e.target.value)}\n placeholder=\"*/5 * * * *\"\n />\n </div>\n )}\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.category', 'Category')}\n </label>\n <input\n aria-label=\"deployment, testing, etc.\"\n className={inputClass}\n value={form.category}\n onChange={(e) => update('category', e.target.value)}\n placeholder=\"deployment, testing, etc.\"\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.tags', 'Tags (comma separated)')}\n </label>\n <input\n aria-label=\"ci, deploy, infra\"\n className={inputClass}\n value={tagsInput}\n onChange={(e) => setTagsInput(e.target.value)}\n placeholder=\"ci, deploy, infra\"\n />\n </div>\n </div>\n\n {/* Type-specific fields */}\n {form.actionType === 'COMMAND' && (\n <div className=\"space-y-4 p-4 rounded-lg bg-bg-sunken/50 border border-border-default\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.actions.form.commandSettings', 'Command Settings')}\n </h3>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.command', 'Command')}\n </label>\n <CommandEditor\n aria-label=\"echo 'Hello World'\"\n language=\"shell\"\n rows={3}\n value={form.command}\n onChange={(e) => update('command', e.target.value)}\n placeholder=\"echo 'Hello World'\"\n required\n />\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.workingDirectory', 'Working Directory')}\n </label>\n <input\n aria-label=\"/home/user/project\"\n className={inputClass}\n value={form.workingDirectory}\n onChange={(e) => update('workingDirectory', e.target.value)}\n placeholder=\"/home/user/project\"\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.targetAgent', 'Target Agent')}\n </label>\n <select\n className={inputClass}\n value={form.targetAgentId}\n onChange={(e) => update('targetAgentId', e.target.value)}\n >\n <option value=\"\">\n {tr('vibecontrols.actions.form.anyAvailableAgent', 'Any available agent')}\n </option>\n {agents.map((a) => (\n <option key={a.id} value={a.id} disabled={!a.isActive}>\n {a.name}{' '}\n {!a.isActive ? tr('vibecontrols.actions.form.offline', '(offline)') : ''}\n </option>\n ))}\n </select>\n {form.targetAgentId ? (\n <RemoteExecutionEnableNotice\n agentId={form.targetAgentId}\n agentName={agents.find((a) => a.id === form.targetAgentId)?.name}\n />\n ) : (\n <p className=\"mt-1 text-xs text-text-tertiary\">\n {tr(\n 'vibecontrols.actions.form.anyAgentHint',\n 'A healthy agent is picked automatically when this action runs.'\n )}\n </p>\n )}\n </div>\n </div>\n </div>\n )}\n\n {form.actionType === 'SCRIPT' && (\n <div className=\"space-y-4 p-4 rounded-lg bg-bg-sunken/50 border border-border-default\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.actions.form.scriptSettings', 'Script Settings')}\n </h3>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.script', 'Script')}\n </label>\n <CommandEditor\n aria-label=\"#!/bin/bash&#10;echo 'Hello'\"\n language={languageForInterpreter(form.scriptInterpreter)}\n rows={6}\n value={form.script}\n onChange={(e) => update('script', e.target.value)}\n placeholder=\"#!/bin/bash&#10;echo 'Hello'\"\n required\n />\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.interpreter', 'Interpreter')}\n </label>\n <select\n className={inputClass}\n value={form.scriptInterpreter}\n onChange={(e) => update('scriptInterpreter', e.target.value)}\n >\n <option value=\"bash\">Bash</option>\n <option value=\"python\">Python</option>\n <option value=\"node\">Node.js</option>\n </select>\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.targetAgent', 'Target Agent')}\n </label>\n <select\n className={inputClass}\n value={form.targetAgentId}\n onChange={(e) => update('targetAgentId', e.target.value)}\n >\n <option value=\"\">\n {tr('vibecontrols.actions.form.anyAvailableAgent', 'Any available agent')}\n </option>\n {agents.map((a) => (\n <option key={a.id} value={a.id} disabled={!a.isActive}>\n {a.name}{' '}\n {!a.isActive ? tr('vibecontrols.actions.form.offline', '(offline)') : ''}\n </option>\n ))}\n </select>\n {form.targetAgentId ? (\n <RemoteExecutionEnableNotice\n agentId={form.targetAgentId}\n agentName={agents.find((a) => a.id === form.targetAgentId)?.name}\n />\n ) : (\n <p className=\"mt-1 text-xs text-text-tertiary\">\n {tr(\n 'vibecontrols.actions.form.anyAgentHint',\n 'A healthy agent is picked automatically when this action runs.'\n )}\n </p>\n )}\n </div>\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.workingDirectory', 'Working Directory')}\n </label>\n <input\n aria-label=\"/home/user/project\"\n className={inputClass}\n value={form.workingDirectory}\n onChange={(e) => update('workingDirectory', e.target.value)}\n placeholder=\"/home/user/project\"\n />\n </div>\n </div>\n )}\n\n {form.actionType === 'WEBHOOK' && (\n <div className=\"space-y-4 p-4 rounded-lg bg-bg-sunken/50 border border-border-default\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.actions.form.webhookSettings', 'Webhook Settings')}\n </h3>\n <div className=\"grid grid-cols-[1fr_auto] gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.url', 'URL')}\n </label>\n <input\n aria-label=\"https://api.example.com/hook\"\n className={inputClass}\n value={form.webhookUrl}\n onChange={(e) => update('webhookUrl', e.target.value)}\n placeholder=\"https://api.example.com/hook\"\n required\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.method', 'Method')}\n </label>\n <select\n className={inputClass}\n value={form.webhookMethod}\n onChange={(e) => update('webhookMethod', e.target.value)}\n >\n <option value=\"GET\">GET</option>\n <option value=\"POST\">POST</option>\n <option value=\"PUT\">PUT</option>\n <option value=\"PATCH\">PATCH</option>\n <option value=\"DELETE\">DELETE</option>\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.bodyJson', 'Body (JSON)')}\n </label>\n <CommandEditor\n aria-label=\"control\"\n language=\"json\"\n rows={4}\n value={form.webhookBody}\n onChange={(e) => update('webhookBody', e.target.value)}\n placeholder='{\"key\": \"value\"}'\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.authType', 'Auth Type')}\n </label>\n <select\n className={inputClass}\n value={form.webhookAuthType}\n onChange={(e) => update('webhookAuthType', e.target.value)}\n >\n <option value=\"\">None</option>\n <option value=\"BEARER\">Bearer Token</option>\n <option value=\"BASIC\">Basic Auth</option>\n <option value=\"API_KEY\">API Key</option>\n </select>\n </div>\n </div>\n )}\n\n {/* Options */}\n <div className=\"grid grid-cols-3 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.timeout', 'Timeout (ms)')}\n </label>\n <input\n aria-label=\"control\"\n className={inputClass}\n type=\"number\"\n min={1000}\n max={300000}\n value={form.timeoutMs}\n onChange={(e) => update('timeoutMs', parseInt(e.target.value) || 30000)}\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.retryCount', 'Retry Count')}\n </label>\n <input\n aria-label=\"control\"\n className={inputClass}\n type=\"number\"\n min={0}\n max={10}\n value={form.retryCount}\n onChange={(e) => update('retryCount', parseInt(e.target.value) || 0)}\n />\n </div>\n <div className=\"flex items-end pb-2\">\n <label className=\"flex items-center gap-2 text-sm text-text-secondary cursor-pointer\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={form.confirmationRequired}\n onChange={(e) => update('confirmationRequired', e.target.checked)}\n className=\"rounded border-border-default\"\n />\n {tr('vibecontrols.actions.form.requireConfirmation', 'Require confirmation')}\n </label>\n </div>\n </div>\n\n {extraSection && (\n <div className=\"pt-4 border-t border-border-default\">{extraSection}</div>\n )}\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 pt-4 border-t border-border-default\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated transition-colors disabled:opacity-50\"\n >\n {tr('vibecontrols.actions.form.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {isLoading\n ? tr('vibecontrols.actions.form.saving', 'Saving...')\n : initialData\n ? tr('vibecontrols.actions.form.updateAction', 'Update Action')\n : tr('vibecontrols.actions.form.createAction', 'Create Action')}\n </button>\n </div>\n </form>\n </motion.div>\n </AnimatePresence>\n </dialog>\n );\n}\n"],"mappings":";;;;;;;;;AAiCA,IAAM,IAA+B;CACnC,MAAM;CACN,aAAa;CACb,aAAa;CACb,YAAY;CACZ,aAAa;CACb,UAAU;CACV,UAAU;CACV,MAAM,EAAE;CACR,SAAS;CACT,kBAAkB;CAClB,eAAe;CACf,sBAAsB,EAAE;CACxB,QAAQ;CACR,mBAAmB;CACnB,YAAY;CACZ,eAAe;CACf,gBAAgB,EAAE;CAClB,aAAa;CACb,iBAAiB;CACjB,WAAW;CACX,YAAY;CACZ,sBAAsB;CACvB;AAaD,SAAgB,EAAW,EACzB,UACA,gBACA,WACA,aACA,aACA,eAAY,IACZ,mBACkB;CAClB,IAAM,CAAC,GAAM,KAAW,EAAyB;EAAE,GAAG;EAAc,GAAG;EAAa,CAAC,EAC/E,CAAC,GAAW,KAAgB,EAAS,GAAa,MAAM,KAAK,KAAK,IAAI,GAAG,EACzE,IAAY,EAA0B,KAAK,EAC3C,IAAqB,GAAkB,EACvC,IAAK,GAAO;AAElB,SAAgB;AACd,IAAU,SAAS,WAAW;IAC7B,EAAE,CAAC;CAEN,IAAM,KAA0C,GAAQ,MACtD,GAAS,OAAU;EAAE,GAAG;GAAO,IAAM;EAAO,EAAE,EAE1C,KAAgB,MAAuB;AAC3C,IAAE,gBAAgB;EAClB,IAAM,IAAO,EACV,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ;AAClB,IAAS;GAAE,GAAG;GAAM;GAAM,CAAC;IAGvB,IACJ,0OACI,IAAa;AAEnB,QACE,kBAAC,UAAD;EACE,KAAK;EACL,WAAU;EACV,SAAS;YAET,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,EAAO,KAAR;GACE,WAAU;GACV,SAAS,IAAqB,KAAA,IAAY,EAAE,SAAS,GAAG;GACxD,SAAS,EAAE,SAAS,GAAG;GACvB,MAAM,EAAE,SAAS,GAAG;GACpB,SAAS;GACT,CAAA,EACF,kBAAC,EAAO,KAAR;GACE,WAAU;GACV,SAAS,IAAqB,KAAA,IAAY;IAAE,SAAS;IAAG,OAAO;IAAM,GAAG;IAAG;GAC3E,SAAS;IAAE,SAAS;IAAG,OAAO;IAAG,GAAG;IAAG;GACvC,MAAM;IAAE,SAAS;IAAG,OAAO;IAAM,GAAG;IAAG;GACvC,YAAY;IAAE,UAAU;IAAK,MAAM;KAAC;KAAK;KAAG;KAAG;KAAE;IAAE;aALrD,CAQE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA2C;KAAW,CAAA,EACpE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAEV,kBAAC,GAAD,EAAG,WAAU,8BAA+B,CAAA;KACrC,CAAA,CACL;OAEN,kBAAC,QAAD;IAAM,UAAU;IAAc,WAAU;cAAxC;KAEE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAW;iBACf,EAAG,sCAAsC,cAAc;OAClD,CAAA,EACR,kBAAC,SAAD;OACE,cAAW;OACX,WAAW;OACX,OAAO,EAAK;OACZ,WAAW,MAAM,EAAO,QAAQ,EAAE,OAAO,MAAM;OAC/C,aAAY;OACZ,UAAA;OACA,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAW;iBACf,EAAG,yCAAyC,eAAe;OACtD,CAAA,EACR,kBAAC,SAAD;OACE,cAAW;OACX,WAAW;OACX,OAAO,EAAK;OACZ,WAAW,MAAM,EAAO,eAAe,EAAE,OAAO,MAAM;OACtD,aAAY;OACZ,UAAA;OACA,CAAA,CACE,EAAA,CAAA,CACF;;KAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAW;gBACf,EAAG,yCAAyC,cAAc;MACrD,CAAA,EACR,kBAAC,YAAD;MACE,cAAW;MACX,WAAW,GAAG,EAAW;MACzB,MAAM;MACN,OAAO,EAAK;MACZ,WAAW,MAAM,EAAO,eAAe,EAAE,OAAO,MAAM;MACtD,aAAY;MACZ,CAAA,CACE,EAAA,CAAA;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAW;iBACf,EAAG,wCAAwC,cAAc;OACpD,CAAA,EACR,kBAAC,UAAD;OACE,WAAW;OACX,OAAO,EAAK;OACZ,WAAW,MAAM,EAAO,cAAc,EAAE,OAAO,MAAM;iBAHvD;QAKE,kBAAC,UAAD;SAAQ,OAAM;mBAAU;SAAgB,CAAA;QACxC,kBAAC,UAAD;SAAQ,OAAM;mBAAS;SAAe,CAAA;QACtC,kBAAC,UAAD;SAAQ,OAAM;mBAAU;SAAgB,CAAA;QACxC,kBAAC,UAAD;SAAQ,OAAM;mBAAW;SAAiB,CAAA;QACnC;SACL,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAW;iBACf,EAAG,yCAAyC,eAAe;OACtD,CAAA,EACR,kBAAC,UAAD;OACE,WAAW;OACX,OAAO,EAAK;OACZ,WAAW,MAAM,EAAO,eAAe,EAAE,OAAO,MAAM;iBAHxD;QAKE,kBAAC,UAAD;SAAQ,OAAM;mBAAS;SAAe,CAAA;QACtC,kBAAC,UAAD;SAAQ,OAAM;mBAAY;SAAkB,CAAA;QAC5C,kBAAC,UAAD;SAAQ,OAAM;mBAAQ;SAAc,CAAA;QACpC,kBAAC,UAAD;SAAQ,OAAM;mBAAY;SAAkB,CAAA;QACrC;SACL,EAAA,CAAA,CACF;;KAEL,EAAK,gBAAgB,eACpB,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,WAAW;gBACf,EAAG,sCAAsC,6BAA6B;MACjE,CAAA,EACR,kBAAC,SAAD;MACE,cAAW;MACX,WAAW;MACX,OAAO,EAAK;MACZ,WAAW,MAAM,EAAO,YAAY,EAAE,OAAO,MAAM;MACnD,aAAY;MACZ,CAAA,CACE,EAAA,CAAA;KAGR,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAW;iBACf,EAAG,sCAAsC,WAAW;OAC/C,CAAA,EACR,kBAAC,SAAD;OACE,cAAW;OACX,WAAW;OACX,OAAO,EAAK;OACZ,WAAW,MAAM,EAAO,YAAY,EAAE,OAAO,MAAM;OACnD,aAAY;OACZ,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAW;iBACf,EAAG,kCAAkC,yBAAyB;OACzD,CAAA,EACR,kBAAC,SAAD;OACE,cAAW;OACX,WAAW;OACX,OAAO;OACP,WAAW,MAAM,EAAa,EAAE,OAAO,MAAM;OAC7C,aAAY;OACZ,CAAA,CACE,EAAA,CAAA,CACF;;KAGL,EAAK,eAAe,aACnB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,6CAA6C,mBAAmB;QACjE,CAAA;OACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAW;kBACf,EAAG,qCAAqC,UAAU;QAC7C,CAAA,EACR,kBAAC,GAAD;QACE,cAAW;QACX,UAAS;QACT,MAAM;QACN,OAAO,EAAK;QACZ,WAAW,MAAM,EAAO,WAAW,EAAE,OAAO,MAAM;QAClD,aAAY;QACZ,UAAA;QACA,CAAA,CACE,EAAA,CAAA;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAW;mBACf,EAAG,8CAA8C,oBAAoB;SAChE,CAAA,EACR,kBAAC,SAAD;SACE,cAAW;SACX,WAAW;SACX,OAAO,EAAK;SACZ,WAAW,MAAM,EAAO,oBAAoB,EAAE,OAAO,MAAM;SAC3D,aAAY;SACZ,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA;SACE,kBAAC,SAAD;UAAO,WAAW;oBACf,EAAG,yCAAyC,eAAe;UACtD,CAAA;SACR,kBAAC,UAAD;UACE,WAAW;UACX,OAAO,EAAK;UACZ,WAAW,MAAM,EAAO,iBAAiB,EAAE,OAAO,MAAM;oBAH1D,CAKE,kBAAC,UAAD;WAAQ,OAAM;qBACX,EAAG,+CAA+C,sBAAsB;WAClE,CAAA,EACR,EAAO,KAAK,MACX,kBAAC,UAAD;WAAmB,OAAO,EAAE;WAAI,UAAU,CAAC,EAAE;qBAA7C;YACG,EAAE;YAAM;YACP,EAAE,WAAkE,KAAvD,EAAG,qCAAqC,YAAY;YAC5D;aAHI,EAAE,GAGN,CACT,CACK;;SACR,EAAK,gBACJ,kBAAC,GAAD;UACE,SAAS,EAAK;UACd,WAAW,EAAO,MAAM,MAAM,EAAE,OAAO,EAAK,cAAc,EAAE;UAC5D,CAAA,GAEF,kBAAC,KAAD;UAAG,WAAU;oBACV,EACC,0CACA,iEACD;UACC,CAAA;SAEF,EAAA,CAAA,CACF;;OACF;;KAGP,EAAK,eAAe,YACnB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,4CAA4C,kBAAkB;QAC/D,CAAA;OACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAW;kBACf,EAAG,oCAAoC,SAAS;QAC3C,CAAA,EACR,kBAAC,GAAD;QACE,cAAW;QACX,UAAU,EAAuB,EAAK,kBAAkB;QACxD,MAAM;QACN,OAAO,EAAK;QACZ,WAAW,MAAM,EAAO,UAAU,EAAE,OAAO,MAAM;QACjD,aAAY;QACZ,UAAA;QACA,CAAA,CACE,EAAA,CAAA;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAW;mBACf,EAAG,yCAAyC,cAAc;SACrD,CAAA,EACR,kBAAC,UAAD;SACE,WAAW;SACX,OAAO,EAAK;SACZ,WAAW,MAAM,EAAO,qBAAqB,EAAE,OAAO,MAAM;mBAH9D;UAKE,kBAAC,UAAD;WAAQ,OAAM;qBAAO;WAAa,CAAA;UAClC,kBAAC,UAAD;WAAQ,OAAM;qBAAS;WAAe,CAAA;UACtC,kBAAC,UAAD;WAAQ,OAAM;qBAAO;WAAgB,CAAA;UAC9B;WACL,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA;SACE,kBAAC,SAAD;UAAO,WAAW;oBACf,EAAG,yCAAyC,eAAe;UACtD,CAAA;SACR,kBAAC,UAAD;UACE,WAAW;UACX,OAAO,EAAK;UACZ,WAAW,MAAM,EAAO,iBAAiB,EAAE,OAAO,MAAM;oBAH1D,CAKE,kBAAC,UAAD;WAAQ,OAAM;qBACX,EAAG,+CAA+C,sBAAsB;WAClE,CAAA,EACR,EAAO,KAAK,MACX,kBAAC,UAAD;WAAmB,OAAO,EAAE;WAAI,UAAU,CAAC,EAAE;qBAA7C;YACG,EAAE;YAAM;YACP,EAAE,WAAkE,KAAvD,EAAG,qCAAqC,YAAY;YAC5D;aAHI,EAAE,GAGN,CACT,CACK;;SACR,EAAK,gBACJ,kBAAC,GAAD;UACE,SAAS,EAAK;UACd,WAAW,EAAO,MAAM,MAAM,EAAE,OAAO,EAAK,cAAc,EAAE;UAC5D,CAAA,GAEF,kBAAC,KAAD;UAAG,WAAU;oBACV,EACC,0CACA,iEACD;UACC,CAAA;SAEF,EAAA,CAAA,CACF;;OACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAW;kBACf,EAAG,8CAA8C,oBAAoB;QAChE,CAAA,EACR,kBAAC,SAAD;QACE,cAAW;QACX,WAAW;QACX,OAAO,EAAK;QACZ,WAAW,MAAM,EAAO,oBAAoB,EAAE,OAAO,MAAM;QAC3D,aAAY;QACZ,CAAA,CACE,EAAA,CAAA;OACF;;KAGP,EAAK,eAAe,aACnB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,6CAA6C,mBAAmB;QACjE,CAAA;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAW;mBACf,EAAG,iCAAiC,MAAM;SACrC,CAAA,EACR,kBAAC,SAAD;SACE,cAAW;SACX,WAAW;SACX,OAAO,EAAK;SACZ,WAAW,MAAM,EAAO,cAAc,EAAE,OAAO,MAAM;SACrD,aAAY;SACZ,UAAA;SACA,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,WAAW;mBACf,EAAG,oCAAoC,SAAS;SAC3C,CAAA,EACR,kBAAC,UAAD;SACE,WAAW;SACX,OAAO,EAAK;SACZ,WAAW,MAAM,EAAO,iBAAiB,EAAE,OAAO,MAAM;mBAH1D;UAKE,kBAAC,UAAD;WAAQ,OAAM;qBAAM;WAAY,CAAA;UAChC,kBAAC,UAAD;WAAQ,OAAM;qBAAO;WAAa,CAAA;UAClC,kBAAC,UAAD;WAAQ,OAAM;qBAAM;WAAY,CAAA;UAChC,kBAAC,UAAD;WAAQ,OAAM;qBAAQ;WAAc,CAAA;UACpC,kBAAC,UAAD;WAAQ,OAAM;qBAAS;WAAe,CAAA;UAC/B;WACL,EAAA,CAAA,CACF;;OACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAW;kBACf,EAAG,sCAAsC,cAAc;QAClD,CAAA,EACR,kBAAC,GAAD;QACE,cAAW;QACX,UAAS;QACT,MAAM;QACN,OAAO,EAAK;QACZ,WAAW,MAAM,EAAO,eAAe,EAAE,OAAO,MAAM;QACtD,aAAY;QACZ,CAAA,CACE,EAAA,CAAA;OACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAW;kBACf,EAAG,sCAAsC,YAAY;QAChD,CAAA,EACR,kBAAC,UAAD;QACE,WAAW;QACX,OAAO,EAAK;QACZ,WAAW,MAAM,EAAO,mBAAmB,EAAE,OAAO,MAAM;kBAH5D;SAKE,kBAAC,UAAD;UAAQ,OAAM;oBAAG;UAAa,CAAA;SAC9B,kBAAC,UAAD;UAAQ,OAAM;oBAAS;UAAqB,CAAA;SAC5C,kBAAC,UAAD;UAAQ,OAAM;oBAAQ;UAAmB,CAAA;SACzC,kBAAC,UAAD;UAAQ,OAAM;oBAAU;UAAgB,CAAA;SACjC;UACL,EAAA,CAAA;OACF;;KAIR,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAW;kBACf,EAAG,qCAAqC,eAAe;QAClD,CAAA,EACR,kBAAC,SAAD;QACE,cAAW;QACX,WAAW;QACX,MAAK;QACL,KAAK;QACL,KAAK;QACL,OAAO,EAAK;QACZ,WAAW,MAAM,EAAO,aAAa,SAAS,EAAE,OAAO,MAAM,IAAI,IAAM;QACvE,CAAA,CACE,EAAA,CAAA;OACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAW;kBACf,EAAG,wCAAwC,cAAc;QACpD,CAAA,EACR,kBAAC,SAAD;QACE,cAAW;QACX,WAAW;QACX,MAAK;QACL,KAAK;QACL,KAAK;QACL,OAAO,EAAK;QACZ,WAAW,MAAM,EAAO,cAAc,SAAS,EAAE,OAAO,MAAM,IAAI,EAAE;QACpE,CAAA,CACE,EAAA,CAAA;OACN,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,SAAD;SAAO,WAAU;mBAAjB,CACE,kBAAC,SAAD;UACE,cAAW;UACX,MAAK;UACL,SAAS,EAAK;UACd,WAAW,MAAM,EAAO,wBAAwB,EAAE,OAAO,QAAQ;UACjE,WAAU;UACV,CAAA,EACD,EAAG,iDAAiD,uBAAuB,CACtE;;QACJ,CAAA;OACF;;KAEL,KACC,kBAAC,OAAD;MAAK,WAAU;gBAAuC;MAAmB,CAAA;KAI3E,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,UAAU;OACV,WAAU;iBAET,EAAG,oCAAoC,SAAS;OAC1C,CAAA,EACT,kBAAC,UAAD;OACE,MAAK;OACL,UAAU;OACV,WAAU;iBAET,IACG,EAAG,oCAAoC,YAAY,GACnD,IACE,EAAG,0CAA0C,gBAAgB,GAC7D,EAAG,0CAA0C,gBAAgB;OAC5D,CAAA,CACL;;KACD;MACI;KACG,EAAA,CAAA;EACX,CAAA"}
1
+ {"version":3,"file":"ActionForm.js","names":[],"sources":["../../../src/components/actions/ActionForm.tsx"],"sourcesContent":["import { useState, useEffect, useRef, type ReactNode } from 'react';\nimport { X } from 'lucide-react';\nimport { motion, AnimatePresence, useReducedMotion } from 'framer-motion';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { RemoteExecutionEnableNotice } from './RemoteExecutionEnableNotice';\nimport { CommandEditor } from '../common/CommandEditor';\nimport { languageForInterpreter } from '../common/commandHighlight';\n\nexport interface ActionFormData {\n name: string;\n displayName: string;\n description: string;\n actionType: string;\n triggerType: string;\n schedule: string;\n category: string;\n tags: string[];\n command: string;\n workingDirectory: string;\n targetAgentId: string;\n environmentVariables: Record<string, string>;\n script: string;\n scriptInterpreter: string;\n webhookUrl: string;\n webhookMethod: string;\n webhookHeaders: Record<string, string>;\n webhookBody: string;\n webhookAuthType: string;\n timeoutMs: number;\n retryCount: number;\n confirmationRequired: boolean;\n}\n\nconst DEFAULT_FORM: ActionFormData = {\n name: '',\n displayName: '',\n description: '',\n actionType: 'COMMAND',\n triggerType: 'MANUAL',\n schedule: '',\n category: '',\n tags: [],\n command: '',\n workingDirectory: '',\n targetAgentId: '',\n environmentVariables: {},\n script: '',\n scriptInterpreter: 'bash',\n webhookUrl: '',\n webhookMethod: 'POST',\n webhookHeaders: {},\n webhookBody: '',\n webhookAuthType: '',\n timeoutMs: 30000,\n retryCount: 0,\n confirmationRequired: false,\n};\n\ninterface ActionFormProps {\n title: string;\n initialData?: Partial<ActionFormData>;\n agents: Array<{ id: string; name: string; isActive: boolean }>;\n onSubmit: (data: ActionFormData) => void;\n onCancel: () => void;\n isLoading?: boolean;\n /** Optional extra content rendered below the form fields (e.g. tag picker in edit mode) */\n extraSection?: ReactNode;\n}\n\nexport function ActionForm({\n title,\n initialData,\n agents,\n onSubmit,\n onCancel,\n isLoading = false,\n extraSection,\n}: ActionFormProps) {\n const [form, setForm] = useState<ActionFormData>({ ...DEFAULT_FORM, ...initialData });\n const [tagsInput, setTagsInput] = useState(initialData?.tags?.join(', ') ?? '');\n const dialogRef = useRef<HTMLDialogElement>(null);\n const shouldReduceMotion = useReducedMotion();\n const tr = useTr();\n\n useEffect(() => {\n dialogRef.current?.showModal();\n }, []);\n\n const update = <K extends keyof ActionFormData>(key: K, value: ActionFormData[K]) =>\n setForm((prev) => ({ ...prev, [key]: value }));\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n const tags = tagsInput\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n onSubmit({ ...form, tags });\n };\n\n const inputClass =\n 'w-full px-3 py-2 text-sm bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-action-primary-bg focus:border-transparent transition-all';\n const labelClass = 'block text-sm font-medium text-text-secondary mb-1';\n\n return (\n <dialog\n ref={dialogRef}\n className=\"fixed inset-0 z-50 m-auto p-0 bg-transparent backdrop:bg-transparent\"\n onClose={onCancel}\n >\n <AnimatePresence>\n <motion.div\n className=\"fixed inset-0 bg-bg-overlay\"\n initial={shouldReduceMotion ? undefined : { opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n onClick={onCancel}\n />\n <motion.div\n className=\"relative w-full max-w-2xl max-h-[85vh] overflow-y-auto bg-bg-surface rounded-lg shadow-xl\"\n initial={shouldReduceMotion ? undefined : { opacity: 0, scale: 0.95, y: 8 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: 8 }}\n transition={{ duration: 0.2, ease: [0.2, 0, 0, 1] }}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between p-6 border-b border-border-default sticky top-0 bg-bg-surface z-10\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-5 text-text-secondary\" />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-6 space-y-4\">\n {/* Basic fields */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.nameSlug', 'Name (slug)')}\n </label>\n <input\n aria-label=\"my-action\"\n className={inputClass}\n value={form.name}\n onChange={(e) => update('name', e.target.value)}\n placeholder=\"my-action\"\n required\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.displayName', 'Display Name')}\n </label>\n <input\n aria-label=\"My Action\"\n className={inputClass}\n value={form.displayName}\n onChange={(e) => update('displayName', e.target.value)}\n placeholder=\"My Action\"\n required\n />\n </div>\n </div>\n\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.description', 'Description')}\n </label>\n <textarea\n aria-label=\"What does this action do?\"\n className={`${inputClass} resize-none`}\n rows={2}\n value={form.description}\n onChange={(e) => update('description', e.target.value)}\n placeholder=\"What does this action do?\"\n />\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.actionType', 'Action Type')}\n </label>\n <select\n className={inputClass}\n value={form.actionType}\n onChange={(e) => update('actionType', e.target.value)}\n >\n <option value=\"COMMAND\">Command</option>\n <option value=\"SCRIPT\">Script</option>\n <option value=\"WEBHOOK\">Webhook</option>\n {/* WORKFLOW is not creatable until the FluidGrids integration\n lands (backend rejects it) — hide it so operators can't\n select a type that fails on save. Still shown when editing\n a pre-existing WORKFLOW action. BOFF-4115. */}\n {form.actionType === 'WORKFLOW' && (\n <option value=\"WORKFLOW\">Workflow (coming soon)</option>\n )}\n </select>\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.triggerType', 'Trigger Type')}\n </label>\n <select\n className={inputClass}\n value={form.triggerType}\n onChange={(e) => update('triggerType', e.target.value)}\n >\n <option value=\"MANUAL\">Manual</option>\n <option value=\"SCHEDULED\">Scheduled</option>\n <option value=\"EVENT\">Event</option>\n <option value=\"ON_DEMAND\">On Demand</option>\n </select>\n </div>\n </div>\n\n {form.triggerType === 'SCHEDULED' && (\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.schedule', 'Schedule (cron expression)')}\n </label>\n <input\n aria-label=\"*/5 * * * *\"\n className={inputClass}\n value={form.schedule}\n onChange={(e) => update('schedule', e.target.value)}\n placeholder=\"*/5 * * * *\"\n />\n </div>\n )}\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.category', 'Category')}\n </label>\n <input\n aria-label=\"deployment, testing, etc.\"\n className={inputClass}\n value={form.category}\n onChange={(e) => update('category', e.target.value)}\n placeholder=\"deployment, testing, etc.\"\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.tags', 'Tags (comma separated)')}\n </label>\n <input\n aria-label=\"ci, deploy, infra\"\n className={inputClass}\n value={tagsInput}\n onChange={(e) => setTagsInput(e.target.value)}\n placeholder=\"ci, deploy, infra\"\n />\n </div>\n </div>\n\n {/* Type-specific fields */}\n {form.actionType === 'COMMAND' && (\n <div className=\"space-y-4 p-4 rounded-lg bg-bg-sunken/50 border border-border-default\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.actions.form.commandSettings', 'Command Settings')}\n </h3>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.command', 'Command')}\n </label>\n <CommandEditor\n aria-label=\"echo 'Hello World'\"\n language=\"shell\"\n rows={3}\n value={form.command}\n onChange={(e) => update('command', e.target.value)}\n placeholder=\"echo 'Hello World'\"\n required\n />\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.workingDirectory', 'Working Directory')}\n </label>\n <input\n aria-label=\"/home/user/project\"\n className={inputClass}\n value={form.workingDirectory}\n onChange={(e) => update('workingDirectory', e.target.value)}\n placeholder=\"/home/user/project\"\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.targetAgent', 'Target Agent')}\n </label>\n <select\n className={inputClass}\n value={form.targetAgentId}\n onChange={(e) => update('targetAgentId', e.target.value)}\n >\n <option value=\"\">\n {tr('vibecontrols.actions.form.anyAvailableAgent', 'Any available agent')}\n </option>\n {agents.map((a) => (\n <option key={a.id} value={a.id} disabled={!a.isActive}>\n {a.name}{' '}\n {!a.isActive ? tr('vibecontrols.actions.form.offline', '(offline)') : ''}\n </option>\n ))}\n </select>\n {form.targetAgentId ? (\n <RemoteExecutionEnableNotice\n agentId={form.targetAgentId}\n agentName={agents.find((a) => a.id === form.targetAgentId)?.name}\n />\n ) : (\n <p className=\"mt-1 text-xs text-text-tertiary\">\n {tr(\n 'vibecontrols.actions.form.anyAgentHint',\n 'A healthy agent is picked automatically when this action runs.'\n )}\n </p>\n )}\n </div>\n </div>\n </div>\n )}\n\n {form.actionType === 'SCRIPT' && (\n <div className=\"space-y-4 p-4 rounded-lg bg-bg-sunken/50 border border-border-default\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.actions.form.scriptSettings', 'Script Settings')}\n </h3>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.script', 'Script')}\n </label>\n <CommandEditor\n aria-label=\"#!/bin/bash&#10;echo 'Hello'\"\n language={languageForInterpreter(form.scriptInterpreter)}\n rows={6}\n value={form.script}\n onChange={(e) => update('script', e.target.value)}\n placeholder=\"#!/bin/bash&#10;echo 'Hello'\"\n required\n />\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.interpreter', 'Interpreter')}\n </label>\n <select\n className={inputClass}\n value={form.scriptInterpreter}\n onChange={(e) => update('scriptInterpreter', e.target.value)}\n >\n <option value=\"bash\">Bash</option>\n <option value=\"python\">Python</option>\n <option value=\"node\">Node.js</option>\n </select>\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.targetAgent', 'Target Agent')}\n </label>\n <select\n className={inputClass}\n value={form.targetAgentId}\n onChange={(e) => update('targetAgentId', e.target.value)}\n >\n <option value=\"\">\n {tr('vibecontrols.actions.form.anyAvailableAgent', 'Any available agent')}\n </option>\n {agents.map((a) => (\n <option key={a.id} value={a.id} disabled={!a.isActive}>\n {a.name}{' '}\n {!a.isActive ? tr('vibecontrols.actions.form.offline', '(offline)') : ''}\n </option>\n ))}\n </select>\n {form.targetAgentId ? (\n <RemoteExecutionEnableNotice\n agentId={form.targetAgentId}\n agentName={agents.find((a) => a.id === form.targetAgentId)?.name}\n />\n ) : (\n <p className=\"mt-1 text-xs text-text-tertiary\">\n {tr(\n 'vibecontrols.actions.form.anyAgentHint',\n 'A healthy agent is picked automatically when this action runs.'\n )}\n </p>\n )}\n </div>\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.workingDirectory', 'Working Directory')}\n </label>\n <input\n aria-label=\"/home/user/project\"\n className={inputClass}\n value={form.workingDirectory}\n onChange={(e) => update('workingDirectory', e.target.value)}\n placeholder=\"/home/user/project\"\n />\n </div>\n </div>\n )}\n\n {form.actionType === 'WEBHOOK' && (\n <div className=\"space-y-4 p-4 rounded-lg bg-bg-sunken/50 border border-border-default\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('vibecontrols.actions.form.webhookSettings', 'Webhook Settings')}\n </h3>\n <div className=\"grid grid-cols-[1fr_auto] gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.url', 'URL')}\n </label>\n <input\n aria-label=\"https://api.example.com/hook\"\n className={inputClass}\n value={form.webhookUrl}\n onChange={(e) => update('webhookUrl', e.target.value)}\n placeholder=\"https://api.example.com/hook\"\n required\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.method', 'Method')}\n </label>\n <select\n className={inputClass}\n value={form.webhookMethod}\n onChange={(e) => update('webhookMethod', e.target.value)}\n >\n <option value=\"GET\">GET</option>\n <option value=\"POST\">POST</option>\n <option value=\"PUT\">PUT</option>\n <option value=\"PATCH\">PATCH</option>\n <option value=\"DELETE\">DELETE</option>\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.bodyJson', 'Body (JSON)')}\n </label>\n <CommandEditor\n aria-label=\"control\"\n language=\"json\"\n rows={4}\n value={form.webhookBody}\n onChange={(e) => update('webhookBody', e.target.value)}\n placeholder='{\"key\": \"value\"}'\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.authType', 'Auth Type')}\n </label>\n <select\n className={inputClass}\n value={form.webhookAuthType}\n onChange={(e) => update('webhookAuthType', e.target.value)}\n >\n <option value=\"\">None</option>\n <option value=\"BEARER\">Bearer Token</option>\n <option value=\"BASIC\">Basic Auth</option>\n <option value=\"API_KEY\">API Key</option>\n </select>\n </div>\n </div>\n )}\n\n {/* Options */}\n <div className=\"grid grid-cols-3 gap-4\">\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.timeout', 'Timeout (ms)')}\n </label>\n <input\n aria-label=\"control\"\n className={inputClass}\n type=\"number\"\n min={1000}\n max={300000}\n value={form.timeoutMs}\n onChange={(e) => update('timeoutMs', parseInt(e.target.value) || 30000)}\n />\n </div>\n <div>\n <label className={labelClass}>\n {tr('vibecontrols.actions.form.retryCount', 'Retry Count')}\n </label>\n <input\n aria-label=\"control\"\n className={inputClass}\n type=\"number\"\n min={0}\n max={10}\n value={form.retryCount}\n onChange={(e) => update('retryCount', parseInt(e.target.value) || 0)}\n />\n </div>\n <div className=\"flex items-end pb-2\">\n <label className=\"flex items-center gap-2 text-sm text-text-secondary cursor-pointer\">\n <input\n aria-label=\"control\"\n type=\"checkbox\"\n checked={form.confirmationRequired}\n onChange={(e) => update('confirmationRequired', e.target.checked)}\n className=\"rounded border-border-default\"\n />\n {tr('vibecontrols.actions.form.requireConfirmation', 'Require confirmation')}\n </label>\n </div>\n </div>\n\n {extraSection && (\n <div className=\"pt-4 border-t border-border-default\">{extraSection}</div>\n )}\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 pt-4 border-t border-border-default\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium text-text-primary bg-bg-surface border border-border-default rounded-md hover:bg-bg-elevated transition-colors disabled:opacity-50\"\n >\n {tr('vibecontrols.actions.form.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {isLoading\n ? tr('vibecontrols.actions.form.saving', 'Saving...')\n : initialData\n ? tr('vibecontrols.actions.form.updateAction', 'Update Action')\n : tr('vibecontrols.actions.form.createAction', 'Create Action')}\n </button>\n </div>\n </form>\n </motion.div>\n </AnimatePresence>\n </dialog>\n );\n}\n"],"mappings":"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"}
@@ -24,7 +24,9 @@ function w({ agentId: w, profile: T }) {
24
24
  agentId: w,
25
25
  profile: T,
26
26
  action: e,
27
- body: t ?? null
27
+ body: t?.method === "GET" ? null : t?.body ?? null,
28
+ method: t?.method ?? null,
29
+ pathSuffix: t?.pathSuffix ?? null
28
30
  } })).data?.agentBackupRequest;
29
31
  if (!n?.ok) throw Error(n?.error || `HTTP ${n?.status ?? 0}`);
30
32
  return n.data ?? {};
@@ -36,11 +38,11 @@ function w({ agentId: w, profile: T }) {
36
38
  try {
37
39
  P(!0), R(null);
38
40
  let [e, t, n] = await Promise.all([
39
- W("config"),
40
- W("status"),
41
- W("list", { limit: 20 })
41
+ W("config", { method: "GET" }),
42
+ W("status", { method: "GET" }),
43
+ W("list", { method: "GET" })
42
44
  ]);
43
- O(e.config), A(t.status), M(n.backups || []);
45
+ O(e.config), A(t.status), M((n.backups || []).slice(0, 20));
44
46
  } catch (e) {
45
47
  R(e instanceof Error ? e.message : String(e));
46
48
  } finally {
@@ -58,7 +60,10 @@ function w({ agentId: w, profile: T }) {
58
60
  let K = async (e) => {
59
61
  try {
60
62
  I("backup"), R(null);
61
- let t = await W("run", { force: e }), n = t?.backup, r = typeof t?.error == "string" && t.error || (n && typeof n.error == "string" && n.error !== "Skipped: no changes" ? n.error : null);
63
+ let t = await W("run", {
64
+ method: "POST",
65
+ body: { force: e }
66
+ }), n = t?.backup, r = typeof t?.error == "string" && t.error || (n && typeof n.error == "string" && n.error !== "Skipped: no changes" ? n.error : null);
62
67
  if (r) {
63
68
  R(b(r));
64
69
  return;
@@ -77,8 +82,11 @@ function w({ agentId: w, profile: T }) {
77
82
  try {
78
83
  I(`restore-${e}`), R(null);
79
84
  let n = await W("restore", {
80
- backupId: e,
81
- dryRun: t
85
+ method: "POST",
86
+ body: {
87
+ backupId: e,
88
+ dryRun: t
89
+ }
82
90
  });
83
91
  n.success ? B(n.message ?? "Restore complete") : R(n.message ?? "Restore failed");
84
92
  } catch (e) {
@@ -89,7 +97,10 @@ function w({ agentId: w, profile: T }) {
89
97
  }, J = async () => {
90
98
  try {
91
99
  I("toggle");
92
- let e = (await W("config-set", { enabled: !D?.enabled })).config;
100
+ let e = (await W("config", {
101
+ method: "PUT",
102
+ body: { enabled: !D?.enabled }
103
+ })).config;
93
104
  e && O(e), B(e?.enabled ? "Backup enabled" : "Backup disabled"), await G();
94
105
  } catch (e) {
95
106
  R(e instanceof Error ? e.message : String(e));
@@ -99,7 +110,7 @@ function w({ agentId: w, profile: T }) {
99
110
  }, Y = async () => {
100
111
  try {
101
112
  I("test");
102
- let e = await W("test-connection");
113
+ let e = await W("test-connection", { method: "POST" });
103
114
  e.ok ? B(`Connection OK: ${e.message ?? ""}`) : R(`Connection failed: ${e.message ?? ""}`);
104
115
  } catch (e) {
105
116
  R(e instanceof Error ? e.message : String(e));
@@ -108,7 +119,16 @@ function w({ agentId: w, profile: T }) {
108
119
  }
109
120
  }, X = async (e) => {
110
121
  try {
111
- I(`delete-${e}`), await W("delete", { backupId: e }), B("Backup deleted"), await G();
122
+ I(`delete-${e}`);
123
+ let t = await W("backups", {
124
+ method: "DELETE",
125
+ pathSuffix: `/${encodeURIComponent(e)}`
126
+ });
127
+ if (t.ok === !1) {
128
+ R(t.message ?? "Backup delete failed");
129
+ return;
130
+ }
131
+ B("Backup deleted"), await G();
112
132
  } catch (e) {
113
133
  R(e instanceof Error ? e.message : String(e));
114
134
  } finally {
@@ -1 +1 @@
1
- {"version":3,"file":"AgentBackupTab.js","names":[],"sources":["../../../../src/components/agents/backup/AgentBackupTab.tsx"],"sourcesContent":["/**\n * AgentBackupTab — UI for managing SQLite database backups of a VibeControls agent.\n * Talks directly to the agent's REST API via its tunnel URL.\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport {\n Database,\n Upload,\n RefreshCw,\n Settings,\n Play,\n Clock,\n CheckCircle2,\n XCircle,\n Loader2,\n Trash2,\n RotateCcw,\n Wifi,\n} from 'lucide-react';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useAgentBackupRequestMutation } from '@/generated/wspace-operations';\n\n/**\n * Detect error strings that look like a raw JavaScript runtime exception\n * the agent leaked through to the platform — e.g.\n * `undefined is not an object (evaluating 'createResult.data.createFolder')`.\n * The actual root cause lives in the agent's backup plugin (it doesn't\n * forward x-workspace-id when calling files-svc, so the GraphQL response\n * has no `data` and the plugin blindly dereferences it). Until the agent\n * fix lands we strip the JS-frame and show the user something they can act\n * on instead of pasting a stack-trace fragment into the UI. [BOFF-2613]\n */\nfunction sanitizeBackupError(raw: string): string {\n if (!raw) return 'Backup failed.';\n const m = raw.toLowerCase();\n // JS runtime exception leaked from the agent.\n if (\n m.includes('undefined is not an object') ||\n m.includes('cannot read property') ||\n m.includes('cannot read properties of') ||\n /\\btypeerror:/.test(m) ||\n /\\breferenceerror:/.test(m)\n ) {\n // Specific, actionable hint for the known x-workspace-id case.\n if (raw.includes('createResult.data.createFolder')) {\n return \"Backup failed: the agent could not reach the workspace files service (missing workspace context). Re-push credentials from the agent's Security tab and try again. See agent logs for the full error.\";\n }\n return 'Backup failed: the agent threw an internal error. See agent logs for the full error.';\n }\n if (raw.length > 200) return raw.slice(0, 200) + '…';\n return raw;\n}\n\ninterface BackupConfig {\n enabled: boolean;\n scheduleIntervalMs: number;\n changeOnlyBackup: boolean;\n storageTarget: 'wspace' | 'custom-s3';\n customS3?: {\n endpoint: string;\n bucket: string;\n region: string;\n accessKeyId: string;\n secretAccessKey: string;\n pathPrefix?: string;\n };\n retentionDays?: number;\n maxBackups?: number;\n}\n\ninterface BackupRecord {\n id: string;\n timestamp: string;\n agentName: string;\n dbSizeBytes: number;\n sha256Hash: string;\n storageTarget: string;\n storagePath: string;\n durationMs: number;\n status: string;\n error?: string;\n}\n\ninterface BackupStatus {\n enabled: boolean;\n schedulerRunning: boolean;\n lastBackupTime: string | null;\n lastBackupHash: string | null;\n nextScheduledBackup: string | null;\n totalBackups: number;\n storageTarget: string;\n}\n\ninterface AgentBackupTabProps {\n /**\n * F4 Wave B: every direct agent fetch (with `x-agent-api-key`) has been\n * replaced by `agentBackupRequest`, an svc-mediated proxy. The agent's\n * tunnel URL is no longer needed by this component; the svc resolves\n * the tunnel from the agent record.\n */\n agentId: string;\n /** Canonical CLI profile name on the remote agent. */\n profile: string;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n return `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction formatTime(iso: string): string {\n return new Date(iso).toLocaleString();\n}\n\nexport function AgentBackupTab({ agentId, profile }: AgentBackupTabProps) {\n const tr = useTr();\n const [config, setConfig] = useState<BackupConfig | null>(null);\n const [status, setStatus] = useState<BackupStatus | null>(null);\n const [backups, setBackups] = useState<BackupRecord[]>([]);\n const [loading, setLoading] = useState(true);\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [successMsg, setSuccessMsg] = useState<string | null>(null);\n const [showConfig, setShowConfig] = useState(false);\n\n const [agentBackupRequest] = useAgentBackupRequestMutation();\n\n // F4 Wave B: every direct browser fetch now routes through the\n // `agentBackupRequest` mutation, which proxies through the svc with\n // RBAC + audit. The svc-side resolver POSTs the body to\n // `/api/profiles/<profile>/backup/<action>` on the agent.\n // TODO(svc): the svc proxy currently only supports POST. GET endpoints\n // (config / status / list) and DELETE (backups/:id) require the\n // agent to interpret them server-side or the svc to learn method\n // routing — tracked separately.\n const proxy = useCallback(\n async (action: string, body?: unknown): Promise<Record<string, unknown>> => {\n const res = await agentBackupRequest({\n variables: {\n agentId,\n profile,\n action,\n body: (body as never) ?? null,\n },\n });\n const result = res.data?.agentBackupRequest;\n if (!result?.ok) {\n throw new Error(result?.error || `HTTP ${result?.status ?? 0}`);\n }\n return (result.data as Record<string, unknown> | null) ?? {};\n },\n [agentBackupRequest, agentId, profile]\n );\n\n const refresh = useCallback(async () => {\n try {\n setLoading(true);\n setError(null);\n const [configRes, statusRes, listRes] = await Promise.all([\n proxy('config'),\n proxy('status'),\n proxy('list', { limit: 20 }),\n ]);\n setConfig(configRes.config as BackupConfig | null);\n setStatus(statusRes.status as BackupStatus | null);\n setBackups((listRes.backups as BackupRecord[] | undefined) || []);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n setError(msg);\n } finally {\n setLoading(false);\n }\n }, [proxy]);\n\n useEffect(() => {\n refresh();\n }, [refresh]);\n\n // Clear success message after 3s\n useEffect(() => {\n if (successMsg) {\n const t = setTimeout(() => setSuccessMsg(null), 3000);\n return () => clearTimeout(t);\n }\n }, [successMsg]);\n\n const handleRunBackup = async (force: boolean) => {\n try {\n setActionLoading('backup');\n setError(null);\n const res = await proxy('run', { force });\n // The agent's `/backup/run` returns HTTP 200 even when the backup\n // failed internally — the failure surfaces as `res.error` (top-level)\n // or as `res.backup === null`. Treat both as failures here and surface\n // a sanitized message. [BOFF-2613]\n const resRecord = res as Record<string, unknown>;\n const b = resRecord?.backup as BackupRecord | null | undefined;\n const failureMessage: string | null =\n (typeof resRecord?.error === 'string' && (resRecord.error as string)) ||\n (b && typeof b.error === 'string' && b.error !== 'Skipped: no changes' ? b.error : null);\n\n if (failureMessage) {\n setError(sanitizeBackupError(failureMessage));\n return;\n }\n if (!b) {\n // No backup payload AND no error string — defensive.\n setError(sanitizeBackupError(''));\n return;\n }\n if (b.error === 'Skipped: no changes') {\n setSuccessMsg('No changes detected — backup skipped');\n } else {\n setSuccessMsg(\n `Backup completed: ${formatBytes(b.dbSizeBytes)} in ${formatDuration(b.durationMs)}`\n );\n }\n await refresh();\n } catch (err) {\n setError(sanitizeBackupError(err instanceof Error ? err.message : String(err)));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleRestore = async (backupId: string, dryRun: boolean) => {\n try {\n setActionLoading(`restore-${backupId}`);\n setError(null);\n const res = await proxy('restore', { backupId, dryRun });\n if (res.success) {\n setSuccessMsg((res.message as string) ?? 'Restore complete');\n } else {\n setError((res.message as string) ?? 'Restore failed');\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleToggleEnabled = async () => {\n try {\n setActionLoading('toggle');\n // TODO(svc): the agent-side mutation here is PUT /backup/config;\n // the svc proxy will need to either advertise a `config-set` action\n // or accept a method override. For now we pass the new value as a\n // POST body and trust the agent's `config` action to interpret it.\n const res = await proxy('config-set', { enabled: !config?.enabled });\n const next = res.config as BackupConfig | undefined;\n if (next) setConfig(next);\n setSuccessMsg(next?.enabled ? 'Backup enabled' : 'Backup disabled');\n await refresh();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleTestConnection = async () => {\n try {\n setActionLoading('test');\n const res = await proxy('test-connection');\n if (res.ok) {\n setSuccessMsg(`Connection OK: ${res.message ?? ''}`);\n } else {\n setError(`Connection failed: ${res.message ?? ''}`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleDeleteBackup = async (backupId: string) => {\n try {\n setActionLoading(`delete-${backupId}`);\n // TODO(svc): the agent endpoint is DELETE /backup/backups/<id>; the\n // svc proxy only POSTs. Pass the id in the body for now and rely\n // on a future `delete` action handler on the agent.\n await proxy('delete', { backupId });\n setSuccessMsg('Backup deleted');\n await refresh();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12 text-text-secondary\">\n <Loader2 className=\"size-5 animate-spin mr-2\" />\n {tr('vibecontrols.agents.backup.loading', 'Loading backup settings...')}\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Status Messages */}\n {error && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\">\n <XCircle className=\"size-4 shrink-0\" />\n {error}\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"ml-auto text-xs underline\"\n >\n Dismiss\n </button>\n </div>\n )}\n {successMsg && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-3 text-sm text-status-success-text\">\n <CheckCircle2 className=\"size-4 shrink-0\" />\n {successMsg}\n </div>\n )}\n\n {/* Status Card */}\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"flex items-center gap-2\">\n <Database className=\"size-5 text-action-primary-bg\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.agents.backup.title', 'Database Backup')}\n </h3>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={refresh}\n className=\"p-2 rounded-lg hover:bg-bg-sunken transition-colors text-text-secondary\"\n title=\"Refresh\"\n >\n <RefreshCw className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setShowConfig(!showConfig)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken transition-colors text-text-secondary\"\n title=\"Settings\"\n >\n <Settings className=\"size-4\" />\n </button>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4 mb-4\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.status', 'Status')}\n </p>\n <div className=\"flex items-center gap-1.5\">\n {config?.enabled ? (\n <>\n <div className=\"size-2 rounded-full bg-status-success-bg\" />\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.agents.backup.enabled', 'Enabled')}\n </span>\n </>\n ) : (\n <>\n <div className=\"size-2 rounded-full bg-text-tertiary\" />\n <span className=\"text-sm font-medium text-text-secondary\">\n {tr('vibecontrols.agents.backup.disabled', 'Disabled')}\n </span>\n </>\n )}\n </div>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.scheduler', 'Scheduler')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.schedulerRunning ? 'Running' : 'Stopped'}\n </span>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.totalBackups', 'Total Backups')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.totalBackups ?? 0}\n </span>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.target', 'Target')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.storageTarget ?? 'N/A'}\n </span>\n </div>\n </div>\n\n {status?.lastBackupTime && (\n <div className=\"flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n <span>\n <Clock className=\"size-3 inline mr-1\" />\n Last: {formatTime(status.lastBackupTime)}\n </span>\n {status.nextScheduledBackup && (\n <span>Next: {formatTime(status.nextScheduledBackup)}</span>\n )}\n </div>\n )}\n\n {/* Action Buttons */}\n <div className=\"flex flex-wrap gap-2 mt-4 pt-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={() => handleRunBackup(true)}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-action-primary-bg text-action-primary-text text-sm font-medium hover:bg-action-primary-bg-hover disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'backup' ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Upload className=\"size-4\" />\n )}\n {tr('vibecontrols.agents.backup.backupNow', 'Backup Now')}\n </button>\n <button\n type=\"button\"\n onClick={() => handleRunBackup(false)}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n <Play className=\"size-4\" />\n {tr('vibecontrols.agents.backup.backupIfChanged', 'Backup if Changed')}\n </button>\n <button\n type=\"button\"\n onClick={handleToggleEnabled}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'toggle' ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : config?.enabled ? (\n <XCircle className=\"size-4\" />\n ) : (\n <CheckCircle2 className=\"size-4\" />\n )}\n {config?.enabled\n ? tr('vibecontrols.agents.backup.disableSchedule', 'Disable Schedule')\n : tr('vibecontrols.agents.backup.enableSchedule', 'Enable Schedule')}\n </button>\n <button\n type=\"button\"\n onClick={handleTestConnection}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'test' ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Wifi className=\"size-4\" />\n )}\n {tr('vibecontrols.agents.backup.testConnection', 'Test Connection')}\n </button>\n </div>\n </div>\n\n {/* Configuration Panel (collapsible) */}\n {showConfig && config && (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <h4 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Settings className=\"size-4\" />\n {tr('vibecontrols.agents.backup.configuration', 'Configuration')}\n </h4>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4 text-sm\">\n <div>\n <span className=\"text-text-muted\">Schedule Interval:</span>{' '}\n <span className=\"text-text-primary font-medium\">\n {config.scheduleIntervalMs >= 3600000\n ? `${config.scheduleIntervalMs / 3600000}h`\n : `${config.scheduleIntervalMs / 60000}m`}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Change Detection:</span>{' '}\n <span className=\"text-text-primary font-medium\">\n {config.changeOnlyBackup ? 'On' : 'Off'}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Storage Target:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.storageTarget}</span>\n </div>\n {config.customS3 && (\n <>\n <div>\n <span className=\"text-text-muted\">S3 Bucket:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.customS3.bucket}</span>\n </div>\n <div>\n <span className=\"text-text-muted\">S3 Region:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.customS3.region}</span>\n </div>\n </>\n )}\n {config.retentionDays && (\n <div>\n <span className=\"text-text-muted\">Retention:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.retentionDays} days</span>\n </div>\n )}\n {config.maxBackups && (\n <div>\n <span className=\"text-text-muted\">Max Backups:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.maxBackups}</span>\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Backup History */}\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <h4 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Clock className=\"size-4\" />\n {tr('vibecontrols.agents.backup.history', 'Backup History')}\n <span className=\"text-xs text-text-muted font-normal\">({backups.length} shown)</span>\n </h4>\n\n {backups.length === 0 ? (\n <p className=\"text-sm text-text-muted py-4 text-center\">\n {tr(\n 'vibecontrols.agents.backup.noBackups',\n 'No backups yet. Click \"Backup Now\" to create one.'\n )}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {backups.map((b) => (\n <div\n key={b.id}\n className=\"flex items-center justify-between p-3 rounded-lg border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div\n className={`size-8 rounded-lg flex items-center justify-center shrink-0 ${\n b.status === 'completed'\n ? 'border border-status-success-border bg-status-success-bg-subtle'\n : 'border border-status-error-border bg-status-error-bg-subtle'\n }`}\n >\n {b.status === 'completed' ? (\n <CheckCircle2 className=\"size-4 text-status-success-text\" />\n ) : (\n <XCircle className=\"size-4 text-status-error-text\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {formatTime(b.timestamp)}\n </p>\n <p className=\"text-xs text-text-muted truncate\">\n {formatBytes(b.dbSizeBytes)} &middot; {formatDuration(b.durationMs)} &middot;{' '}\n {b.storageTarget}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n <button\n type=\"button\"\n onClick={() => handleRestore(b.id, true)}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded hover:bg-bg-canvas text-text-secondary transition-colors\"\n title=\"Validate (dry run)\"\n >\n {actionLoading === `restore-${b.id}` ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"size-4\" />\n )}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n if (confirm('Restore this backup? The agent will need to restart.')) {\n handleRestore(b.id, false);\n }\n }}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded hover:bg-bg-canvas text-text-secondary transition-colors\"\n title=\"Restore\"\n >\n <RotateCcw className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeleteBackup(b.id)}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded text-text-secondary transition-colors hover:bg-status-error-bg/80 hover:text-status-error-text\"\n title=\"Delete\"\n >\n {actionLoading === `delete-${b.id}` ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4\" />\n )}\n </button>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AgentBackupTab.js","names":[],"sources":["../../../../src/components/agents/backup/AgentBackupTab.tsx"],"sourcesContent":["/**\n * AgentBackupTab — UI for managing SQLite database backups of a VibeControls agent.\n * Talks directly to the agent's REST API via its tunnel URL.\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport {\n Database,\n Upload,\n RefreshCw,\n Settings,\n Play,\n Clock,\n CheckCircle2,\n XCircle,\n Loader2,\n Trash2,\n RotateCcw,\n Wifi,\n} from 'lucide-react';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useAgentBackupRequestMutation } from '@/generated/wspace-operations';\n\n/**\n * Detect error strings that look like a raw JavaScript runtime exception\n * the agent leaked through to the platform — e.g.\n * `undefined is not an object (evaluating 'createResult.data.createFolder')`.\n * The actual root cause lives in the agent's backup plugin (it doesn't\n * forward x-workspace-id when calling files-svc, so the GraphQL response\n * has no `data` and the plugin blindly dereferences it). Until the agent\n * fix lands we strip the JS-frame and show the user something they can act\n * on instead of pasting a stack-trace fragment into the UI. [BOFF-2613]\n */\nfunction sanitizeBackupError(raw: string): string {\n if (!raw) return 'Backup failed.';\n const m = raw.toLowerCase();\n // JS runtime exception leaked from the agent.\n if (\n m.includes('undefined is not an object') ||\n m.includes('cannot read property') ||\n m.includes('cannot read properties of') ||\n /\\btypeerror:/.test(m) ||\n /\\breferenceerror:/.test(m)\n ) {\n // Specific, actionable hint for the known x-workspace-id case.\n if (raw.includes('createResult.data.createFolder')) {\n return \"Backup failed: the agent could not reach the workspace files service (missing workspace context). Re-push credentials from the agent's Security tab and try again. See agent logs for the full error.\";\n }\n return 'Backup failed: the agent threw an internal error. See agent logs for the full error.';\n }\n if (raw.length > 200) return raw.slice(0, 200) + '…';\n return raw;\n}\n\ninterface BackupConfig {\n enabled: boolean;\n scheduleIntervalMs: number;\n changeOnlyBackup: boolean;\n storageTarget: 'wspace' | 'custom-s3';\n customS3?: {\n endpoint: string;\n bucket: string;\n region: string;\n accessKeyId: string;\n secretAccessKey: string;\n pathPrefix?: string;\n };\n retentionDays?: number;\n maxBackups?: number;\n}\n\ninterface BackupRecord {\n id: string;\n timestamp: string;\n agentName: string;\n dbSizeBytes: number;\n sha256Hash: string;\n storageTarget: string;\n storagePath: string;\n durationMs: number;\n status: string;\n error?: string;\n}\n\ninterface BackupStatus {\n enabled: boolean;\n schedulerRunning: boolean;\n lastBackupTime: string | null;\n lastBackupHash: string | null;\n nextScheduledBackup: string | null;\n totalBackups: number;\n storageTarget: string;\n}\n\ninterface AgentBackupTabProps {\n /**\n * F4 Wave B: every direct agent fetch (with `x-agent-api-key`) has been\n * replaced by `agentBackupRequest`, an svc-mediated proxy. The agent's\n * tunnel URL is no longer needed by this component; the svc resolves\n * the tunnel from the agent record.\n */\n agentId: string;\n /** Canonical CLI profile name on the remote agent. */\n profile: string;\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n return `${(ms / 1000).toFixed(1)}s`;\n}\n\nfunction formatTime(iso: string): string {\n return new Date(iso).toLocaleString();\n}\n\nexport function AgentBackupTab({ agentId, profile }: AgentBackupTabProps) {\n const tr = useTr();\n const [config, setConfig] = useState<BackupConfig | null>(null);\n const [status, setStatus] = useState<BackupStatus | null>(null);\n const [backups, setBackups] = useState<BackupRecord[]>([]);\n const [loading, setLoading] = useState(true);\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [successMsg, setSuccessMsg] = useState<string | null>(null);\n const [showConfig, setShowConfig] = useState(false);\n\n const [agentBackupRequest] = useAgentBackupRequestMutation();\n\n // F4 Wave B: every direct browser fetch now routes through the\n // `agentBackupRequest` mutation, which proxies through the svc with\n // RBAC + audit. The svc-side resolver forwards `method`/`pathSuffix` to\n // the matching REST verb on the agent's backup plugin:\n // GET/PUT /backup/config, GET /backup/status, GET /backup/list, and\n // DELETE /backup/backups/<id>. [BOFF-4112] `run` / `restore` /\n // `test-connection` stay on the default POST — those are genuinely\n // POST-only actions on the agent side, not REST reads/writes.\n const proxy = useCallback(\n async (\n action: string,\n opts?: { method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; body?: unknown; pathSuffix?: string }\n ): Promise<Record<string, unknown>> => {\n const res = await agentBackupRequest({\n variables: {\n agentId,\n profile,\n action,\n // GET requests never carry a body — the svc proxy drops it too,\n // but skip serializing one at all so the intent is unambiguous.\n body: opts?.method === 'GET' ? null : ((opts?.body as never) ?? null),\n method: opts?.method ?? null,\n pathSuffix: opts?.pathSuffix ?? null,\n },\n });\n const result = res.data?.agentBackupRequest;\n if (!result?.ok) {\n throw new Error(result?.error || `HTTP ${result?.status ?? 0}`);\n }\n return (result.data as Record<string, unknown> | null) ?? {};\n },\n [agentBackupRequest, agentId, profile]\n );\n\n const refresh = useCallback(async () => {\n try {\n setLoading(true);\n setError(null);\n const [configRes, statusRes, listRes] = await Promise.all([\n proxy('config', { method: 'GET' }),\n proxy('status', { method: 'GET' }),\n proxy('list', { method: 'GET' }),\n ]);\n setConfig(configRes.config as BackupConfig | null);\n setStatus(statusRes.status as BackupStatus | null);\n // The agent's GET /backup/list has no server-side limit applied when\n // called without a query string (which the svc proxy's pathSuffix\n // doesn't carry query params for); it returns every completed backup\n // sorted newest-first, so trim to the same 20-item window the tab has\n // always shown.\n const allBackups = (listRes.backups as BackupRecord[] | undefined) || [];\n setBackups(allBackups.slice(0, 20));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n setError(msg);\n } finally {\n setLoading(false);\n }\n }, [proxy]);\n\n useEffect(() => {\n refresh();\n }, [refresh]);\n\n // Clear success message after 3s\n useEffect(() => {\n if (successMsg) {\n const t = setTimeout(() => setSuccessMsg(null), 3000);\n return () => clearTimeout(t);\n }\n }, [successMsg]);\n\n const handleRunBackup = async (force: boolean) => {\n try {\n setActionLoading('backup');\n setError(null);\n const res = await proxy('run', { method: 'POST', body: { force } });\n // The agent's `/backup/run` returns HTTP 200 even when the backup\n // failed internally — the failure surfaces as `res.error` (top-level)\n // or as `res.backup === null`. Treat both as failures here and surface\n // a sanitized message. [BOFF-2613]\n const resRecord = res as Record<string, unknown>;\n const b = resRecord?.backup as BackupRecord | null | undefined;\n const failureMessage: string | null =\n (typeof resRecord?.error === 'string' && (resRecord.error as string)) ||\n (b && typeof b.error === 'string' && b.error !== 'Skipped: no changes' ? b.error : null);\n\n if (failureMessage) {\n setError(sanitizeBackupError(failureMessage));\n return;\n }\n if (!b) {\n // No backup payload AND no error string — defensive.\n setError(sanitizeBackupError(''));\n return;\n }\n if (b.error === 'Skipped: no changes') {\n setSuccessMsg('No changes detected — backup skipped');\n } else {\n setSuccessMsg(\n `Backup completed: ${formatBytes(b.dbSizeBytes)} in ${formatDuration(b.durationMs)}`\n );\n }\n await refresh();\n } catch (err) {\n setError(sanitizeBackupError(err instanceof Error ? err.message : String(err)));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleRestore = async (backupId: string, dryRun: boolean) => {\n try {\n setActionLoading(`restore-${backupId}`);\n setError(null);\n const res = await proxy('restore', { method: 'POST', body: { backupId, dryRun } });\n if (res.success) {\n setSuccessMsg((res.message as string) ?? 'Restore complete');\n } else {\n setError((res.message as string) ?? 'Restore failed');\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleToggleEnabled = async () => {\n try {\n setActionLoading('toggle');\n // BOFF-4112: the agent's config mutation is PUT /backup/config; route\n // it as a real PUT instead of overloading a synthetic POST action.\n const res = await proxy('config', { method: 'PUT', body: { enabled: !config?.enabled } });\n const next = res.config as BackupConfig | undefined;\n if (next) setConfig(next);\n setSuccessMsg(next?.enabled ? 'Backup enabled' : 'Backup disabled');\n await refresh();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleTestConnection = async () => {\n try {\n setActionLoading('test');\n const res = await proxy('test-connection', { method: 'POST' });\n if (res.ok) {\n setSuccessMsg(`Connection OK: ${res.message ?? ''}`);\n } else {\n setError(`Connection failed: ${res.message ?? ''}`);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleDeleteBackup = async (backupId: string) => {\n try {\n setActionLoading(`delete-${backupId}`);\n // BOFF-4112: the agent endpoint is DELETE /backup/backups/<id> — route\n // the resource id through `pathSuffix` and use the real DELETE verb\n // instead of a synthetic POST action (which 404'd on the agent; there\n // is no POST /backup/delete route).\n const res = await proxy('backups', {\n method: 'DELETE',\n pathSuffix: `/${encodeURIComponent(backupId)}`,\n });\n if (res.ok === false) {\n setError((res.message as string) ?? 'Backup delete failed');\n return;\n }\n setSuccessMsg('Backup deleted');\n await refresh();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setActionLoading(null);\n }\n };\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center py-12 text-text-secondary\">\n <Loader2 className=\"size-5 animate-spin mr-2\" />\n {tr('vibecontrols.agents.backup.loading', 'Loading backup settings...')}\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Status Messages */}\n {error && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text\">\n <XCircle className=\"size-4 shrink-0\" />\n {error}\n <button\n type=\"button\"\n onClick={() => setError(null)}\n className=\"ml-auto text-xs underline\"\n >\n Dismiss\n </button>\n </div>\n )}\n {successMsg && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-3 text-sm text-status-success-text\">\n <CheckCircle2 className=\"size-4 shrink-0\" />\n {successMsg}\n </div>\n )}\n\n {/* Status Card */}\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <div className=\"flex items-center justify-between mb-4\">\n <div className=\"flex items-center gap-2\">\n <Database className=\"size-5 text-action-primary-bg\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibecontrols.agents.backup.title', 'Database Backup')}\n </h3>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={refresh}\n className=\"p-2 rounded-lg hover:bg-bg-sunken transition-colors text-text-secondary\"\n title=\"Refresh\"\n >\n <RefreshCw className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setShowConfig(!showConfig)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken transition-colors text-text-secondary\"\n title=\"Settings\"\n >\n <Settings className=\"size-4\" />\n </button>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-4 mb-4\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.status', 'Status')}\n </p>\n <div className=\"flex items-center gap-1.5\">\n {config?.enabled ? (\n <>\n <div className=\"size-2 rounded-full bg-status-success-bg\" />\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('vibecontrols.agents.backup.enabled', 'Enabled')}\n </span>\n </>\n ) : (\n <>\n <div className=\"size-2 rounded-full bg-text-tertiary\" />\n <span className=\"text-sm font-medium text-text-secondary\">\n {tr('vibecontrols.agents.backup.disabled', 'Disabled')}\n </span>\n </>\n )}\n </div>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.scheduler', 'Scheduler')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.schedulerRunning ? 'Running' : 'Stopped'}\n </span>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.totalBackups', 'Total Backups')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.totalBackups ?? 0}\n </span>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">\n {tr('vibecontrols.agents.backup.target', 'Target')}\n </p>\n <span className=\"text-sm font-medium text-text-primary\">\n {status?.storageTarget ?? 'N/A'}\n </span>\n </div>\n </div>\n\n {status?.lastBackupTime && (\n <div className=\"flex flex-wrap items-center gap-3 text-xs text-text-muted\">\n <span>\n <Clock className=\"size-3 inline mr-1\" />\n Last: {formatTime(status.lastBackupTime)}\n </span>\n {status.nextScheduledBackup && (\n <span>Next: {formatTime(status.nextScheduledBackup)}</span>\n )}\n </div>\n )}\n\n {/* Action Buttons */}\n <div className=\"flex flex-wrap gap-2 mt-4 pt-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={() => handleRunBackup(true)}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-action-primary-bg text-action-primary-text text-sm font-medium hover:bg-action-primary-bg-hover disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'backup' ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Upload className=\"size-4\" />\n )}\n {tr('vibecontrols.agents.backup.backupNow', 'Backup Now')}\n </button>\n <button\n type=\"button\"\n onClick={() => handleRunBackup(false)}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n <Play className=\"size-4\" />\n {tr('vibecontrols.agents.backup.backupIfChanged', 'Backup if Changed')}\n </button>\n <button\n type=\"button\"\n onClick={handleToggleEnabled}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'toggle' ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : config?.enabled ? (\n <XCircle className=\"size-4\" />\n ) : (\n <CheckCircle2 className=\"size-4\" />\n )}\n {config?.enabled\n ? tr('vibecontrols.agents.backup.disableSchedule', 'Disable Schedule')\n : tr('vibecontrols.agents.backup.enableSchedule', 'Enable Schedule')}\n </button>\n <button\n type=\"button\"\n onClick={handleTestConnection}\n disabled={!!actionLoading}\n className=\"flex items-center gap-2 px-3 py-1.5 rounded-lg bg-bg-sunken text-text-primary text-sm font-medium hover:bg-bg-canvas border border-border-default disabled:opacity-50 transition-colors\"\n >\n {actionLoading === 'test' ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Wifi className=\"size-4\" />\n )}\n {tr('vibecontrols.agents.backup.testConnection', 'Test Connection')}\n </button>\n </div>\n </div>\n\n {/* Configuration Panel (collapsible) */}\n {showConfig && config && (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <h4 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Settings className=\"size-4\" />\n {tr('vibecontrols.agents.backup.configuration', 'Configuration')}\n </h4>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4 text-sm\">\n <div>\n <span className=\"text-text-muted\">Schedule Interval:</span>{' '}\n <span className=\"text-text-primary font-medium\">\n {config.scheduleIntervalMs >= 3600000\n ? `${config.scheduleIntervalMs / 3600000}h`\n : `${config.scheduleIntervalMs / 60000}m`}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Change Detection:</span>{' '}\n <span className=\"text-text-primary font-medium\">\n {config.changeOnlyBackup ? 'On' : 'Off'}\n </span>\n </div>\n <div>\n <span className=\"text-text-muted\">Storage Target:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.storageTarget}</span>\n </div>\n {config.customS3 && (\n <>\n <div>\n <span className=\"text-text-muted\">S3 Bucket:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.customS3.bucket}</span>\n </div>\n <div>\n <span className=\"text-text-muted\">S3 Region:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.customS3.region}</span>\n </div>\n </>\n )}\n {config.retentionDays && (\n <div>\n <span className=\"text-text-muted\">Retention:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.retentionDays} days</span>\n </div>\n )}\n {config.maxBackups && (\n <div>\n <span className=\"text-text-muted\">Max Backups:</span>{' '}\n <span className=\"text-text-primary font-medium\">{config.maxBackups}</span>\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Backup History */}\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-5\">\n <h4 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Clock className=\"size-4\" />\n {tr('vibecontrols.agents.backup.history', 'Backup History')}\n <span className=\"text-xs text-text-muted font-normal\">({backups.length} shown)</span>\n </h4>\n\n {backups.length === 0 ? (\n <p className=\"text-sm text-text-muted py-4 text-center\">\n {tr(\n 'vibecontrols.agents.backup.noBackups',\n 'No backups yet. Click \"Backup Now\" to create one.'\n )}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {backups.map((b) => (\n <div\n key={b.id}\n className=\"flex items-center justify-between p-3 rounded-lg border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div\n className={`size-8 rounded-lg flex items-center justify-center shrink-0 ${\n b.status === 'completed'\n ? 'border border-status-success-border bg-status-success-bg-subtle'\n : 'border border-status-error-border bg-status-error-bg-subtle'\n }`}\n >\n {b.status === 'completed' ? (\n <CheckCircle2 className=\"size-4 text-status-success-text\" />\n ) : (\n <XCircle className=\"size-4 text-status-error-text\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {formatTime(b.timestamp)}\n </p>\n <p className=\"text-xs text-text-muted truncate\">\n {formatBytes(b.dbSizeBytes)} &middot; {formatDuration(b.durationMs)} &middot;{' '}\n {b.storageTarget}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n <button\n type=\"button\"\n onClick={() => handleRestore(b.id, true)}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded hover:bg-bg-canvas text-text-secondary transition-colors\"\n title=\"Validate (dry run)\"\n >\n {actionLoading === `restore-${b.id}` ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"size-4\" />\n )}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n if (confirm('Restore this backup? The agent will need to restart.')) {\n handleRestore(b.id, false);\n }\n }}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded hover:bg-bg-canvas text-text-secondary transition-colors\"\n title=\"Restore\"\n >\n <RotateCcw className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeleteBackup(b.id)}\n disabled={!!actionLoading}\n className=\"p-1.5 rounded text-text-secondary transition-colors hover:bg-status-error-bg/80 hover:text-status-error-text\"\n title=\"Delete\"\n >\n {actionLoading === `delete-${b.id}` ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Trash2 className=\"size-4\" />\n )}\n </button>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}