diff --git a/src/App.tsx b/src/App.tsx index 3230d38..df1dcd9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,6 +2,7 @@ import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import AppLayout from "./components/layout/AppLayout"; import ProjectForm from "./components/projects/ProjectForm"; import ProjectDashboard from "./components/projects/ProjectDashboard"; +import SettingsPage from "./components/settings/SettingsPage"; function EmptyState() { return ( @@ -20,6 +21,7 @@ function App() { } /> } /> } /> + } /> } /> diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 91d3773..7d1d943 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -58,6 +58,12 @@ export default function Sidebar() {

No projects yet

)} + +
+ + Settings + +
); } diff --git a/src/components/settings/SettingsPage.tsx b/src/components/settings/SettingsPage.tsx new file mode 100644 index 0000000..170a092 --- /dev/null +++ b/src/components/settings/SettingsPage.tsx @@ -0,0 +1,174 @@ +import { useState, useEffect } from "react"; +import { + getTuleapCredentials, + setTuleapCredentials, + deleteTuleapCredentials, + testTuleapConnection, +} from "../../lib/api"; +import type { TuleapCredentialsSafe } from "../../lib/types"; + +export default function SettingsPage() { + const [tuleapUrl, setTuleapUrl] = useState(""); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [existing, setExisting] = useState(null); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + const [saving, setSaving] = useState(false); + const [testing, setTesting] = useState(false); + const [deleting, setDeleting] = useState(false); + + useEffect(() => { + getTuleapCredentials().then((creds) => { + if (creds) { + setExisting(creds); + setTuleapUrl(creds.tuleap_url); + setUsername(creds.username); + } + }); + }, []); + + function clearMessages() { + setError(null); + setSuccess(null); + } + + async function handleSave(e: React.FormEvent) { + e.preventDefault(); + clearMessages(); + setSaving(true); + try { + const creds = await setTuleapCredentials(tuleapUrl, username, password); + setExisting(creds); + setPassword(""); + setSuccess("Credentials saved."); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : String(err)); + } finally { + setSaving(false); + } + } + + async function handleTest() { + clearMessages(); + setTesting(true); + try { + const msg = await testTuleapConnection(); + setSuccess(msg); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : String(err)); + } finally { + setTesting(false); + } + } + + async function handleDelete() { + if (!window.confirm("Delete Tuleap credentials? This cannot be undone.")) return; + clearMessages(); + setDeleting(true); + try { + await deleteTuleapCredentials(); + setExisting(null); + setTuleapUrl(""); + setUsername(""); + setPassword(""); + setSuccess("Credentials deleted."); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : String(err)); + } finally { + setDeleting(false); + } + } + + return ( +
+

Settings

+ +
+

Tuleap credentials

+ +
+
+ + setTuleapUrl(e.target.value)} + required + placeholder="https://tuleap.example.com" + className="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ +
+ + setUsername(e.target.value)} + required + className="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ +
+ + setPassword(e.target.value)} + placeholder={existing ? "Leave empty to keep current" : ""} + className="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ + {error && ( +
+ {error} +
+ )} + + {success && ( +
+ {success} +
+ )} + +
+ + + {existing && ( + + )} +
+
+
+
+ ); +} diff --git a/src/lib/api.ts b/src/lib/api.ts index 6f01319..ca6e5e0 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,5 +1,13 @@ import { invoke } from "@tauri-apps/api/core"; -import type { Project } from "./types"; +import type { + Project, + TuleapCredentialsSafe, + AgentConfig, + FilterGroup, + WatchedTracker, + TrackerField, + ProcessedTicket, +} from "./types"; export async function createProject( name: string, @@ -32,3 +40,45 @@ export async function updateProject( export async function deleteProject(id: string): Promise { return invoke("delete_project", { id }); } + +// Credentials +export async function setTuleapCredentials(tuleapUrl: string, username: string, password: string): Promise { + return invoke("set_tuleap_credentials", { tuleapUrl, username, password }); +} +export async function getTuleapCredentials(): Promise { + return invoke("get_tuleap_credentials"); +} +export async function deleteTuleapCredentials(): Promise { + return invoke("delete_tuleap_credentials"); +} +export async function testTuleapConnection(): Promise { + return invoke("test_tuleap_connection"); +} + +// Trackers +export async function addTracker(projectId: string, trackerId: number, trackerLabel: string, pollingInterval: number, agentConfig: AgentConfig, filters: FilterGroup[]): Promise { + return invoke("add_tracker", { projectId, trackerId, trackerLabel, pollingInterval, agentConfig, filters }); +} +export async function listTrackers(projectId: string): Promise { + return invoke("list_trackers", { projectId }); +} +export async function updateTracker(id: string, pollingInterval: number, agentConfig: AgentConfig, filters: FilterGroup[], enabled: boolean): Promise { + return invoke("update_tracker", { id, pollingInterval, agentConfig, filters, enabled }); +} +export async function removeTracker(id: string): Promise { + return invoke("remove_tracker", { id }); +} +export async function getTrackerFields(trackerId: number): Promise { + return invoke("get_tracker_fields", { trackerId }); +} + +// Tickets & Polling +export async function listProcessedTickets(projectId: string): Promise { + return invoke("list_processed_tickets", { projectId }); +} +export async function manualPoll(trackerId: string): Promise { + return invoke("manual_poll", { trackerId }); +} +export async function getQueueStatus(projectId: string): Promise { + return invoke("get_queue_status", { projectId }); +} diff --git a/src/lib/types.ts b/src/lib/types.ts index d5161cc..85ebc7d 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -6,3 +6,66 @@ export interface Project { base_branch: string; created_at: string; } + +export interface TuleapCredentialsSafe { + id: string; + tuleap_url: string; + username: string; +} + +export interface AgentConfig { + analyst_command: string; + analyst_args: string[]; + developer_command: string; + developer_args: string[]; +} + +export interface Filter { + field: string; + operator: string; + value: string[]; +} + +export interface FilterGroup { + conditions: Filter[]; +} + +export interface TrackerField { + field_id: number; + label: string; + field_type: string; + values: FieldValue[]; +} + +export interface FieldValue { + id: number; + label: string; +} + +export interface WatchedTracker { + id: string; + project_id: string; + tracker_id: number; + tracker_label: string; + polling_interval: number; + agent_config: AgentConfig; + filters: FilterGroup[]; + enabled: boolean; + last_polled_at: string | null; + created_at: string; +} + +export interface ProcessedTicket { + id: string; + tracker_id: string; + artifact_id: number; + artifact_title: string; + artifact_data: string; + status: string; + analyst_report: string | null; + developer_report: string | null; + worktree_path: string | null; + branch_name: string | null; + detected_at: string; + processed_at: string | null; +}