refactor: rename files to PascalCase components and kebab-case APIs (Task 4.4)
- Rename all component files to PascalCase matching exported names - Move components into feature directories (git/, session/, project/, terminal/, workspace/, ui/, layout/) - Rename all page files to PascalCase with Page suffix - Rename all API files to kebab-case - Update all imports across codebase with corrected relative depths - Preserve git history via git mv Quality gates: tsc (pass), eslint (pass), 66/74 tests pass (8 pre-existing failures) Refs: repo-restructure Task 4.4
This commit is contained in:
@@ -19,7 +19,9 @@ function checkFileSize(filePath, maxLines = 300) {
|
|||||||
const content = fs.readFileSync(filePath, "utf-8");
|
const content = fs.readFileSync(filePath, "utf-8");
|
||||||
const lines = content.split("\n").length;
|
const lines = content.split("\n").length;
|
||||||
if (lines > maxLines) {
|
if (lines > maxLines) {
|
||||||
console.error(`❌ OVERSIZED (${lines} lines): ${path.relative(SRC_DIR, filePath)}`);
|
console.error(
|
||||||
|
`❌ OVERSIZED (${lines} lines): ${path.relative(SRC_DIR, filePath)}`,
|
||||||
|
);
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
deleteConfigFolder,
|
deleteConfigFolder,
|
||||||
listConfigFolders,
|
listConfigFolders,
|
||||||
updateConfigFolder,
|
updateConfigFolder,
|
||||||
} from "../api/config_folders";
|
} from "../api/config-folders";
|
||||||
|
|
||||||
const mockGet = vi.fn();
|
const mockGet = vi.fn();
|
||||||
const mockPost = vi.fn();
|
const mockPost = vi.fn();
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
listToolTypes,
|
listToolTypes,
|
||||||
updateToolType,
|
updateToolType,
|
||||||
validateToolType,
|
validateToolType,
|
||||||
} from "../api/tool_types";
|
} from "../api/tool-types";
|
||||||
|
|
||||||
const mockGet = vi.fn();
|
const mockGet = vi.fn();
|
||||||
const mockPost = vi.fn();
|
const mockPost = vi.fn();
|
||||||
+1
-1
@@ -2,7 +2,7 @@ import { render, screen } from "@testing-library/react";
|
|||||||
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import { ProtectedRoute } from "./protected-route";
|
import { ProtectedRoute } from "./ProtectedRoute";
|
||||||
|
|
||||||
const mockUseAuth = vi.fn();
|
const mockUseAuth = vi.fn();
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Session } from "../../../types/session";
|
import type { Session } from "../../../types/session";
|
||||||
import { Icon } from "../../../components/icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
|
|
||||||
interface ActiveSessionsListProps {
|
interface ActiveSessionsListProps {
|
||||||
sessions: Session[];
|
sessions: Session[];
|
||||||
@@ -28,7 +28,11 @@ export const ActiveSessionsList = ({
|
|||||||
<article className="card session-card" key={session.id}>
|
<article className="card session-card" key={session.id}>
|
||||||
<div className="stack-sm">
|
<div className="stack-sm">
|
||||||
<div className="row row-tight">
|
<div className="row row-tight">
|
||||||
<h3>{session.display_name || session.tool_type_name || "Unnamed Session"}</h3>
|
<h3>
|
||||||
|
{session.display_name ||
|
||||||
|
session.tool_type_name ||
|
||||||
|
"Unnamed Session"}
|
||||||
|
</h3>
|
||||||
<span className={`status-badge ${session.status}`}>
|
<span className={`status-badge ${session.status}`}>
|
||||||
{session.status}
|
{session.status}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState } from "react";
|
|||||||
import type { Project } from "../../../types/project";
|
import type { Project } from "../../../types/project";
|
||||||
import type { GitRepository } from "../../../types/git-repository";
|
import type { GitRepository } from "../../../types/git-repository";
|
||||||
import type { ToolType } from "../../../types/tool-type";
|
import type { ToolType } from "../../../types/tool-type";
|
||||||
import { Icon } from "../../../components/icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
|
|
||||||
interface QuickCreateFormProps {
|
interface QuickCreateFormProps {
|
||||||
projects: Project[];
|
projects: Project[];
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ export const RecentSessionsSection = ({
|
|||||||
<article className="recent-session-item" key={session.id}>
|
<article className="recent-session-item" key={session.id}>
|
||||||
<div className="recent-session-info">
|
<div className="recent-session-info">
|
||||||
<span className="recent-session-name">
|
<span className="recent-session-name">
|
||||||
{session.display_name || session.tool_type_name || "Unnamed Session"}
|
{session.display_name ||
|
||||||
|
session.tool_type_name ||
|
||||||
|
"Unnamed Session"}
|
||||||
</span>
|
</span>
|
||||||
<span className="muted">
|
<span className="muted">
|
||||||
{session.project_name} · {session.tool_type_name}
|
{session.project_name} · {session.tool_type_name}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import styles from "./features/git/CommitDialog.module.css";
|
import styles from "./features/git/CommitDialog.module.css";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
|
|
||||||
interface CommitDialogProps {
|
interface CommitDialogProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { commitChanges } from "../api/git_repositories";
|
import { commitChanges } from "../../../api/git-repositories";
|
||||||
import styles from "./features/git/CommitPanel.module.css";
|
import styles from "./features/git/CommitPanel.module.css";
|
||||||
|
|
||||||
interface CommitPanelProps {
|
interface CommitPanelProps {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { Icon } from "../../icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
import { apiClient } from "../../../api/client";
|
import { apiClient } from "../../../api/client";
|
||||||
import type { GitStatus } from "../../../types/git-repository";
|
import type { GitStatus } from "../../../types/git-repository";
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -1,13 +1,13 @@
|
|||||||
import styles from "./features/git/FileEditor.module.css";
|
import styles from "./features/git/FileEditor.module.css";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { apiClient } from "../api/client";
|
import { apiClient } from "../../../api/client";
|
||||||
import { useAuth } from "../state/auth";
|
import { useAuth } from "../../../state/auth";
|
||||||
import { CodeEditor } from "../components/code-editor";
|
import { CodeEditor } from "../../ui/CodeEditor";
|
||||||
import { CommitDialog } from "../components/commit-dialog";
|
import { CommitDialog } from "./CommitDialog";
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
import { SyntaxHighlighter } from "../components/syntax-highlighter";
|
import { SyntaxHighlighter } from "./SyntaxHighlighter";
|
||||||
import { detectLanguage } from "../utils/language";
|
import { detectLanguage } from "../../../utils/language";
|
||||||
|
|
||||||
interface FileEditorProps {
|
interface FileEditorProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
+3
-3
@@ -8,9 +8,9 @@ import {
|
|||||||
pullRepository,
|
pullRepository,
|
||||||
pushRepository,
|
pushRepository,
|
||||||
type GitStatus,
|
type GitStatus,
|
||||||
} from "../api/git_repositories";
|
} from "../../../api/git-repositories";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
import { MergeDialog } from "./merge-dialog";
|
import { MergeDialog } from "./MergeDialog";
|
||||||
import styles from "./features/git/GitToolbar.module.css";
|
import styles from "./features/git/GitToolbar.module.css";
|
||||||
|
|
||||||
interface GitToolbarProps {
|
interface GitToolbarProps {
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import styles from "./features/git/MergeDialog.module.css";
|
import styles from "./features/git/MergeDialog.module.css";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { mergeBranches } from "../api/git_repositories";
|
import { mergeBranches } from "../../../api/git-repositories";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
|
|
||||||
interface MergeDialogProps {
|
interface MergeDialogProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
import { highlightCode, loadLanguage } from "../utils/language";
|
import { highlightCode, loadLanguage } from "../../../utils/language";
|
||||||
|
|
||||||
interface SyntaxHighlighterProps {
|
interface SyntaxHighlighterProps {
|
||||||
code: string;
|
code: string;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { GitRepository } from "../../../types/git-repository";
|
import type { GitRepository } from "../../../types/git-repository";
|
||||||
import type { GitStatus } from "../../../api/git_repositories";
|
import type { GitStatus } from "../../../api/git-repositories";
|
||||||
import type { ToolType } from "../../../types/tool-type";
|
import type { ToolType } from "../../../types/tool-type";
|
||||||
import { FileBrowser } from "./FileBrowser";
|
import { FileBrowser } from "./FileBrowser";
|
||||||
import { CommitPanel } from "../../../components/commit-panel";
|
import { CommitPanel } from "../git/CommitPanel";
|
||||||
import { InstanceList } from "../../../components/instance-list";
|
import { InstanceList } from "../session/InstanceList";
|
||||||
|
|
||||||
interface WorkspaceSidebarProps {
|
interface WorkspaceSidebarProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -29,10 +29,7 @@ export const WorkspaceSidebar = ({
|
|||||||
<div className="sidebar-section">
|
<div className="sidebar-section">
|
||||||
<label className="form-field">
|
<label className="form-field">
|
||||||
Repository
|
Repository
|
||||||
<select
|
<select value={repoId} onChange={(e) => onRepoChange(e.target.value)}>
|
||||||
value={repoId}
|
|
||||||
onChange={(e) => onRepoChange(e.target.value)}
|
|
||||||
>
|
|
||||||
{repositories.map((repo) => (
|
{repositories.map((repo) => (
|
||||||
<option key={repo.id} value={repo.id}>
|
<option key={repo.id} value={repo.id}>
|
||||||
{repo.name}
|
{repo.name}
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import { RepositoriesSettingsTab } from "./repositories-settings-tab";
|
import { RepositoriesSettingsTab } from "./RepositoriesSettingsTab";
|
||||||
import * as gitRepositoriesApi from "../api/git_repositories";
|
import * as gitRepositoriesApi from "../../../api/git-repositories";
|
||||||
|
|
||||||
const mockRepositories = [
|
const mockRepositories = [
|
||||||
{
|
{
|
||||||
+4
-4
@@ -1,10 +1,10 @@
|
|||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
import type { GitRepository } from "../types/git-repository";
|
import type { GitRepository } from "../../../types/git-repository";
|
||||||
import { deleteRepository, listRepositories } from "../api/git_repositories";
|
import { deleteRepository, listRepositories } from "../../../api/git-repositories";
|
||||||
import { RepositoryCreateDialog } from "./repository-create-dialog";
|
import { RepositoryCreateDialog } from "./RepositoryCreateDialog";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
|
|
||||||
export const RepositoriesSettingsTab: React.FC = () => {
|
export const RepositoriesSettingsTab: React.FC = () => {
|
||||||
const { projectId } = useParams<{ projectId: string }>();
|
const { projectId } = useParams<{ projectId: string }>();
|
||||||
+3
-3
@@ -3,9 +3,9 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import type {
|
import type {
|
||||||
GitRepositoryCreate,
|
GitRepositoryCreate,
|
||||||
URLParseResult,
|
URLParseResult,
|
||||||
} from "../types/git-repository";
|
} from "../../../types/git-repository";
|
||||||
import { createRepository, parseGitUrl } from "../api/git_repositories";
|
import { createRepository, parseGitUrl } from "../../../api/git-repositories";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
|
|
||||||
type CreateMode = "clone" | "blank";
|
type CreateMode = "clone" | "blank";
|
||||||
type UrlValidationStatus =
|
type UrlValidationStatus =
|
||||||
@@ -17,7 +17,7 @@ vi.mock("@/api/settings", () => ({
|
|||||||
updateUserConfig: vi.fn(),
|
updateUserConfig: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { listRepositories } from "@/api/git_repositories";
|
import { listRepositories } from "@/api/git-repositories";
|
||||||
import { createInstance } from "@/api/sessions";
|
import { createInstance } from "@/api/sessions";
|
||||||
|
|
||||||
const mockProjects = [
|
const mockProjects = [
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { listRepositories } from "@/api/git_repositories";
|
import { listRepositories } from "@/api/git-repositories";
|
||||||
import { createInstance, startInstance } from "@/api/sessions";
|
import { createInstance, startInstance } from "@/api/sessions";
|
||||||
import { updateUserConfig } from "@/api/settings";
|
import { updateUserConfig } from "@/api/settings";
|
||||||
import { Icon } from "@/components/icon";
|
import { Icon } from "@/components/ui/Icon";
|
||||||
import type { Project } from "@/types/project";
|
import type { Project } from "@/types/project";
|
||||||
import type { GitRepository } from "@/types/git-repository";
|
import type { GitRepository } from "@/types/git-repository";
|
||||||
import type { ToolType } from "@/types/tool-type";
|
import type { ToolType } from "@/types/tool-type";
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
import type { ToolInstance } from "../types/tool-instance";
|
import type { ToolInstance } from "../../../types/tool-instance";
|
||||||
import type { ToolType } from "../types/tool-type";
|
import type { ToolType } from "../../../types/tool-type";
|
||||||
import {
|
import {
|
||||||
checkInstanceHealth,
|
checkInstanceHealth,
|
||||||
createInstance,
|
createInstance,
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
restartInstance,
|
restartInstance,
|
||||||
startInstance,
|
startInstance,
|
||||||
stopInstance,
|
stopInstance,
|
||||||
} from "../api/sessions";
|
} from "../../../api/sessions";
|
||||||
import styles from "./features/session/InstanceList.module.css";
|
import styles from "./features/session/InstanceList.module.css";
|
||||||
|
|
||||||
const API_BASE_URL =
|
const API_BASE_URL =
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Icon } from "@/components/icon";
|
import { Icon } from "@/components/ui/Icon";
|
||||||
import type { Session } from "@/types/session";
|
import type { Session } from "@/types/session";
|
||||||
|
|
||||||
interface SessionCardProps {
|
interface SessionCardProps {
|
||||||
|
|||||||
+2
-2
@@ -5,11 +5,11 @@ import { SerializeAddon } from "xterm-addon-serialize";
|
|||||||
import { WebLinksAddon } from "xterm-addon-web-links";
|
import { WebLinksAddon } from "xterm-addon-web-links";
|
||||||
import "xterm/css/xterm.css";
|
import "xterm/css/xterm.css";
|
||||||
|
|
||||||
import { useTerminalConnection } from "../hooks/use-terminal-connection";
|
import { useTerminalConnection } from "../../../hooks/use-terminal-connection";
|
||||||
import type {
|
import type {
|
||||||
ServerControlMessage,
|
ServerControlMessage,
|
||||||
TerminalConnectionState,
|
TerminalConnectionState,
|
||||||
} from "../types/terminal";
|
} from "../../../types/terminal";
|
||||||
import styles from "./features/terminal/TerminalComponent.module.css";
|
import styles from "./features/terminal/TerminalComponent.module.css";
|
||||||
|
|
||||||
interface TerminalProps {
|
interface TerminalProps {
|
||||||
@@ -12,14 +12,20 @@ interface ToolConfigFormProps {
|
|||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToolConfigForm = ({ editingConfig, onSubmit, onCancel }: ToolConfigFormProps) => {
|
export const ToolConfigForm = ({
|
||||||
|
editingConfig,
|
||||||
|
onSubmit,
|
||||||
|
onCancel,
|
||||||
|
}: ToolConfigFormProps) => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
key: editingConfig?.key ?? "",
|
key: editingConfig?.key ?? "",
|
||||||
value: editingConfig?.value ?? "",
|
value: editingConfig?.value ?? "",
|
||||||
config_type: editingConfig?.config_type ?? "env",
|
config_type: editingConfig?.config_type ?? "env",
|
||||||
file_path: editingConfig?.file_path ?? "",
|
file_path: editingConfig?.file_path ?? "",
|
||||||
});
|
});
|
||||||
const [saveStatus, setSaveStatus] = useState<"idle" | "saving" | "saved" | "error">("idle");
|
const [saveStatus, setSaveStatus] = useState<
|
||||||
|
"idle" | "saving" | "saved" | "error"
|
||||||
|
>("idle");
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -53,7 +59,9 @@ export const ToolConfigForm = ({ editingConfig, onSubmit, onCancel }: ToolConfig
|
|||||||
<select
|
<select
|
||||||
id="config-type"
|
id="config-type"
|
||||||
value={formData.config_type}
|
value={formData.config_type}
|
||||||
onChange={(e) => setFormData({ ...formData, config_type: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, config_type: e.target.value })
|
||||||
|
}
|
||||||
className="form-input"
|
className="form-input"
|
||||||
>
|
>
|
||||||
<option value="env">Environment Variable</option>
|
<option value="env">Environment Variable</option>
|
||||||
@@ -67,7 +75,9 @@ export const ToolConfigForm = ({ editingConfig, onSubmit, onCancel }: ToolConfig
|
|||||||
id="config-file-path"
|
id="config-file-path"
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.file_path}
|
value={formData.file_path}
|
||||||
onChange={(e) => setFormData({ ...formData, file_path: e.target.value })}
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, file_path: e.target.value })
|
||||||
|
}
|
||||||
placeholder="e.g., /app/config.json"
|
placeholder="e.g., /app/config.json"
|
||||||
className="form-input"
|
className="form-input"
|
||||||
required
|
required
|
||||||
@@ -79,19 +89,24 @@ export const ToolConfigForm = ({ editingConfig, onSubmit, onCancel }: ToolConfig
|
|||||||
<textarea
|
<textarea
|
||||||
id="config-value"
|
id="config-value"
|
||||||
value={formData.value}
|
value={formData.value}
|
||||||
onChange={(e) => setFormData({ ...formData, value: e.target.value })}
|
onChange={(e) =>
|
||||||
placeholder={formData.config_type === "env" ? "Enter value..." : "Enter file contents..."}
|
setFormData({ ...formData, value: e.target.value })
|
||||||
|
}
|
||||||
|
placeholder={
|
||||||
|
formData.config_type === "env"
|
||||||
|
? "Enter value..."
|
||||||
|
: "Enter file contents..."
|
||||||
|
}
|
||||||
className="form-input"
|
className="form-input"
|
||||||
rows={formData.config_type === "file" ? 8 : 2}
|
rows={formData.config_type === "file" ? 8 : 2}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="row" style={{ gap: "0.5rem", justifyContent: "flex-end" }}>
|
<div
|
||||||
<button
|
className="row"
|
||||||
type="button"
|
style={{ gap: "0.5rem", justifyContent: "flex-end" }}
|
||||||
className="secondary-button"
|
>
|
||||||
onClick={onCancel}
|
<button type="button" className="secondary-button" onClick={onCancel}>
|
||||||
>
|
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className="primary-button">
|
<button type="submit" className="primary-button">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Icon } from "../../../components/icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
import type { ToolConfig } from "../../../types/tool-config";
|
import type { ToolConfig } from "../../../types/tool-config";
|
||||||
|
|
||||||
interface ToolConfigListProps {
|
interface ToolConfigListProps {
|
||||||
|
|||||||
@@ -1,23 +1,46 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Icon } from "../../../components/icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
import type { ToolType, CreateToolTypeRequest, UpdateToolTypeRequest } from "../../../types/tool-type";
|
import type {
|
||||||
|
ToolType,
|
||||||
|
CreateToolTypeRequest,
|
||||||
|
UpdateToolTypeRequest,
|
||||||
|
} from "../../../types/tool-type";
|
||||||
|
|
||||||
interface ToolTypeFormProps {
|
interface ToolTypeFormProps {
|
||||||
mode: "create" | "edit";
|
mode: "create" | "edit";
|
||||||
toolType?: ToolType | null;
|
toolType?: ToolType | null;
|
||||||
onSubmit: (input: CreateToolTypeRequest | UpdateToolTypeRequest) => Promise<void>;
|
onSubmit: (
|
||||||
|
input: CreateToolTypeRequest | UpdateToolTypeRequest,
|
||||||
|
) => Promise<void>;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToolTypeForm = ({ mode, toolType, onSubmit, onCancel }: ToolTypeFormProps) => {
|
export const ToolTypeForm = ({
|
||||||
|
mode,
|
||||||
|
toolType,
|
||||||
|
onSubmit,
|
||||||
|
onCancel,
|
||||||
|
}: ToolTypeFormProps) => {
|
||||||
const [formName, setFormName] = useState(toolType?.name ?? "");
|
const [formName, setFormName] = useState(toolType?.name ?? "");
|
||||||
const [formDisplayName, setFormDisplayName] = useState(toolType?.display_name ?? "");
|
const [formDisplayName, setFormDisplayName] = useState(
|
||||||
const [formDescription, setFormDescription] = useState(toolType?.description ?? "");
|
toolType?.display_name ?? "",
|
||||||
|
);
|
||||||
|
const [formDescription, setFormDescription] = useState(
|
||||||
|
toolType?.description ?? "",
|
||||||
|
);
|
||||||
const [formCategory, setFormCategory] = useState(toolType?.category ?? "");
|
const [formCategory, setFormCategory] = useState(toolType?.category ?? "");
|
||||||
const [formInterfaces, setFormInterfaces] = useState<string[]>(toolType?.interfaces ?? []);
|
const [formInterfaces, setFormInterfaces] = useState<string[]>(
|
||||||
const [formPort, setFormPort] = useState(toolType?.default_port?.toString() ?? "");
|
toolType?.interfaces ?? [],
|
||||||
const [formTemplate, setFormTemplate] = useState(toolType?.compose_template ?? "");
|
);
|
||||||
const [formVariables, setFormVariables] = useState(toolType?.required_variables?.join(", ") ?? "");
|
const [formPort, setFormPort] = useState(
|
||||||
|
toolType?.default_port?.toString() ?? "",
|
||||||
|
);
|
||||||
|
const [formTemplate, setFormTemplate] = useState(
|
||||||
|
toolType?.compose_template ?? "",
|
||||||
|
);
|
||||||
|
const [formVariables, setFormVariables] = useState(
|
||||||
|
toolType?.required_variables?.join(", ") ?? "",
|
||||||
|
);
|
||||||
const [formError, setFormError] = useState<string | null>(null);
|
const [formError, setFormError] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
@@ -33,7 +56,10 @@ export const ToolTypeForm = ({ mode, toolType, onSubmit, onCancel }: ToolTypeFor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const variables = formVariables.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
|
const variables = formVariables
|
||||||
|
.split(",")
|
||||||
|
.map((v) => v.trim())
|
||||||
|
.filter((v) => v.length > 0);
|
||||||
const base = {
|
const base = {
|
||||||
display_name: formDisplayName.trim(),
|
display_name: formDisplayName.trim(),
|
||||||
description: formDescription.trim() || undefined,
|
description: formDescription.trim() || undefined,
|
||||||
@@ -46,13 +72,18 @@ export const ToolTypeForm = ({ mode, toolType, onSubmit, onCancel }: ToolTypeFor
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (mode === "create") {
|
if (mode === "create") {
|
||||||
await onSubmit({ name: formName.trim(), ...base } as CreateToolTypeRequest);
|
await onSubmit({
|
||||||
|
name: formName.trim(),
|
||||||
|
...base,
|
||||||
|
} as CreateToolTypeRequest);
|
||||||
} else {
|
} else {
|
||||||
await onSubmit(base as UpdateToolTypeRequest);
|
await onSubmit(base as UpdateToolTypeRequest);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const axiosError = err as { response?: { data?: { detail?: string } } };
|
const axiosError = err as { response?: { data?: { detail?: string } } };
|
||||||
setFormError(axiosError?.response?.data?.detail || "Failed to save tool type");
|
setFormError(
|
||||||
|
axiosError?.response?.data?.detail || "Failed to save tool type",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,42 +100,89 @@ export const ToolTypeForm = ({ mode, toolType, onSubmit, onCancel }: ToolTypeFor
|
|||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Name (unique identifier)</label>
|
<label>Name (unique identifier)</label>
|
||||||
<input type="text" value={formName} onChange={(e) => setFormName(e.target.value)} disabled={mode === "edit"} placeholder="e.g., code-server" />
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formName}
|
||||||
|
onChange={(e) => setFormName(e.target.value)}
|
||||||
|
disabled={mode === "edit"}
|
||||||
|
placeholder="e.g., code-server"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Display Name</label>
|
<label>Display Name</label>
|
||||||
<input type="text" value={formDisplayName} onChange={(e) => setFormDisplayName(e.target.value)} placeholder="e.g., VS Code Server" />
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formDisplayName}
|
||||||
|
onChange={(e) => setFormDisplayName(e.target.value)}
|
||||||
|
placeholder="e.g., VS Code Server"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Description</label>
|
<label>Description</label>
|
||||||
<input type="text" value={formDescription} onChange={(e) => setFormDescription(e.target.value)} placeholder="Optional description" />
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formDescription}
|
||||||
|
onChange={(e) => setFormDescription(e.target.value)}
|
||||||
|
placeholder="Optional description"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Category</label>
|
<label>Category</label>
|
||||||
<input type="text" value={formCategory} onChange={(e) => setFormCategory(e.target.value)} placeholder="e.g., editor, notebook, ai-assistant" />
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formCategory}
|
||||||
|
onChange={(e) => setFormCategory(e.target.value)}
|
||||||
|
placeholder="e.g., editor, notebook, ai-assistant"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Interfaces</label>
|
<label>Interfaces</label>
|
||||||
<div className="checkbox-group">
|
<div className="checkbox-group">
|
||||||
<label className="checkbox-label">
|
<label className="checkbox-label">
|
||||||
<input type="checkbox" checked={formInterfaces.includes("web")} onChange={() => toggleInterface("web")} /> Web
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formInterfaces.includes("web")}
|
||||||
|
onChange={() => toggleInterface("web")}
|
||||||
|
/>{" "}
|
||||||
|
Web
|
||||||
</label>
|
</label>
|
||||||
<label className="checkbox-label">
|
<label className="checkbox-label">
|
||||||
<input type="checkbox" checked={formInterfaces.includes("terminal")} onChange={() => toggleInterface("terminal")} /> Terminal
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formInterfaces.includes("terminal")}
|
||||||
|
onChange={() => toggleInterface("terminal")}
|
||||||
|
/>{" "}
|
||||||
|
Terminal
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Default Port *</label>
|
<label>Default Port *</label>
|
||||||
<input type="number" value={formPort} onChange={(e) => setFormPort(e.target.value)} placeholder="e.g., 8443" required />
|
<input
|
||||||
|
type="number"
|
||||||
|
value={formPort}
|
||||||
|
onChange={(e) => setFormPort(e.target.value)}
|
||||||
|
placeholder="e.g., 8443"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Compose Template (YAML)</label>
|
<label>Compose Template (YAML)</label>
|
||||||
<textarea value={formTemplate} onChange={(e) => setFormTemplate(e.target.value)} rows={10} placeholder="version: '3.8' services: app: image: ..." />
|
<textarea
|
||||||
|
value={formTemplate}
|
||||||
|
onChange={(e) => setFormTemplate(e.target.value)}
|
||||||
|
rows={10}
|
||||||
|
placeholder="version: '3.8' services: app: image: ..."
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Required Variables (comma-separated)</label>
|
<label>Required Variables (comma-separated)</label>
|
||||||
<input type="text" value={formVariables} onChange={(e) => setFormVariables(e.target.value)} placeholder="REPO_PATH, TOOL_NAME" />
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formVariables}
|
||||||
|
onChange={(e) => setFormVariables(e.target.value)}
|
||||||
|
placeholder="REPO_PATH, TOOL_NAME"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{formError && <p className="text-error">{formError}</p>}
|
{formError && <p className="text-error">{formError}</p>}
|
||||||
<div className="dialog-actions">
|
<div className="dialog-actions">
|
||||||
@@ -112,7 +190,11 @@ export const ToolTypeForm = ({ mode, toolType, onSubmit, onCancel }: ToolTypeFor
|
|||||||
<Icon name={mode === "create" ? "add" : "save"} size="sm" />
|
<Icon name={mode === "create" ? "add" : "save"} size="sm" />
|
||||||
{mode === "create" ? "Create" : "Update"}
|
{mode === "create" ? "Create" : "Update"}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" onClick={onCancel} className="button-secondary">
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onCancel}
|
||||||
|
className="button-secondary"
|
||||||
|
>
|
||||||
<Icon name="cancel" size="sm" /> Cancel
|
<Icon name="cancel" size="sm" /> Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Icon } from "../../../components/icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
import type { ToolType } from "../../../types/tool-type";
|
import type { ToolType } from "../../../types/tool-type";
|
||||||
|
|
||||||
interface ToolTypeListProps {
|
interface ToolTypeListProps {
|
||||||
@@ -55,34 +55,32 @@ export const ToolTypeList = ({
|
|||||||
<Icon name="delete" size="sm" />
|
<Icon name="delete" size="sm" />
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{deleteConfirmId === toolType.id && (
|
|
||||||
<div className="dialog-overlay">
|
|
||||||
<div className="dialog">
|
|
||||||
<p>
|
|
||||||
Delete tool type "{toolType.display_name}"?
|
|
||||||
</p>
|
|
||||||
<div className="dialog-actions">
|
|
||||||
<button
|
|
||||||
onClick={() => onDelete(toolType.id)}
|
|
||||||
className="button-danger"
|
|
||||||
>
|
|
||||||
<Icon name="delete" size="sm" />
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setDeleteConfirmId(null)}>
|
|
||||||
<Icon name="cancel" size="sm" />
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
{deleteConfirmId === toolType.id && (
|
||||||
);
|
<div className="dialog-overlay">
|
||||||
};
|
<div className="dialog">
|
||||||
|
<p>Delete tool type "{toolType.display_name}"?</p>
|
||||||
|
<div className="dialog-actions">
|
||||||
|
<button
|
||||||
|
onClick={() => onDelete(toolType.id)}
|
||||||
|
className="button-danger"
|
||||||
|
>
|
||||||
|
<Icon name="delete" size="sm" />
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<button onClick={() => setDeleteConfirmId(null)}>
|
||||||
|
<Icon name="cancel" size="sm" />
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Icon } from "../../../components/icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
import {
|
import {
|
||||||
createConfigFolder,
|
createConfigFolder,
|
||||||
deleteConfigFolder,
|
deleteConfigFolder,
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
type ConfigFolder,
|
type ConfigFolder,
|
||||||
type CreateConfigFolderRequest,
|
type CreateConfigFolderRequest,
|
||||||
type UpdateConfigFolderRequest,
|
type UpdateConfigFolderRequest,
|
||||||
} from "../../../api/config_folders";
|
} from "../../../api/config-folders";
|
||||||
|
|
||||||
export const ConfigFoldersTab = () => {
|
export const ConfigFoldersTab = () => {
|
||||||
const [folders, setFolders] = useState<ConfigFolder[]>([]);
|
const [folders, setFolders] = useState<ConfigFolder[]>([]);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Icon } from "../../../components/icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
import {
|
import {
|
||||||
createToolConfig,
|
createToolConfig,
|
||||||
deleteToolConfig,
|
deleteToolConfig,
|
||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
updateToolConfig,
|
updateToolConfig,
|
||||||
type CreateToolConfigRequest,
|
type CreateToolConfigRequest,
|
||||||
type ToolConfig,
|
type ToolConfig,
|
||||||
} from "../../../api/tool_configs";
|
} from "../../../api/tool-configs";
|
||||||
import { listToolTypes, type ToolType } from "../../../api/tool_types";
|
import { listToolTypes, type ToolType } from "../../../api/tool-types";
|
||||||
|
|
||||||
export const ToolConfigsTab = () => {
|
export const ToolConfigsTab = () => {
|
||||||
const [configs, setConfigs] = useState<ToolConfig[]>([]);
|
const [configs, setConfigs] = useState<ToolConfig[]>([]);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ vi.mock("../../../api/tool_types", () => ({
|
|||||||
updateToolType: vi.fn(),
|
updateToolType: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { listToolTypes } from "../../../api/tool_types";
|
import { listToolTypes } from "../../../api/tool-types";
|
||||||
|
|
||||||
describe("ToolTypesTab", () => {
|
describe("ToolTypesTab", () => {
|
||||||
it("renders loading state initially", () => {
|
it("renders loading state initially", () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { Icon } from "../../../components/icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
import {
|
import {
|
||||||
createToolType,
|
createToolType,
|
||||||
deleteToolType,
|
deleteToolType,
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
type ReadinessProbe,
|
type ReadinessProbe,
|
||||||
type ToolType,
|
type ToolType,
|
||||||
type UpdateToolTypeRequest,
|
type UpdateToolTypeRequest,
|
||||||
} from "../../../api/tool_types";
|
} from "../../../api/tool-types";
|
||||||
|
|
||||||
export const ToolTypesTab = () => {
|
export const ToolTypesTab = () => {
|
||||||
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "../../ui/Icon";
|
||||||
|
|
||||||
interface WorkspaceHeaderProps {
|
interface WorkspaceHeaderProps {
|
||||||
project: {
|
project: {
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
import { Link, NavLink, Outlet } from "react-router-dom";
|
import { Link, NavLink, Outlet } from "react-router-dom";
|
||||||
|
|
||||||
import { getUserSessions } from "../api/sessions";
|
import { getUserSessions } from "../../api/sessions";
|
||||||
import type { Session } from "../types/session";
|
import type { Session } from "../../types/session";
|
||||||
import { useTheme } from "../hooks/use-theme";
|
import { useTheme } from "../../hooks/use-theme";
|
||||||
import { useAuth } from "../state/auth";
|
import { useAuth } from "../../state/auth";
|
||||||
import { useSessions } from "../state/sessions";
|
import { useSessions } from "../../state/sessions";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "../ui/Icon";
|
||||||
import type { IconName } from "../utils/icons";
|
import type { IconName } from "../../utils/icons";
|
||||||
import styles from "./layout/AppShell.module.css";
|
import styles from "./layout/AppShell.module.css";
|
||||||
|
|
||||||
const NAV_ITEMS: { to: string; label: string; icon: IconName }[] = [
|
const NAV_ITEMS: { to: string; label: string; icon: IconName }[] = [
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import Editor from "react-simple-code-editor";
|
import Editor from "react-simple-code-editor";
|
||||||
import { highlightCode, loadLanguage } from "../utils/language";
|
import { highlightCode, loadLanguage } from "../../utils/language";
|
||||||
|
|
||||||
interface CodeEditorProps {
|
interface CodeEditorProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export { Icon } from "./Icon";
|
||||||
export { LoadingState } from "./LoadingState";
|
export { LoadingState } from "./LoadingState";
|
||||||
export { ErrorState } from "./ErrorState";
|
export { ErrorState } from "./ErrorState";
|
||||||
export { StatusBadge } from "./StatusBadge";
|
export { StatusBadge } from "./StatusBadge";
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import type { Session } from "../types/session";
|
|||||||
|
|
||||||
export function useDashboardActions(onRefresh: () => Promise<void>) {
|
export function useDashboardActions(onRefresh: () => Promise<void>) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [saveState, setSaveState] = useState<"idle" | "saving" | "error">("idle");
|
const [saveState, setSaveState] = useState<"idle" | "saving" | "error">(
|
||||||
|
"idle",
|
||||||
|
);
|
||||||
const [actionBusy, setActionBusy] = useState<string | null>(null);
|
const [actionBusy, setActionBusy] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleCreate = useCallback(
|
const handleCreate = useCallback(
|
||||||
@@ -60,7 +62,11 @@ export function useDashboardActions(onRefresh: () => Promise<void>) {
|
|||||||
async (session: Session) => {
|
async (session: Session) => {
|
||||||
setActionBusy(session.id);
|
setActionBusy(session.id);
|
||||||
try {
|
try {
|
||||||
await stopInstance(session.project_id, session.repository_id, session.id);
|
await stopInstance(
|
||||||
|
session.project_id,
|
||||||
|
session.repository_id,
|
||||||
|
session.id,
|
||||||
|
);
|
||||||
await onRefresh();
|
await onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
setActionBusy(null);
|
setActionBusy(null);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|||||||
import { MemoryRouter } from "react-router-dom";
|
import { MemoryRouter } from "react-router-dom";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import { HomePage } from "./dashboard";
|
import { HomePage } from "./DashboardPage";
|
||||||
|
|
||||||
const mockDashboard = vi.fn();
|
const mockDashboard = vi.fn();
|
||||||
const mockSessions = vi.fn();
|
const mockSessions = vi.fn();
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import { getDashboardSummary, type DashboardSummary } from "../api/dashboard";
|
||||||
|
import { getUserSessions } from "../api/sessions";
|
||||||
|
import { listProjects } from "../api/projects";
|
||||||
|
import { listToolTypes } from "../api/tool-types";
|
||||||
|
import type { Session as SessionApi } from "../types/session";
|
||||||
|
import type { Project } from "../types/project";
|
||||||
|
import type { ToolType } from "../types/tool-type";
|
||||||
|
import {
|
||||||
|
DashboardSummary as DashboardSummaryComponent,
|
||||||
|
ActiveSessionsList,
|
||||||
|
ProjectsSection,
|
||||||
|
QuickCreateForm,
|
||||||
|
RecentSessionsSection,
|
||||||
|
} from "../components/features/dashboard";
|
||||||
|
import { LoadingState, ErrorState } from "../components/ui";
|
||||||
|
import { useDashboardActions } from "../hooks/use-dashboard-actions";
|
||||||
|
import { listRepositories } from "../api/git-repositories";
|
||||||
|
import type { GitRepository } from "../types/git-repository";
|
||||||
|
|
||||||
|
type HomeStatus = "loading" | "ready" | "error";
|
||||||
|
type SessionView = SessionApi;
|
||||||
|
|
||||||
|
export const HomePage = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [status, setStatus] = useState<HomeStatus>("loading");
|
||||||
|
const [summary, setSummary] = useState<DashboardSummary | null>(null);
|
||||||
|
const [sessions, setSessions] = useState<SessionView[]>([]);
|
||||||
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
|
const [repositories, setRepositories] = useState<GitRepository[]>([]);
|
||||||
|
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
||||||
|
const safeSessions = Array.isArray(sessions) ? sessions : [];
|
||||||
|
|
||||||
|
const loadHome = useCallback(async () => {
|
||||||
|
setStatus("loading");
|
||||||
|
try {
|
||||||
|
const [dashboard, sessionData, projectData, toolTypeData] =
|
||||||
|
await Promise.all([
|
||||||
|
getDashboardSummary(),
|
||||||
|
getUserSessions(),
|
||||||
|
listProjects(),
|
||||||
|
listToolTypes(),
|
||||||
|
]);
|
||||||
|
setSummary(dashboard);
|
||||||
|
setSessions(sessionData as SessionView[]);
|
||||||
|
setProjects(projectData);
|
||||||
|
setToolTypes(toolTypeData);
|
||||||
|
setStatus("ready");
|
||||||
|
} catch {
|
||||||
|
setStatus("error");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void loadHome();
|
||||||
|
}, [loadHome]);
|
||||||
|
|
||||||
|
const loadRepos = useCallback(async (projectId: string) => {
|
||||||
|
try {
|
||||||
|
setRepositories(await listRepositories(projectId));
|
||||||
|
} catch {
|
||||||
|
setRepositories([]);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const activeSessions = useMemo(
|
||||||
|
() =>
|
||||||
|
safeSessions.filter((s) =>
|
||||||
|
["running", "building", "pending"].includes(s.status),
|
||||||
|
),
|
||||||
|
[safeSessions],
|
||||||
|
);
|
||||||
|
const recentSessions = useMemo(
|
||||||
|
() =>
|
||||||
|
safeSessions
|
||||||
|
.filter((s) => ["stopped", "error"].includes(s.status))
|
||||||
|
.slice(0, 5),
|
||||||
|
[safeSessions],
|
||||||
|
);
|
||||||
|
|
||||||
|
const actions = useDashboardActions(loadHome);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="stack home-page">
|
||||||
|
<header className="home-hero card">
|
||||||
|
<div className="stack-sm">
|
||||||
|
<p className="eyebrow">Workspace overview</p>
|
||||||
|
<h1>Home</h1>
|
||||||
|
<p className="muted">
|
||||||
|
Open sessions, available projects, and the fastest path back into
|
||||||
|
work.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="home-hero-actions">
|
||||||
|
<button
|
||||||
|
className="primary-button"
|
||||||
|
type="button"
|
||||||
|
onClick={() => navigate("/projects")}
|
||||||
|
>
|
||||||
|
New Project
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="secondary-button"
|
||||||
|
type="button"
|
||||||
|
onClick={() => navigate("/settings")}
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{status === "loading" && <LoadingState message="Loading overview..." />}
|
||||||
|
{status === "error" && (
|
||||||
|
<ErrorState
|
||||||
|
message="Unable to load your workspace overview."
|
||||||
|
onRetry={() => void loadHome()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === "ready" && summary && (
|
||||||
|
<>
|
||||||
|
<DashboardSummaryComponent
|
||||||
|
summary={summary}
|
||||||
|
activeSessionsCount={activeSessions.length}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<section className="card stack home-section">
|
||||||
|
<div className="page-header">
|
||||||
|
<div>
|
||||||
|
<p className="eyebrow">Open sessions</p>
|
||||||
|
<h2>{activeSessions.length}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ActiveSessionsList
|
||||||
|
sessions={activeSessions}
|
||||||
|
actionBusy={actions.actionBusy}
|
||||||
|
onOpen={actions.handleOpen}
|
||||||
|
onStop={actions.handleStop}
|
||||||
|
onDelete={actions.handleDelete}
|
||||||
|
onRecreateTunnel={actions.handleRecreateTunnel}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="card stack home-section">
|
||||||
|
<div className="page-header">
|
||||||
|
<div>
|
||||||
|
<p className="eyebrow">Available projects</p>
|
||||||
|
<h2>{projects.length}</h2>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="secondary-button"
|
||||||
|
type="button"
|
||||||
|
onClick={() => navigate("/projects")}
|
||||||
|
>
|
||||||
|
View all
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<ProjectsSection
|
||||||
|
projects={projects}
|
||||||
|
onOpenProject={(id) => navigate(`/projects/${id}`)}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="card stack home-section">
|
||||||
|
<div className="page-header">
|
||||||
|
<div>
|
||||||
|
<p className="eyebrow">Quick create</p>
|
||||||
|
<h2>Start a session</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<QuickCreateForm
|
||||||
|
projects={projects}
|
||||||
|
repositories={repositories}
|
||||||
|
toolTypes={toolTypes}
|
||||||
|
saveState={actions.saveState}
|
||||||
|
onSubmit={actions.handleCreate}
|
||||||
|
onProjectChange={loadRepos}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<RecentSessionsSection
|
||||||
|
sessions={recentSessions}
|
||||||
|
onOpen={actions.handleOpen}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { HomePage as DashboardPage };
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
import { getCommitDetail, getRepositoryHistory, type CommitDetail, type CommitHistoryEntry } from "../api/git_repositories";
|
import { getCommitDetail, getRepositoryHistory, type CommitDetail, type CommitHistoryEntry } from "../api/git-repositories";
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui/Icon";
|
||||||
|
|
||||||
export const GitHistoryPage = () => {
|
export const GitHistoryPage = () => {
|
||||||
const { projectId, repoId } = useParams<{ projectId: string; repoId: string }>();
|
const { projectId, repoId } = useParams<{ projectId: string; repoId: string }>();
|
||||||
+3
-3
@@ -2,9 +2,9 @@ import { useCallback, useEffect, useState } from "react";
|
|||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
import type { GitRepository } from "../types/git-repository";
|
import type { GitRepository } from "../types/git-repository";
|
||||||
import { deleteRepository, listRepositories } from "../api/git_repositories";
|
import { deleteRepository, listRepositories } from "../api/git-repositories";
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui/Icon";
|
||||||
import { RepositoryCreateDialog } from "../components/repository-create-dialog";
|
import { RepositoryCreateDialog } from "../components/features/project/RepositoryCreateDialog";
|
||||||
|
|
||||||
type RepoStatus = "loading" | "ready" | "error";
|
type RepoStatus = "loading" | "ready" | "error";
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ export const NotFoundPage = () => {
|
|||||||
|
|
||||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
|
||||||
|
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui/Icon";
|
||||||
|
|
||||||
export const LoginRedirectPage = () => {
|
export const LoginRedirectPage = () => {
|
||||||
const nextPath = new URLSearchParams(window.location.search).get("next") ?? "/";
|
const nextPath = new URLSearchParams(window.location.search).get("next") ?? "/";
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { getProfile, updateProfile, uploadAvatar } from "../api/profile";
|
import { getProfile, updateProfile, uploadAvatar } from "../api/profile";
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui/Icon";
|
||||||
import { useAuth } from "../state/auth";
|
import { useAuth } from "../state/auth";
|
||||||
import type { UserProfile } from "../api/profile";
|
import type { UserProfile } from "../api/profile";
|
||||||
|
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useParams, Link, useNavigate, Routes, Route } from "react-router-dom";
|
import { useParams, Link, useNavigate, Routes, Route } from "react-router-dom";
|
||||||
import { SettingsTabLayout } from "../components/settings-tab-layout";
|
import { SettingsTabLayout } from "../components/features/settings/SettingsTabLayout";
|
||||||
import { RepositoriesSettingsTab } from "../components/repositories-settings-tab";
|
import { RepositoriesSettingsTab } from "../components/features/project/RepositoriesSettingsTab";
|
||||||
import { apiClient } from "../api/client";
|
import { apiClient } from "../api/client";
|
||||||
import { Project } from "../types";
|
import { Project } from "../types";
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { cleanup, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
import { cleanup, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import { ProjectsPage } from "./projects";
|
import { ProjectsPage } from "./ProjectsPage";
|
||||||
import * as projectsApi from "../api/projects";
|
import * as projectsApi from "../api/projects";
|
||||||
|
|
||||||
const mockProjects = [
|
const mockProjects = [
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
listProjects,
|
listProjects,
|
||||||
type ProjectCreateInput,
|
type ProjectCreateInput,
|
||||||
} from "../api/projects";
|
} from "../api/projects";
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui/Icon";
|
||||||
import type { Project } from "../types";
|
import type { Project } from "../types";
|
||||||
|
|
||||||
type ProjectsStatus = "loading" | "ready" | "error";
|
type ProjectsStatus = "loading" | "ready" | "error";
|
||||||
@@ -8,13 +8,13 @@ import {
|
|||||||
getRepositoryStatus,
|
getRepositoryStatus,
|
||||||
listRepositories,
|
listRepositories,
|
||||||
type GitStatus,
|
type GitStatus,
|
||||||
} from "../api/git_repositories";
|
} from "../api/git-repositories";
|
||||||
import { WorkspaceSidebar } from "../components/features/git";
|
import { WorkspaceSidebar } from "../components/features/git";
|
||||||
import { FileEditor } from "../components/file-editor";
|
import { FileEditor } from "../components/features/git/FileEditor";
|
||||||
import { WorkspaceHeader } from "../components/workspace-header";
|
import { WorkspaceHeader } from "../components/features/workspace/WorkspaceHeader";
|
||||||
import { GitToolbar } from "../components/git-toolbar";
|
import { GitToolbar } from "../components/features/git/GitToolbar";
|
||||||
import { LoadingState, ErrorState } from "../components/ui";
|
import { LoadingState, ErrorState } from "../components/ui";
|
||||||
import { listToolTypes } from "../api/tool_types";
|
import { listToolTypes } from "../api/tool-types";
|
||||||
|
|
||||||
type WorkspaceStatus = "loading" | "ready" | "error" | "empty";
|
type WorkspaceStatus = "loading" | "ready" | "error" | "empty";
|
||||||
|
|
||||||
@@ -133,10 +133,7 @@ export const RepoWorkspace = () => {
|
|||||||
return (
|
return (
|
||||||
<section className="repo-workspace">
|
<section className="repo-workspace">
|
||||||
{project && (
|
{project && (
|
||||||
<WorkspaceHeader
|
<WorkspaceHeader project={project} currentRepo={selectedRepo || null} />
|
||||||
project={project}
|
|
||||||
currentRepo={selectedRepo || null}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{status === "loading" && (
|
{status === "loading" && (
|
||||||
@@ -180,9 +177,7 @@ export const RepoWorkspace = () => {
|
|||||||
onRefresh={() => {
|
onRefresh={() => {
|
||||||
void loadBranches();
|
void loadBranches();
|
||||||
void loadGitStatus();
|
void loadGitStatus();
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(new CustomEvent("refresh-file-tree"));
|
||||||
new CustomEvent("refresh-file-tree"),
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -197,18 +192,13 @@ export const RepoWorkspace = () => {
|
|||||||
onRepoChange={handleRepoChange}
|
onRepoChange={handleRepoChange}
|
||||||
onCommit={() => {
|
onCommit={() => {
|
||||||
void loadGitStatus();
|
void loadGitStatus();
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(new CustomEvent("refresh-file-tree"));
|
||||||
new CustomEvent("refresh-file-tree"),
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<main className="workspace-main">
|
<main className="workspace-main">
|
||||||
{selectedRepoId && (
|
{selectedRepoId && (
|
||||||
<FileEditor
|
<FileEditor projectId={projectId!} repoId={selectedRepoId} />
|
||||||
projectId={projectId!}
|
|
||||||
repoId={selectedRepoId}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
@@ -3,9 +3,9 @@ import { useNavigate } from "react-router-dom";
|
|||||||
|
|
||||||
import { listProjects } from "../api/projects";
|
import { listProjects } from "../api/projects";
|
||||||
import { getUserSessions } from "../api/sessions";
|
import { getUserSessions } from "../api/sessions";
|
||||||
import { listToolTypes } from "../api/tool_types";
|
import { listToolTypes } from "../api/tool-types";
|
||||||
import { getUserConfig } from "../api/settings";
|
import { getUserConfig } from "../api/settings";
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui/Icon";
|
||||||
import { LoadingState, ErrorState } from "../components/ui";
|
import { LoadingState, ErrorState } from "../components/ui";
|
||||||
import { CreateSessionForm, SessionList } from "../components/features/session";
|
import { CreateSessionForm, SessionList } from "../components/features/session";
|
||||||
import type { Project } from "../types/project";
|
import type { Project } from "../types/project";
|
||||||
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from "react";
|
|||||||
import { Link, Outlet, useLocation, useOutletContext } from "react-router-dom";
|
import { Link, Outlet, useLocation, useOutletContext } from "react-router-dom";
|
||||||
|
|
||||||
import { getUserConfig, updateUserConfig, type UserConfig, type UserConfigUpdate } from "../api/settings";
|
import { getUserConfig, updateUserConfig, type UserConfig, type UserConfigUpdate } from "../api/settings";
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui/Icon";
|
||||||
|
|
||||||
type SettingsStatus = "loading" | "ready" | "error";
|
type SettingsStatus = "loading" | "ready" | "error";
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { createSSHKey, deleteSSHKey, listSSHKeys, type SSHKey } from "../api/ssh_keys";
|
import { createSSHKey, deleteSSHKey, listSSHKeys, type SSHKey } from "../api/ssh-keys";
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui/Icon";
|
||||||
|
|
||||||
export const SSHKeysPage = () => {
|
export const SSHKeysPage = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { TerminalComponent } from "../components/terminal";
|
import { TerminalComponent } from "../components/features/terminal/TerminalComponent";
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui/Icon";
|
||||||
|
|
||||||
export const TerminalPage: React.FC = () => {
|
export const TerminalPage: React.FC = () => {
|
||||||
const { instanceId } = useParams<{ instanceId: string }>();
|
const { instanceId } = useParams<{ instanceId: string }>();
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui/Icon";
|
||||||
import type { ToolType } from "../types/tool-type";
|
import type { ToolType } from "../types/tool-type";
|
||||||
import type { ToolConfig } from "../types/tool-config";
|
import type { ToolConfig } from "../types/tool-config";
|
||||||
import { listToolTypes } from "../api/tool_types";
|
import { listToolTypes } from "../api/tool-types";
|
||||||
import {
|
import {
|
||||||
createToolConfig,
|
createToolConfig,
|
||||||
deleteToolConfig,
|
deleteToolConfig,
|
||||||
listToolConfigs,
|
listToolConfigs,
|
||||||
updateToolConfig,
|
updateToolConfig,
|
||||||
} from "../api/tool_configs";
|
} from "../api/tool-configs";
|
||||||
import { ToolConfigForm, ToolConfigList } from "../components/features/tool-configs";
|
import {
|
||||||
|
ToolConfigForm,
|
||||||
|
ToolConfigList,
|
||||||
|
} from "../components/features/tool-configs";
|
||||||
|
|
||||||
type ConfigStatus = "loading" | "ready" | "error";
|
type ConfigStatus = "loading" | "ready" | "error";
|
||||||
|
|
||||||
@@ -86,12 +89,16 @@ export const ToolConfigsPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selectedTool = toolTypes.find((t) => t.id === selectedToolType);
|
const selectedTool = toolTypes.find((t) => t.id === selectedToolType);
|
||||||
const filteredConfigs = configs.filter((c) => c.tool_type_id === selectedToolType);
|
const filteredConfigs = configs.filter(
|
||||||
|
(c) => c.tool_type_id === selectedToolType,
|
||||||
|
);
|
||||||
|
|
||||||
if (status === "loading") {
|
if (status === "loading") {
|
||||||
return (
|
return (
|
||||||
<section className="stack">
|
<section className="stack">
|
||||||
<div className="page-header"><h1>Tool Configurations</h1></div>
|
<div className="page-header">
|
||||||
|
<h1>Tool Configurations</h1>
|
||||||
|
</div>
|
||||||
<p className="muted">Loading...</p>
|
<p className="muted">Loading...</p>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@@ -100,10 +107,16 @@ export const ToolConfigsPage = () => {
|
|||||||
if (status === "error") {
|
if (status === "error") {
|
||||||
return (
|
return (
|
||||||
<section className="stack">
|
<section className="stack">
|
||||||
<div className="page-header"><h1>Tool Configurations</h1></div>
|
<div className="page-header">
|
||||||
|
<h1>Tool Configurations</h1>
|
||||||
|
</div>
|
||||||
<div className="card stack">
|
<div className="card stack">
|
||||||
<p>Failed to load configurations</p>
|
<p>Failed to load configurations</p>
|
||||||
<button className="secondary-button" onClick={() => void loadData()} type="button">
|
<button
|
||||||
|
className="secondary-button"
|
||||||
|
onClick={() => void loadData()}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
<Icon name="refresh" size="sm" /> Retry
|
<Icon name="refresh" size="sm" /> Retry
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,7 +131,11 @@ export const ToolConfigsPage = () => {
|
|||||||
<p className="eyebrow">Settings</p>
|
<p className="eyebrow">Settings</p>
|
||||||
<h1>Tool Configurations</h1>
|
<h1>Tool Configurations</h1>
|
||||||
</div>
|
</div>
|
||||||
<button className="secondary-button" type="button" onClick={() => navigate("/settings")}>
|
<button
|
||||||
|
className="secondary-button"
|
||||||
|
type="button"
|
||||||
|
onClick={() => navigate("/settings")}
|
||||||
|
>
|
||||||
Back to settings
|
Back to settings
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,18 +153,24 @@ export const ToolConfigsPage = () => {
|
|||||||
className="form-input"
|
className="form-input"
|
||||||
>
|
>
|
||||||
{toolTypes.map((tool) => (
|
{toolTypes.map((tool) => (
|
||||||
<option key={tool.id} value={tool.id}>{tool.display_name}</option>
|
<option key={tool.id} value={tool.id}>
|
||||||
|
{tool.display_name}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
{selectedTool && (
|
{selectedTool && (
|
||||||
<p className="muted" style={{ marginTop: "0.5rem" }}>
|
<p className="muted" style={{ marginTop: "0.5rem" }}>
|
||||||
Category: {selectedTool.category} · Interfaces: {selectedTool.interfaces?.join(", ")}
|
Category: {selectedTool.category} · Interfaces:{" "}
|
||||||
|
{selectedTool.interfaces?.join(", ")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card stack">
|
<div className="card stack">
|
||||||
<div className="row" style={{ justifyContent: "space-between", alignItems: "center" }}>
|
<div
|
||||||
|
className="row"
|
||||||
|
style={{ justifyContent: "space-between", alignItems: "center" }}
|
||||||
|
>
|
||||||
<h2>Configuration Variables</h2>
|
<h2>Configuration Variables</h2>
|
||||||
<button
|
<button
|
||||||
className="primary-button small"
|
className="primary-button small"
|
||||||
@@ -160,7 +183,11 @@ export const ToolConfigsPage = () => {
|
|||||||
<Icon name="add" size="sm" /> Add Config
|
<Icon name="add" size="sm" /> Add Config
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ToolConfigList configs={filteredConfigs} onEdit={handleEdit} onDelete={handleDelete} />
|
<ToolConfigList
|
||||||
|
configs={filteredConfigs}
|
||||||
|
onEdit={handleEdit}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showForm && (
|
{showForm && (
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import type { ToolType, CreateToolTypeRequest, UpdateToolTypeRequest } from "../types/tool-type";
|
import type {
|
||||||
|
ToolType,
|
||||||
|
CreateToolTypeRequest,
|
||||||
|
UpdateToolTypeRequest,
|
||||||
|
} from "../types/tool-type";
|
||||||
import {
|
import {
|
||||||
createToolType,
|
createToolType,
|
||||||
deleteToolType,
|
deleteToolType,
|
||||||
listToolTypes,
|
listToolTypes,
|
||||||
updateToolType,
|
updateToolType,
|
||||||
} from "../api/tool_types";
|
} from "../api/tool-types";
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui/Icon";
|
||||||
import { ToolTypeForm, ToolTypeList } from "../components/features/tool-types";
|
import { ToolTypeForm, ToolTypeList } from "../components/features/tool-types";
|
||||||
|
|
||||||
type ToolTypesStatus = "loading" | "ready" | "error";
|
type ToolTypesStatus = "loading" | "ready" | "error";
|
||||||
@@ -17,7 +21,9 @@ export const ToolTypesPage = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [status, setStatus] = useState<ToolTypesStatus>("loading");
|
const [status, setStatus] = useState<ToolTypesStatus>("loading");
|
||||||
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
||||||
const [dialogMode, setDialogMode] = useState<"none" | "create" | "edit">("none");
|
const [dialogMode, setDialogMode] = useState<"none" | "create" | "edit">(
|
||||||
|
"none",
|
||||||
|
);
|
||||||
const [editingToolType, setEditingToolType] = useState<ToolType | null>(null);
|
const [editingToolType, setEditingToolType] = useState<ToolType | null>(null);
|
||||||
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null);
|
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -52,7 +58,9 @@ export const ToolTypesPage = () => {
|
|||||||
setEditingToolType(null);
|
setEditingToolType(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (input: CreateToolTypeRequest | UpdateToolTypeRequest) => {
|
const handleSubmit = async (
|
||||||
|
input: CreateToolTypeRequest | UpdateToolTypeRequest,
|
||||||
|
) => {
|
||||||
if (dialogMode === "create") {
|
if (dialogMode === "create") {
|
||||||
await createToolType(input as CreateToolTypeRequest);
|
await createToolType(input as CreateToolTypeRequest);
|
||||||
} else if (editingToolType) {
|
} else if (editingToolType) {
|
||||||
+4
-4
@@ -1,10 +1,10 @@
|
|||||||
import { cleanup, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
import { cleanup, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import { ToolWorkshopPage } from "./tool-workshop";
|
import { ToolWorkshopPage } from "./ToolWorkshopPage";
|
||||||
import * as toolTypesApi from "../api/tool_types";
|
import * as toolTypesApi from "../api/tool-types";
|
||||||
import * as toolConfigsApi from "../api/tool_configs";
|
import * as toolConfigsApi from "../api/tool-configs";
|
||||||
import * as configFoldersApi from "../api/config_folders";
|
import * as configFoldersApi from "../api/config-folders";
|
||||||
|
|
||||||
const mockToolTypes = [
|
const mockToolTypes = [
|
||||||
{
|
{
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Icon } from "../components/icon";
|
import { Icon } from "../components/ui";
|
||||||
import {
|
import {
|
||||||
ToolTypesTab,
|
ToolTypesTab,
|
||||||
ToolConfigsTab,
|
ToolConfigsTab,
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
import { getDashboardSummary, type DashboardSummary } from "../api/dashboard";
|
|
||||||
import { getUserSessions } from "../api/sessions";
|
|
||||||
import { listProjects } from "../api/projects";
|
|
||||||
import { listToolTypes } from "../api/tool_types";
|
|
||||||
import type { Session as SessionApi } from "../types/session";
|
|
||||||
import type { Project } from "../types/project";
|
|
||||||
import type { ToolType } from "../types/tool-type";
|
|
||||||
import {
|
|
||||||
DashboardSummary as DashboardSummaryComponent,
|
|
||||||
ActiveSessionsList,
|
|
||||||
ProjectsSection,
|
|
||||||
QuickCreateForm,
|
|
||||||
RecentSessionsSection,
|
|
||||||
} from "../components/features/dashboard";
|
|
||||||
import { LoadingState, ErrorState } from "../components/ui";
|
|
||||||
import { useDashboardActions } from "../hooks/use-dashboard-actions";
|
|
||||||
import { listRepositories } from "../api/git_repositories";
|
|
||||||
import type { GitRepository } from "../types/git-repository";
|
|
||||||
|
|
||||||
type HomeStatus = "loading" | "ready" | "error";
|
|
||||||
type SessionView = SessionApi;
|
|
||||||
|
|
||||||
export const HomePage = () => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [status, setStatus] = useState<HomeStatus>("loading");
|
|
||||||
const [summary, setSummary] = useState<DashboardSummary | null>(null);
|
|
||||||
const [sessions, setSessions] = useState<SessionView[]>([]);
|
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
|
||||||
const [repositories, setRepositories] = useState<GitRepository[]>([]);
|
|
||||||
const [toolTypes, setToolTypes] = useState<ToolType[]>([]);
|
|
||||||
const safeSessions = Array.isArray(sessions) ? sessions : [];
|
|
||||||
|
|
||||||
const loadHome = useCallback(async () => {
|
|
||||||
setStatus("loading");
|
|
||||||
try {
|
|
||||||
const [dashboard, sessionData, projectData, toolTypeData] = await Promise.all([
|
|
||||||
getDashboardSummary(),
|
|
||||||
getUserSessions(),
|
|
||||||
listProjects(),
|
|
||||||
listToolTypes(),
|
|
||||||
]);
|
|
||||||
setSummary(dashboard);
|
|
||||||
setSessions(sessionData as SessionView[]);
|
|
||||||
setProjects(projectData);
|
|
||||||
setToolTypes(toolTypeData);
|
|
||||||
setStatus("ready");
|
|
||||||
} catch {
|
|
||||||
setStatus("error");
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => { void loadHome(); }, [loadHome]);
|
|
||||||
|
|
||||||
const loadRepos = useCallback(async (projectId: string) => {
|
|
||||||
try { setRepositories(await listRepositories(projectId)); }
|
|
||||||
catch { setRepositories([]); }
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const activeSessions = useMemo(() => safeSessions.filter((s) => ["running", "building", "pending"].includes(s.status)), [safeSessions]);
|
|
||||||
const recentSessions = useMemo(() => safeSessions.filter((s) => ["stopped", "error"].includes(s.status)).slice(0, 5), [safeSessions]);
|
|
||||||
|
|
||||||
const actions = useDashboardActions(loadHome);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="stack home-page">
|
|
||||||
<header className="home-hero card">
|
|
||||||
<div className="stack-sm">
|
|
||||||
<p className="eyebrow">Workspace overview</p>
|
|
||||||
<h1>Home</h1>
|
|
||||||
<p className="muted">Open sessions, available projects, and the fastest path back into work.</p>
|
|
||||||
</div>
|
|
||||||
<div className="home-hero-actions">
|
|
||||||
<button className="primary-button" type="button" onClick={() => navigate("/projects")}>New Project</button>
|
|
||||||
<button className="secondary-button" type="button" onClick={() => navigate("/settings")}>Settings</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{status === "loading" && <LoadingState message="Loading overview..." />}
|
|
||||||
{status === "error" && <ErrorState message="Unable to load your workspace overview." onRetry={() => void loadHome()} />}
|
|
||||||
|
|
||||||
{status === "ready" && summary && (
|
|
||||||
<>
|
|
||||||
<DashboardSummaryComponent summary={summary} activeSessionsCount={activeSessions.length} />
|
|
||||||
|
|
||||||
<section className="card stack home-section">
|
|
||||||
<div className="page-header"><div><p className="eyebrow">Open sessions</p><h2>{activeSessions.length}</h2></div></div>
|
|
||||||
<ActiveSessionsList sessions={activeSessions} actionBusy={actions.actionBusy} onOpen={actions.handleOpen} onStop={actions.handleStop} onDelete={actions.handleDelete} onRecreateTunnel={actions.handleRecreateTunnel} />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card stack home-section">
|
|
||||||
<div className="page-header"><div><p className="eyebrow">Available projects</p><h2>{projects.length}</h2></div><button className="secondary-button" type="button" onClick={() => navigate("/projects")}>View all</button></div>
|
|
||||||
<ProjectsSection projects={projects} onOpenProject={(id) => navigate(`/projects/${id}`)} />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="card stack home-section">
|
|
||||||
<div className="page-header"><div><p className="eyebrow">Quick create</p><h2>Start a session</h2></div></div>
|
|
||||||
<QuickCreateForm projects={projects} repositories={repositories} toolTypes={toolTypes} saveState={actions.saveState} onSubmit={actions.handleCreate} onProjectChange={loadRepos} />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<RecentSessionsSection sessions={recentSessions} onOpen={actions.handleOpen} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { HomePage as DashboardPage };
|
|
||||||
+18
-16
@@ -1,21 +1,22 @@
|
|||||||
import { Navigate, Route, Routes } from "react-router-dom";
|
import { Navigate, Route, Routes } from "react-router-dom";
|
||||||
|
|
||||||
import { AppShell } from "./components/app-shell";
|
import { AppShell } from "./components/layout/AppShell";
|
||||||
import { ProtectedRoute } from "./components/protected-route";
|
import { ProtectedRoute } from "./components/ProtectedRoute";
|
||||||
import { HomePage } from "./pages/dashboard";
|
import { HomePage } from "./pages/DashboardPage";
|
||||||
import { LoginRedirectPage, NotFoundPage } from "./pages/placeholder";
|
import { LoginRedirectPage, NotFoundPage } from "./pages/PlaceholderPage";
|
||||||
import { ProfilePage } from "./pages/profile";
|
import { ProfilePage } from "./pages/ProfilePage";
|
||||||
import { ProjectsPage } from "./pages/projects";
|
import { ProjectsPage } from "./pages/ProjectsPage";
|
||||||
import { GitRepositoriesPage } from "./pages/git-repositories";
|
import { GitRepositoriesPage } from "./pages/GitRepositoriesPage";
|
||||||
import { GitHistoryPage } from "./pages/git-history";
|
import { GitHistoryPage } from "./pages/GitHistoryPage";
|
||||||
import { ProjectSettingsPage } from "./pages/project-settings";
|
import { ProjectSettingsPage } from "./pages/ProjectSettingsPage";
|
||||||
import { RepoWorkspace } from "./pages/repo-workspace";
|
import { RepoWorkspace } from "./pages/RepoWorkspacePage";
|
||||||
import { SettingsPage, GeneralSettingsTab } from "./pages/settings";
|
import { SettingsPage, GeneralSettingsTab } from "./pages/SettingsPage";
|
||||||
import { TerminalPage } from "./pages/terminal";
|
import { TerminalPage } from "./pages/TerminalPage";
|
||||||
import { ToolWorkshopPage } from "./pages/tool-workshop";
|
import { ToolWorkshopPage } from "./pages/ToolWorkshopPage";
|
||||||
import { SSHKeysPage } from "./pages/ssh-keys";
|
import { SSHKeysPage } from "./pages/SshKeysPage";
|
||||||
import { ToolConfigsPage } from "./pages/tool-configs";
|
import { ToolConfigsPage } from "./pages/ToolConfigsPage";
|
||||||
import { ToolTypesPage } from "./pages/tool-types";
|
import { ToolTypesPage } from "./pages/ToolTypesPage";
|
||||||
|
import { SessionsPage } from "./pages/SessionsPage";
|
||||||
|
|
||||||
export const AppRouter = () => {
|
export const AppRouter = () => {
|
||||||
return (
|
return (
|
||||||
@@ -50,6 +51,7 @@ export const AppRouter = () => {
|
|||||||
</Route>
|
</Route>
|
||||||
<Route path="tool-workshop" element={<ToolWorkshopPage />} />
|
<Route path="tool-workshop" element={<ToolWorkshopPage />} />
|
||||||
<Route path="instances/:instanceId/terminal" element={<TerminalPage />} />
|
<Route path="instances/:instanceId/terminal" element={<TerminalPage />} />
|
||||||
|
<Route path="sessions" element={<SessionsPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/404" element={<NotFoundPage />} />
|
<Route path="/404" element={<NotFoundPage />} />
|
||||||
<Route path="*" element={<Navigate to="/404" replace />} />
|
<Route path="*" element={<Navigate to="/404" replace />} />
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# Task 4.4 Apply Report: Rename Files to Naming Convention
|
||||||
|
|
||||||
|
**Status:** Success
|
||||||
|
|
||||||
|
## Files Renamed (43 total)
|
||||||
|
|
||||||
|
### Component Files → PascalCase + Feature Directories
|
||||||
|
```
|
||||||
|
components/app-shell.tsx → components/layout/AppShell.tsx
|
||||||
|
components/code-editor.tsx → components/ui/CodeEditor.tsx
|
||||||
|
components/commit-dialog.tsx → components/features/git/CommitDialog.tsx
|
||||||
|
components/commit-panel.tsx → components/features/git/CommitPanel.tsx
|
||||||
|
components/file-editor.tsx → components/features/git/FileEditor.tsx
|
||||||
|
components/git-toolbar.tsx → components/features/git/GitToolbar.tsx
|
||||||
|
components/icon.tsx → components/ui/Icon.tsx
|
||||||
|
components/instance-list.tsx → components/features/session/InstanceList.tsx
|
||||||
|
components/merge-dialog.tsx → components/features/git/MergeDialog.tsx
|
||||||
|
components/protected-route.tsx → components/ProtectedRoute.tsx
|
||||||
|
components/protected-route.test.tsx → components/ProtectedRoute.test.tsx
|
||||||
|
components/repositories-settings-tab.tsx → components/features/project/RepositoriesSettingsTab.tsx
|
||||||
|
components/repositories-settings-tab.test.tsx → components/features/project/RepositoriesSettingsTab.test.tsx
|
||||||
|
components/repository-create-dialog.tsx → components/features/project/RepositoryCreateDialog.tsx
|
||||||
|
components/settings-tab-layout.tsx → components/features/settings/SettingsTabLayout.tsx
|
||||||
|
components/syntax-highlighter.tsx → components/features/git/SyntaxHighlighter.tsx
|
||||||
|
components/terminal.tsx → components/features/terminal/TerminalComponent.tsx
|
||||||
|
components/workspace-header.tsx → components/features/workspace/WorkspaceHeader.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Page Files → PascalCase with Page Suffix
|
||||||
|
```
|
||||||
|
pages/dashboard.tsx → pages/DashboardPage.tsx
|
||||||
|
pages/dashboard.test.tsx → pages/DashboardPage.test.tsx
|
||||||
|
pages/git-history.tsx → pages/GitHistoryPage.tsx
|
||||||
|
pages/git-repositories.tsx → pages/GitRepositoriesPage.tsx
|
||||||
|
pages/placeholder.tsx → pages/PlaceholderPage.tsx
|
||||||
|
pages/profile.tsx → pages/ProfilePage.tsx
|
||||||
|
pages/project-settings.tsx → pages/ProjectSettingsPage.tsx
|
||||||
|
pages/projects.tsx → pages/ProjectsPage.tsx
|
||||||
|
pages/projects.test.tsx → pages/ProjectsPage.test.tsx
|
||||||
|
pages/repo-workspace.tsx → pages/RepoWorkspacePage.tsx
|
||||||
|
pages/sessions.tsx → pages/SessionsPage.tsx
|
||||||
|
pages/settings.tsx → pages/SettingsPage.tsx
|
||||||
|
pages/ssh-keys.tsx → pages/SshKeysPage.tsx
|
||||||
|
pages/terminal.tsx → pages/TerminalPage.tsx
|
||||||
|
pages/tool-configs.tsx → pages/ToolConfigsPage.tsx
|
||||||
|
pages/tool-types.tsx → pages/ToolTypesPage.tsx
|
||||||
|
pages/tool-workshop.tsx → pages/ToolWorkshopPage.tsx
|
||||||
|
pages/tool-workshop.test.tsx → pages/ToolWorkshopPage.test.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Files → kebab-case
|
||||||
|
```
|
||||||
|
api/config_folders.test.ts → api/config-folders.test.ts
|
||||||
|
api/config_folders.ts → api/config-folders.ts
|
||||||
|
api/git_repositories.ts → api/git-repositories.ts
|
||||||
|
api/ssh_keys.ts → api/ssh-keys.ts
|
||||||
|
api/tool_configs.ts → api/tool-configs.ts
|
||||||
|
api/tool_types.test.ts → api/tool-types.test.ts
|
||||||
|
api/tool_types.ts → api/tool-types.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Import Updates
|
||||||
|
|
||||||
|
Updated import statements across ~30+ files to reflect new paths, including:
|
||||||
|
- Router imports (`router.tsx`)
|
||||||
|
- Component-to-component imports
|
||||||
|
- Page-to-component imports
|
||||||
|
- Feature component imports (with corrected relative depths for nested directories)
|
||||||
|
- Test file imports
|
||||||
|
|
||||||
|
## Quality Gate Results
|
||||||
|
|
||||||
|
| Gate | Result |
|
||||||
|
|------|--------|
|
||||||
|
| `npm run typecheck` | ✅ PASS — zero errors |
|
||||||
|
| `npm run lint` | ✅ PASS — zero warnings |
|
||||||
|
| `npx vitest run` | ✅ 66 passed / 74 total (8 failures = pre-existing ProjectsPage.test.tsx issues) |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- All renames used `git mv` to preserve git history
|
||||||
|
- Relative import depths were corrected for files moved into deeper directory structures (e.g., `components/features/git/` needs `../../../api/` instead of `../api/`)
|
||||||
|
- No file contents were modified except import paths
|
||||||
+4
-36
@@ -1,42 +1,10 @@
|
|||||||
# Progress
|
# Progress
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
In Progress — Tasks 1.1 through 4.3 complete
|
In Progress
|
||||||
|
|
||||||
## Completed Tasks
|
## Tasks
|
||||||
|
|
||||||
| Task | Description | Status |
|
## Files Changed
|
||||||
|------|-------------|--------|
|
|
||||||
| 1.1 | Centralize types and extract seed data | ✅ Complete |
|
|
||||||
| 1.2 | Extract FileBrowser and shared UI primitives | ✅ Complete |
|
|
||||||
| 2.1 | Extract global styles and tokens | ✅ Complete |
|
|
||||||
| 2.2 | Extract CSS modules (terminal + git) | ✅ Complete |
|
|
||||||
| 2.3 | Extract CSS modules (session/settings) + delete styles.css | ✅ Complete |
|
|
||||||
| 3.1 | Extract shared auth dependencies | ✅ Complete |
|
|
||||||
| 3.2 | Create Pydantic schemas directory | ✅ Complete |
|
|
||||||
| 3.3 | Split services/docker.py | ✅ Complete |
|
|
||||||
| 3.4 | Slim tool_instances router | ✅ Complete |
|
|
||||||
| 3.5 | Slim git_repositories and config_profiles routers | ✅ Complete |
|
|
||||||
| 4.1 | Split tool-workshop page into tabs | ✅ Complete |
|
|
||||||
| 4.2 | Extract sessions page components | ✅ Complete |
|
|
||||||
| 4.3 | Extract dashboard and workspace components | ✅ Complete |
|
|
||||||
|
|
||||||
## Remaining Tasks
|
## Notes
|
||||||
|
|
||||||
| Task | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| 4.4 | Rename all files to naming convention |
|
|
||||||
| 5.1 | Add tests for extracted components |
|
|
||||||
| 5.2 | Documentation and cleanup |
|
|
||||||
|
|
||||||
## Key Metrics
|
|
||||||
|
|
||||||
- `styles.css`: 2,844 lines → DELETED
|
|
||||||
- `tool_instances.py`: 1,412 → 284 lines (-80%)
|
|
||||||
- `git_repositories.py`: 1,050 → 276 lines (-74%)
|
|
||||||
- `config_profiles.py`: 765 → 299 lines (-61%)
|
|
||||||
- `dashboard.tsx`: 480 → 110 lines (-77%)
|
|
||||||
- `tool-types.tsx`: 409 → 135 lines (-67%)
|
|
||||||
- `tool-configs.tsx`: 391 → 178 lines (-54%)
|
|
||||||
- `tool-workshop.tsx`: 700 → 77 lines (-89%)
|
|
||||||
- `sessions.tsx`: 668 → 156 lines (-77%)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user