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
+
+
+
+
+ );
+}
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;
+}