2026-02-28 20:42:40 +00:00
|
|
|
import { useState, useEffect } from "react";
|
2026-02-27 04:29:51 +00:00
|
|
|
import ApiKeyInput from "./ApiKeyInput";
|
|
|
|
|
import DockerSettings from "./DockerSettings";
|
2026-02-27 15:22:49 +00:00
|
|
|
import AwsSettings from "./AwsSettings";
|
2026-02-27 18:39:20 -08:00
|
|
|
import { useSettings } from "../../hooks/useSettings";
|
2026-02-28 21:18:33 +00:00
|
|
|
import { useUpdates } from "../../hooks/useUpdates";
|
2026-03-01 01:21:33 +00:00
|
|
|
import ClaudeInstructionsModal from "../projects/ClaudeInstructionsModal";
|
|
|
|
|
import EnvVarsModal from "../projects/EnvVarsModal";
|
2026-03-01 15:57:22 +00:00
|
|
|
import { detectHostTimezone } from "../../lib/tauri-commands";
|
2026-03-01 01:21:33 +00:00
|
|
|
import type { EnvVar } from "../../lib/types";
|
2026-02-27 04:29:51 +00:00
|
|
|
|
|
|
|
|
export default function SettingsPanel() {
|
2026-02-27 18:39:20 -08:00
|
|
|
const { appSettings, saveSettings } = useSettings();
|
2026-02-28 21:18:33 +00:00
|
|
|
const { appVersion, checkForUpdates } = useUpdates();
|
2026-02-28 20:42:40 +00:00
|
|
|
const [globalInstructions, setGlobalInstructions] = useState(appSettings?.global_claude_instructions ?? "");
|
2026-03-01 01:21:33 +00:00
|
|
|
const [globalEnvVars, setGlobalEnvVars] = useState<EnvVar[]>(appSettings?.global_custom_env_vars ?? []);
|
2026-02-28 21:18:33 +00:00
|
|
|
const [checkingUpdates, setCheckingUpdates] = useState(false);
|
2026-03-01 15:57:22 +00:00
|
|
|
const [timezone, setTimezone] = useState(appSettings?.timezone ?? "");
|
2026-03-01 01:21:33 +00:00
|
|
|
const [showInstructionsModal, setShowInstructionsModal] = useState(false);
|
|
|
|
|
const [showEnvVarsModal, setShowEnvVarsModal] = useState(false);
|
2026-02-28 20:42:40 +00:00
|
|
|
|
|
|
|
|
// Sync local state when appSettings change
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setGlobalInstructions(appSettings?.global_claude_instructions ?? "");
|
2026-03-01 01:21:33 +00:00
|
|
|
setGlobalEnvVars(appSettings?.global_custom_env_vars ?? []);
|
2026-03-01 15:57:22 +00:00
|
|
|
setTimezone(appSettings?.timezone ?? "");
|
|
|
|
|
}, [appSettings?.global_claude_instructions, appSettings?.global_custom_env_vars, appSettings?.timezone]);
|
|
|
|
|
|
|
|
|
|
// Auto-detect timezone on first load if not yet set
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (appSettings && !appSettings.timezone) {
|
|
|
|
|
detectHostTimezone().then((tz) => {
|
|
|
|
|
setTimezone(tz);
|
|
|
|
|
saveSettings({ ...appSettings, timezone: tz });
|
|
|
|
|
}).catch(() => {});
|
|
|
|
|
}
|
|
|
|
|
}, [appSettings?.timezone]);
|
2026-02-27 18:39:20 -08:00
|
|
|
|
2026-02-28 21:18:33 +00:00
|
|
|
const handleCheckNow = async () => {
|
|
|
|
|
setCheckingUpdates(true);
|
|
|
|
|
try {
|
|
|
|
|
await checkForUpdates();
|
|
|
|
|
} finally {
|
|
|
|
|
setCheckingUpdates(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleAutoCheckToggle = async () => {
|
|
|
|
|
if (!appSettings) return;
|
|
|
|
|
await saveSettings({ ...appSettings, auto_check_updates: !appSettings.auto_check_updates });
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-27 04:29:51 +00:00
|
|
|
return (
|
|
|
|
|
<div className="p-4 space-y-6">
|
|
|
|
|
<h2 className="text-xs font-semibold uppercase text-[var(--text-secondary)]">
|
|
|
|
|
Settings
|
|
|
|
|
</h2>
|
|
|
|
|
<ApiKeyInput />
|
|
|
|
|
<DockerSettings />
|
2026-02-27 15:22:49 +00:00
|
|
|
<AwsSettings />
|
2026-03-01 01:21:33 +00:00
|
|
|
|
2026-03-01 15:57:22 +00:00
|
|
|
{/* Container Timezone */}
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium mb-1">Container Timezone</label>
|
|
|
|
|
<p className="text-xs text-[var(--text-secondary)] mb-1.5">
|
|
|
|
|
Timezone for containers — affects scheduled task timing (IANA format, e.g. America/New_York)
|
|
|
|
|
</p>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={timezone}
|
|
|
|
|
onChange={(e) => setTimezone(e.target.value)}
|
|
|
|
|
onBlur={async () => {
|
|
|
|
|
if (appSettings) {
|
|
|
|
|
await saveSettings({ ...appSettings, timezone: timezone || null });
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
placeholder="UTC"
|
|
|
|
|
className="w-full px-2 py-1 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded focus:outline-none focus:border-[var(--accent)]"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-01 01:21:33 +00:00
|
|
|
{/* Global Claude Instructions */}
|
2026-02-27 18:39:20 -08:00
|
|
|
<div>
|
2026-03-01 01:21:33 +00:00
|
|
|
<label className="block text-sm font-medium mb-1">Claude Instructions</label>
|
2026-02-27 18:39:20 -08:00
|
|
|
<p className="text-xs text-[var(--text-secondary)] mb-1.5">
|
|
|
|
|
Global instructions applied to all projects (written to ~/.claude/CLAUDE.md in containers)
|
|
|
|
|
</p>
|
2026-03-01 01:21:33 +00:00
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<span className="text-xs text-[var(--text-secondary)]">
|
|
|
|
|
{globalInstructions ? "Configured" : "Not set"}
|
|
|
|
|
</span>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setShowInstructionsModal(true)}
|
|
|
|
|
className="text-xs px-2 py-0.5 text-[var(--accent)] hover:text-[var(--accent-hover)] hover:bg-[var(--bg-primary)] rounded transition-colors"
|
|
|
|
|
>
|
|
|
|
|
Edit
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Global Environment Variables */}
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium mb-1">Global Environment Variables</label>
|
|
|
|
|
<p className="text-xs text-[var(--text-secondary)] mb-1.5">
|
|
|
|
|
Applied to all project containers. Per-project variables override global ones with the same key.
|
|
|
|
|
</p>
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<span className="text-xs text-[var(--text-secondary)]">
|
|
|
|
|
{globalEnvVars.length > 0 ? `${globalEnvVars.length} variable${globalEnvVars.length === 1 ? "" : "s"}` : "None"}
|
|
|
|
|
</span>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setShowEnvVarsModal(true)}
|
|
|
|
|
className="text-xs px-2 py-0.5 text-[var(--accent)] hover:text-[var(--accent-hover)] hover:bg-[var(--bg-primary)] rounded transition-colors"
|
|
|
|
|
>
|
|
|
|
|
Edit
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-02-27 18:39:20 -08:00
|
|
|
</div>
|
2026-02-28 21:18:33 +00:00
|
|
|
|
|
|
|
|
{/* Updates section */}
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium mb-2">Updates</label>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
{appVersion && (
|
|
|
|
|
<p className="text-xs text-[var(--text-secondary)]">
|
|
|
|
|
Current version: <span className="text-[var(--text-primary)] font-mono">{appVersion}</span>
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<label className="text-xs text-[var(--text-secondary)]">Auto-check for updates</label>
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleAutoCheckToggle}
|
|
|
|
|
className={`px-2 py-0.5 text-xs rounded transition-colors ${
|
|
|
|
|
appSettings?.auto_check_updates !== false
|
|
|
|
|
? "bg-[var(--success)] text-white"
|
|
|
|
|
: "bg-[var(--bg-primary)] border border-[var(--border-color)] text-[var(--text-secondary)]"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{appSettings?.auto_check_updates !== false ? "ON" : "OFF"}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleCheckNow}
|
|
|
|
|
disabled={checkingUpdates}
|
|
|
|
|
className="px-3 py-1.5 text-xs bg-[var(--bg-primary)] border border-[var(--border-color)] rounded hover:bg-[var(--border-color)] disabled:opacity-50 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
{checkingUpdates ? "Checking..." : "Check now"}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-01 01:21:33 +00:00
|
|
|
|
|
|
|
|
{showInstructionsModal && (
|
|
|
|
|
<ClaudeInstructionsModal
|
|
|
|
|
instructions={globalInstructions}
|
|
|
|
|
disabled={false}
|
|
|
|
|
onSave={async (instructions) => {
|
|
|
|
|
setGlobalInstructions(instructions);
|
|
|
|
|
if (appSettings) {
|
|
|
|
|
await saveSettings({ ...appSettings, global_claude_instructions: instructions || null });
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
onClose={() => setShowInstructionsModal(false)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{showEnvVarsModal && (
|
|
|
|
|
<EnvVarsModal
|
|
|
|
|
envVars={globalEnvVars}
|
|
|
|
|
disabled={false}
|
|
|
|
|
onSave={async (vars) => {
|
|
|
|
|
setGlobalEnvVars(vars);
|
|
|
|
|
if (appSettings) {
|
|
|
|
|
await saveSettings({ ...appSettings, global_custom_env_vars: vars });
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
onClose={() => setShowEnvVarsModal(false)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2026-02-27 04:29:51 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|