fmt: use biome
This commit is contained in:
@@ -35,6 +35,7 @@
|
|||||||
pkg-config
|
pkg-config
|
||||||
sqlite
|
sqlite
|
||||||
gemini-cli
|
gemini-cli
|
||||||
|
biome
|
||||||
];
|
];
|
||||||
buildInputs = [ ];
|
buildInputs = [ ];
|
||||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (nativeBuildInputs ++ buildInputs);
|
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (nativeBuildInputs ++ buildInputs);
|
||||||
|
|||||||
41
frontend/biome.json
Normal file
41
frontend/biome.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.3.6/schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": true,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": true
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"includes": ["**", "!!**/dist"]
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"formatWithErrors": true,
|
||||||
|
"attributePosition": "auto",
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 110,
|
||||||
|
"lineEnding": "lf"
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"arrowParentheses": "always",
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"jsxQuoteStyle": "double",
|
||||||
|
"quoteProperties": "asNeeded",
|
||||||
|
"semicolons": "always",
|
||||||
|
"trailingCommas": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"json": {
|
||||||
|
"formatter": {
|
||||||
|
"trailingCommas": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"css": {
|
||||||
|
"parser": {
|
||||||
|
"tailwindDirectives": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,4 @@ export default {
|
|||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Component, createSignal, onMount, Show } from 'solid-js';
|
import { Component, createSignal, onMount, Show } from "solid-js";
|
||||||
import RulesList from './components/RulesList';
|
import RulesList from "./components/RulesList";
|
||||||
import CommandQueue from './components/CommandQueue';
|
import CommandQueue from "./components/CommandQueue";
|
||||||
import Login from './components/Login';
|
import Login from "./components/Login";
|
||||||
import ChangePassword from './components/ChangePassword';
|
import ChangePassword from "./components/ChangePassword";
|
||||||
import RequestLog from './components/RequestLog';
|
import RequestLog from "./components/RequestLog";
|
||||||
import { authStore } from './api/auth';
|
import { authStore } from "./api/auth";
|
||||||
|
|
||||||
const App: Component = () => {
|
const App: Component = () => {
|
||||||
const [activeTab, setActiveTab] = createSignal<'rules' | 'commands' | 'logs'>('rules');
|
const [activeTab, setActiveTab] = createSignal<"rules" | "commands" | "logs">("rules");
|
||||||
const [isAuthenticated, setIsAuthenticated] = createSignal(false);
|
const [isAuthenticated, setIsAuthenticated] = createSignal(false);
|
||||||
const [showChangePassword, setShowChangePassword] = createSignal(false);
|
const [showChangePassword, setShowChangePassword] = createSignal(false);
|
||||||
|
|
||||||
@@ -31,9 +31,7 @@ const App: Component = () => {
|
|||||||
<div class="min-h-screen bg-gray-100">
|
<div class="min-h-screen bg-gray-100">
|
||||||
<header class="bg-white shadow">
|
<header class="bg-white shadow">
|
||||||
<div class="max-w-7xl mx-auto px-4 py-6 flex justify-between items-center">
|
<div class="max-w-7xl mx-auto px-4 py-6 flex justify-between items-center">
|
||||||
<h1 class="text-3xl font-bold text-gray-900">
|
<h1 class="text-3xl font-bold text-gray-900">My Linspirer Control Panel</h1>
|
||||||
My Linspirer Control Panel
|
|
||||||
</h1>
|
|
||||||
<div class="flex space-x-3">
|
<div class="flex space-x-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowChangePassword(true)}
|
onClick={() => setShowChangePassword(true)}
|
||||||
@@ -55,31 +53,31 @@ const App: Component = () => {
|
|||||||
<div class="max-w-7xl mx-auto px-4">
|
<div class="max-w-7xl mx-auto px-4">
|
||||||
<div class="flex space-x-8">
|
<div class="flex space-x-8">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('rules')}
|
onClick={() => setActiveTab("rules")}
|
||||||
class={`py-4 px-1 border-b-2 font-medium text-sm ${
|
class={`py-4 px-1 border-b-2 font-medium text-sm ${
|
||||||
activeTab() === 'rules'
|
activeTab() === "rules"
|
||||||
? 'border-indigo-500 text-indigo-600'
|
? "border-indigo-500 text-indigo-600"
|
||||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
: "border-transparent text-gray-500 hover:text-gray-700"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Interception Rules
|
Interception Rules
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('commands')}
|
onClick={() => setActiveTab("commands")}
|
||||||
class={`py-4 px-1 border-b-2 font-medium text-sm ${
|
class={`py-4 px-1 border-b-2 font-medium text-sm ${
|
||||||
activeTab() === 'commands'
|
activeTab() === "commands"
|
||||||
? 'border-indigo-500 text-indigo-600'
|
? "border-indigo-500 text-indigo-600"
|
||||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
: "border-transparent text-gray-500 hover:text-gray-700"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Command Queue
|
Command Queue
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('logs')}
|
onClick={() => setActiveTab("logs")}
|
||||||
class={`py-4 px-1 border-b-2 font-medium text-sm ${
|
class={`py-4 px-1 border-b-2 font-medium text-sm ${
|
||||||
activeTab() === 'logs'
|
activeTab() === "logs"
|
||||||
? 'border-indigo-500 text-indigo-600'
|
? "border-indigo-500 text-indigo-600"
|
||||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
: "border-transparent text-gray-500 hover:text-gray-700"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Request Log
|
Request Log
|
||||||
@@ -89,16 +87,13 @@ const App: Component = () => {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<main class="max-w-7xl mx-auto px-4 py-6">
|
<main class="max-w-7xl mx-auto px-4 py-6">
|
||||||
{activeTab() === 'rules' && <RulesList />}
|
{activeTab() === "rules" && <RulesList />}
|
||||||
{activeTab() === 'commands' && <CommandQueue />}
|
{activeTab() === "commands" && <CommandQueue />}
|
||||||
{activeTab() === 'logs' && <RequestLog />}
|
{activeTab() === "logs" && <RequestLog />}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<Show when={showChangePassword()}>
|
<Show when={showChangePassword()}>
|
||||||
<ChangePassword
|
<ChangePassword onClose={() => setShowChangePassword(false)} onLogout={handleLogout} />
|
||||||
onClose={() => setShowChangePassword(false)}
|
|
||||||
onLogout={handleLogout}
|
|
||||||
/>
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const TOKEN_KEY = 'admin_token';
|
const TOKEN_KEY = "admin_token";
|
||||||
|
|
||||||
export const authStore = {
|
export const authStore = {
|
||||||
getToken(): string | null {
|
getToken(): string | null {
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
import type { InterceptionRule, Command, CreateRuleRequest, UpdateRuleRequest, UpdateCommandRequest, RequestLog } from '../types';
|
import type {
|
||||||
import { authStore } from './auth';
|
InterceptionRule,
|
||||||
|
Command,
|
||||||
|
CreateRuleRequest,
|
||||||
|
UpdateRuleRequest,
|
||||||
|
UpdateCommandRequest,
|
||||||
|
RequestLog,
|
||||||
|
} from "../types";
|
||||||
|
import { authStore } from "./auth";
|
||||||
|
|
||||||
const API_BASE = '/admin/api';
|
const API_BASE = "/admin/api";
|
||||||
|
|
||||||
async function request<T>(
|
async function request<T>(endpoint: string, options?: RequestInit): Promise<T> {
|
||||||
endpoint: string,
|
|
||||||
options?: RequestInit
|
|
||||||
): Promise<T> {
|
|
||||||
const token = authStore.getToken();
|
const token = authStore.getToken();
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE}${endpoint}`, {
|
const response = await fetch(`${API_BASE}${endpoint}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
...options?.headers,
|
...options?.headers,
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
@@ -22,7 +26,7 @@ async function request<T>(
|
|||||||
// Unauthorized - clear token and trigger re-authentication
|
// Unauthorized - clear token and trigger re-authentication
|
||||||
authStore.clearToken();
|
authStore.clearToken();
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
throw new Error('Unauthorized');
|
throw new Error("Unauthorized");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -32,14 +36,14 @@ async function request<T>(
|
|||||||
|
|
||||||
if (response.status === 204 || response.status === 200) {
|
if (response.status === 204 || response.status === 200) {
|
||||||
// Check if response has content
|
// Check if response has content
|
||||||
const contentType = response.headers.get('content-type');
|
const contentType = response.headers.get("content-type");
|
||||||
if (!contentType || !contentType.includes('application/json')) {
|
if (!contentType || !contentType.includes("application/json")) {
|
||||||
return undefined as T;
|
return undefined as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there's actually content to parse
|
// Check if there's actually content to parse
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
if (!text || text.trim() === '') {
|
if (!text || text.trim() === "") {
|
||||||
return undefined as T;
|
return undefined as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,43 +55,40 @@ async function request<T>(
|
|||||||
|
|
||||||
// Rules API
|
// Rules API
|
||||||
export const rulesApi = {
|
export const rulesApi = {
|
||||||
list: () =>
|
list: () => request<InterceptionRule[]>("/rules"),
|
||||||
request<InterceptionRule[]>('/rules'),
|
|
||||||
|
|
||||||
create: (rule: CreateRuleRequest) =>
|
create: (rule: CreateRuleRequest) =>
|
||||||
request<InterceptionRule>('/rules', {
|
request<InterceptionRule>("/rules", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
body: JSON.stringify(rule),
|
body: JSON.stringify(rule),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
update: (id: number, rule: UpdateRuleRequest) =>
|
update: (id: number, rule: UpdateRuleRequest) =>
|
||||||
request<InterceptionRule>(`/rules/${id}`, {
|
request<InterceptionRule>(`/rules/${id}`, {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
body: JSON.stringify(rule),
|
body: JSON.stringify(rule),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
delete: (id: number) =>
|
delete: (id: number) => request<void>(`/rules/${id}`, { method: "DELETE" }),
|
||||||
request<void>(`/rules/${id}`, { method: 'DELETE' }),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Commands API
|
// Commands API
|
||||||
export const commandsApi = {
|
export const commandsApi = {
|
||||||
list: (status?: string) =>
|
list: (status?: string) => request<Command[]>(`/commands${status ? `?status=${status}` : ""}`),
|
||||||
request<Command[]>(`/commands${status ? `?status=${status}` : ''}`),
|
|
||||||
|
|
||||||
updateStatus: (id: number, req: UpdateCommandRequest) =>
|
updateStatus: (id: number, req: UpdateCommandRequest) =>
|
||||||
request<Command>(`/commands/${id}`, {
|
request<Command>(`/commands/${id}`, {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
body: JSON.stringify(req),
|
body: JSON.stringify(req),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Config API
|
// Config API
|
||||||
export const configApi = {
|
export const configApi = {
|
||||||
get: () => request<Record<string, string>>('/config'),
|
get: () => request<Record<string, string>>("/config"),
|
||||||
update: (config: Record<string, string>) =>
|
update: (config: Record<string, string>) =>
|
||||||
request<void>('/config', {
|
request<void>("/config", {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
body: JSON.stringify(config),
|
body: JSON.stringify(config),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -95,8 +96,8 @@ export const configApi = {
|
|||||||
// Auth API
|
// Auth API
|
||||||
export const authApi = {
|
export const authApi = {
|
||||||
changePassword: (oldPassword: string, newPassword: string) =>
|
changePassword: (oldPassword: string, newPassword: string) =>
|
||||||
request<void>('/password', {
|
request<void>("/password", {
|
||||||
method: 'PUT',
|
method: "PUT",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
old_password: oldPassword,
|
old_password: oldPassword,
|
||||||
new_password: newPassword,
|
new_password: newPassword,
|
||||||
@@ -109,12 +110,12 @@ export const logsApi = {
|
|||||||
list: (params?: { method?: string; search?: string }) => {
|
list: (params?: { method?: string; search?: string }) => {
|
||||||
const query = new URLSearchParams();
|
const query = new URLSearchParams();
|
||||||
if (params?.method) {
|
if (params?.method) {
|
||||||
query.set('method', params.method);
|
query.set("method", params.method);
|
||||||
}
|
}
|
||||||
if (params?.search) {
|
if (params?.search) {
|
||||||
query.set('search', params.search);
|
query.set("search", params.search);
|
||||||
}
|
}
|
||||||
const queryString = query.toString();
|
const queryString = query.toString();
|
||||||
return request<RequestLog[]>(`/logs${queryString ? `?${queryString}` : ''}`);
|
return request<RequestLog[]>(`/logs${queryString ? `?${queryString}` : ""}`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Component, createSignal } from 'solid-js';
|
import { Component, createSignal } from "solid-js";
|
||||||
import { authApi } from '../api/client';
|
import { authApi } from "../api/client";
|
||||||
import { authStore } from '../api/auth';
|
import { authStore } from "../api/auth";
|
||||||
import { Button } from './ui/Button';
|
import { Button } from "./ui/Button";
|
||||||
import { CardContent, CardFooter, CardHeader } from './ui/Card';
|
import { CardContent, CardFooter, CardHeader } from "./ui/Card";
|
||||||
import { Input } from './ui/Input';
|
import { Input } from "./ui/Input";
|
||||||
import { Modal, ModalContent } from './ui/Modal';
|
import { Modal, ModalContent } from "./ui/Modal";
|
||||||
|
|
||||||
interface ChangePasswordProps {
|
interface ChangePasswordProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -12,9 +12,9 @@ interface ChangePasswordProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ChangePassword: Component<ChangePasswordProps> = (props) => {
|
const ChangePassword: Component<ChangePasswordProps> = (props) => {
|
||||||
const [oldPassword, setOldPassword] = createSignal('');
|
const [oldPassword, setOldPassword] = createSignal("");
|
||||||
const [newPassword, setNewPassword] = createSignal('');
|
const [newPassword, setNewPassword] = createSignal("");
|
||||||
const [confirmPassword, setConfirmPassword] = createSignal('');
|
const [confirmPassword, setConfirmPassword] = createSignal("");
|
||||||
const [error, setError] = createSignal<string | null>(null);
|
const [error, setError] = createSignal<string | null>(null);
|
||||||
const [success, setSuccess] = createSignal(false);
|
const [success, setSuccess] = createSignal(false);
|
||||||
const [loading, setLoading] = createSignal(false);
|
const [loading, setLoading] = createSignal(false);
|
||||||
@@ -26,17 +26,17 @@ const ChangePassword: Component<ChangePasswordProps> = (props) => {
|
|||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (!oldPassword() || !newPassword() || !confirmPassword()) {
|
if (!oldPassword() || !newPassword() || !confirmPassword()) {
|
||||||
setError('Please fill in all fields');
|
setError("Please fill in all fields");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPassword() !== confirmPassword()) {
|
if (newPassword() !== confirmPassword()) {
|
||||||
setError('New passwords do not match');
|
setError("New passwords do not match");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPassword().length < 6) {
|
if (newPassword().length < 6) {
|
||||||
setError('New password must be at least 6 characters');
|
setError("New password must be at least 6 characters");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,9 +45,9 @@ const ChangePassword: Component<ChangePasswordProps> = (props) => {
|
|||||||
try {
|
try {
|
||||||
await authApi.changePassword(oldPassword(), newPassword());
|
await authApi.changePassword(oldPassword(), newPassword());
|
||||||
setSuccess(true);
|
setSuccess(true);
|
||||||
setOldPassword('');
|
setOldPassword("");
|
||||||
setNewPassword('');
|
setNewPassword("");
|
||||||
setConfirmPassword('');
|
setConfirmPassword("");
|
||||||
|
|
||||||
// Close modal and logout after 2 seconds
|
// Close modal and logout after 2 seconds
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -56,7 +56,7 @@ const ChangePassword: Component<ChangePasswordProps> = (props) => {
|
|||||||
props.onLogout();
|
props.onLogout();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to change password');
|
setError(err instanceof Error ? err.message : "Failed to change password");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -72,9 +72,7 @@ const ChangePassword: Component<ChangePasswordProps> = (props) => {
|
|||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{error() && (
|
{error() && (
|
||||||
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
|
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">{error()}</div>
|
||||||
{error()}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{success() && (
|
{success() && (
|
||||||
@@ -84,9 +82,7 @@ const ChangePassword: Component<ChangePasswordProps> = (props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
<label class="block text-sm font-medium text-gray-700 mb-1">Old Password</label>
|
||||||
Old Password
|
|
||||||
</label>
|
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
value={oldPassword()}
|
value={oldPassword()}
|
||||||
@@ -96,9 +92,7 @@ const ChangePassword: Component<ChangePasswordProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
<label class="block text-sm font-medium text-gray-700 mb-1">New Password</label>
|
||||||
New Password
|
|
||||||
</label>
|
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
value={newPassword()}
|
value={newPassword()}
|
||||||
@@ -108,9 +102,7 @@ const ChangePassword: Component<ChangePasswordProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
<label class="block text-sm font-medium text-gray-700 mb-1">Confirm New Password</label>
|
||||||
Confirm New Password
|
|
||||||
</label>
|
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
value={confirmPassword()}
|
value={confirmPassword()}
|
||||||
@@ -120,19 +112,11 @@ const ChangePassword: Component<ChangePasswordProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter class="flex justify-end space-x-3 pt-4">
|
<CardFooter class="flex justify-end space-x-3 pt-4">
|
||||||
<Button
|
<Button type="button" variant="secondary" onClick={props.onClose} disabled={loading()}>
|
||||||
type="button"
|
|
||||||
variant="secondary"
|
|
||||||
onClick={props.onClose}
|
|
||||||
disabled={loading()}
|
|
||||||
>
|
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button type="submit" disabled={loading()}>
|
||||||
type="submit"
|
{loading() ? "Changing..." : "Change Password"}
|
||||||
disabled={loading()}
|
|
||||||
>
|
|
||||||
{loading() ? 'Changing...' : 'Change Password'}
|
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, createResource, For, Show } from 'solid-js';
|
import { Component, createResource, For, Show } from "solid-js";
|
||||||
import { commandsApi } from '../api/client';
|
import { commandsApi } from "../api/client";
|
||||||
import { Button } from './ui/Button';
|
import { Button } from "./ui/Button";
|
||||||
import { Card } from './ui/Card';
|
import { Card } from "./ui/Card";
|
||||||
|
|
||||||
const CommandQueue: Component = () => {
|
const CommandQueue: Component = () => {
|
||||||
const [commands, { refetch }] = createResource(commandsApi.list);
|
const [commands, { refetch }] = createResource(commandsApi.list);
|
||||||
@@ -11,7 +11,7 @@ const CommandQueue: Component = () => {
|
|||||||
await commandsApi.updateStatus(id, { status });
|
await commandsApi.updateStatus(id, { status });
|
||||||
refetch();
|
refetch();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to update command:', err);
|
console.error("Failed to update command:", err);
|
||||||
alert(`Error: ${err}`);
|
alert(`Error: ${err}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -26,26 +26,27 @@ const CommandQueue: Component = () => {
|
|||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<Show when={!commands.loading} fallback={<div class="p-4">Loading...</div>}>
|
<Show when={!commands.loading} fallback={<div class="p-4">Loading...</div>}>
|
||||||
<For each={commands()} fallback={
|
<For
|
||||||
<div class="p-8 text-center text-gray-500">
|
each={commands()}
|
||||||
No commands in queue
|
fallback={<div class="p-8 text-center text-gray-500">No commands in queue</div>}
|
||||||
</div>
|
>
|
||||||
}>
|
|
||||||
{(cmd) => (
|
{(cmd) => (
|
||||||
<div class="border-b p-4 hover:bg-gray-50">
|
<div class="border-b p-4 hover:bg-gray-50">
|
||||||
<div class="flex justify-between items-start">
|
<div class="flex justify-between items-start">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center gap-2 mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<span class={`px-2 py-1 rounded-full text-xs font-semibold ${
|
<span
|
||||||
cmd.status === 'verified' ? 'bg-green-100 text-green-800' :
|
class={`px-2 py-1 rounded-full text-xs font-semibold ${
|
||||||
cmd.status === 'rejected' ? 'bg-red-100 text-red-800' :
|
cmd.status === "verified"
|
||||||
'bg-yellow-100 text-yellow-800'
|
? "bg-green-100 text-green-800"
|
||||||
}`}>
|
: cmd.status === "rejected"
|
||||||
|
? "bg-red-100 text-red-800"
|
||||||
|
: "bg-yellow-100 text-yellow-800"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{cmd.status}
|
{cmd.status}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-gray-500">
|
<span class="text-sm text-gray-500">{formatDate(cmd.received_at)}</span>
|
||||||
{formatDate(cmd.received_at)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<pre class="text-sm bg-gray-100 p-3 rounded overflow-x-auto">
|
<pre class="text-sm bg-gray-100 p-3 rounded overflow-x-auto">
|
||||||
{JSON.stringify(cmd.command, null, 2)}
|
{JSON.stringify(cmd.command, null, 2)}
|
||||||
@@ -56,19 +57,19 @@ const CommandQueue: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={cmd.status === 'unverified'}>
|
<Show when={cmd.status === "unverified"}>
|
||||||
<div class="ml-4 flex flex-col gap-2">
|
<div class="ml-4 flex flex-col gap-2">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="success"
|
variant="success"
|
||||||
onClick={() => updateCommandStatus(cmd.id, 'verified')}
|
onClick={() => updateCommandStatus(cmd.id, "verified")}
|
||||||
>
|
>
|
||||||
Verify
|
Verify
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="danger"
|
variant="danger"
|
||||||
onClick={() => updateCommandStatus(cmd.id, 'rejected')}
|
onClick={() => updateCommandStatus(cmd.id, "rejected")}
|
||||||
>
|
>
|
||||||
Reject
|
Reject
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
import { Component, createSignal } from 'solid-js';
|
import { Component, createSignal } from "solid-js";
|
||||||
import { Button } from './ui/Button';
|
import { Button } from "./ui/Button";
|
||||||
import { Card } from './ui/Card';
|
import { Card } from "./ui/Card";
|
||||||
import { Input } from './ui/Input';
|
import { Input } from "./ui/Input";
|
||||||
|
|
||||||
interface LoginProps {
|
interface LoginProps {
|
||||||
onLoginSuccess: (token: string) => void;
|
onLoginSuccess: (token: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Login: Component<LoginProps> = (props) => {
|
const Login: Component<LoginProps> = (props) => {
|
||||||
const [password, setPassword] = createSignal('');
|
const [password, setPassword] = createSignal("");
|
||||||
const [error, setError] = createSignal('');
|
const [error, setError] = createSignal("");
|
||||||
const [isLoading, setIsLoading] = createSignal(false);
|
const [isLoading, setIsLoading] = createSignal(false);
|
||||||
|
|
||||||
const handleSubmit = async (e: Event) => {
|
const handleSubmit = async (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError('');
|
setError("");
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/admin/api/login', {
|
const response = await fetch("/admin/api/login", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ password: password() }),
|
body: JSON.stringify({ password: password() }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({ error: 'Login failed' }));
|
const errorData = await response.json().catch(() => ({ error: "Login failed" }));
|
||||||
throw new Error(errorData.error || 'Invalid password');
|
throw new Error(errorData.error || "Invalid password");
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
props.onLoginSuccess(data.token);
|
props.onLoginSuccess(data.token);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Login failed');
|
setError(err instanceof Error ? err.message : "Login failed");
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -43,9 +43,7 @@ const Login: Component<LoginProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div class="min-h-screen bg-gray-100 flex items-center justify-center">
|
<div class="min-h-screen bg-gray-100 flex items-center justify-center">
|
||||||
<Card class="p-8 w-96">
|
<Card class="p-8 w-96">
|
||||||
<h1 class="text-2xl font-bold text-gray-900 mb-6 text-center">
|
<h1 class="text-2xl font-bold text-gray-900 mb-6 text-center">My Linspirer Admin Login</h1>
|
||||||
My Linspirer Admin Login
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
@@ -69,12 +67,8 @@ const Login: Component<LoginProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button type="submit" size="lg" disabled={isLoading()}>
|
||||||
type="submit"
|
{isLoading() ? "Logging in..." : "Login"}
|
||||||
size="lg"
|
|
||||||
disabled={isLoading()}
|
|
||||||
>
|
|
||||||
{isLoading() ? 'Logging in...' : 'Login'}
|
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Component, createSignal, For, Show } from 'solid-js';
|
import { Component, createSignal, For, Show } from "solid-js";
|
||||||
import type { RequestLog } from '../types';
|
import type { RequestLog } from "../types";
|
||||||
import { Button } from './ui/Button';
|
import { Button } from "./ui/Button";
|
||||||
import { CardContent, CardFooter, CardHeader } from './ui/Card';
|
import { CardContent, CardFooter, CardHeader } from "./ui/Card";
|
||||||
import { Modal, ModalContent } from './ui/Modal';
|
import { Modal, ModalContent } from "./ui/Modal";
|
||||||
|
|
||||||
interface TreeViewProps {
|
interface TreeViewProps {
|
||||||
data: any;
|
data: any;
|
||||||
@@ -12,17 +12,17 @@ interface TreeViewProps {
|
|||||||
|
|
||||||
const TreeView: Component<TreeViewProps> = (props) => {
|
const TreeView: Component<TreeViewProps> = (props) => {
|
||||||
const [isOpen, setIsOpen] = createSignal(props.isRoot ?? false);
|
const [isOpen, setIsOpen] = createSignal(props.isRoot ?? false);
|
||||||
const isObject = typeof props.data === 'object' && props.data !== null;
|
const isObject = typeof props.data === "object" && props.data !== null;
|
||||||
|
|
||||||
const renderValue = (value: any) => {
|
const renderValue = (value: any) => {
|
||||||
switch (typeof value) {
|
switch (typeof value) {
|
||||||
case 'string':
|
case "string":
|
||||||
return <span class="text-green-600">"{value}"</span>;
|
return <span class="text-green-600">"{value}"</span>;
|
||||||
case 'number':
|
case "number":
|
||||||
return <span class="text-blue-600">{value}</span>;
|
return <span class="text-blue-600">{value}</span>;
|
||||||
case 'boolean':
|
case "boolean":
|
||||||
return <span class="text-purple-600">{String(value)}</span>;
|
return <span class="text-purple-600">{String(value)}</span>;
|
||||||
case 'object':
|
case "object":
|
||||||
if (value === null) return <span class="text-gray-500">null</span>;
|
if (value === null) return <span class="text-gray-500">null</span>;
|
||||||
// This case is handled by recursive TreeView
|
// This case is handled by recursive TreeView
|
||||||
default:
|
default:
|
||||||
@@ -36,9 +36,7 @@ const TreeView: Component<TreeViewProps> = (props) => {
|
|||||||
class="flex items-center cursor-pointer hover:bg-gray-100 rounded px-1"
|
class="flex items-center cursor-pointer hover:bg-gray-100 rounded px-1"
|
||||||
onClick={() => setIsOpen(!isOpen())}
|
onClick={() => setIsOpen(!isOpen())}
|
||||||
>
|
>
|
||||||
<span class="w-4 inline-block text-gray-500">
|
<span class="w-4 inline-block text-gray-500">{isObject ? (isOpen() ? "▼" : "►") : ""}</span>
|
||||||
{isObject ? (isOpen() ? '▼' : '►') : ''}
|
|
||||||
</span>
|
|
||||||
<span class="font-semibold text-gray-800">{props.name}:</span>
|
<span class="font-semibold text-gray-800">{props.name}:</span>
|
||||||
<Show when={!isObject}>
|
<Show when={!isObject}>
|
||||||
<span class="ml-2">{renderValue(props.data)}</span>
|
<span class="ml-2">{renderValue(props.data)}</span>
|
||||||
@@ -55,7 +53,6 @@ const TreeView: Component<TreeViewProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
interface RequestDetailsProps {
|
interface RequestDetailsProps {
|
||||||
log: RequestLog;
|
log: RequestLog;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -67,7 +64,9 @@ const RequestDetails: Component<RequestDetailsProps> = (props) => {
|
|||||||
<ModalContent class="max-w-3xl h-auto max-h-[90vh]">
|
<ModalContent class="max-w-3xl h-auto max-h-[90vh]">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<h3 class="text-lg font-medium text-gray-900">Request Details</h3>
|
<h3 class="text-lg font-medium text-gray-900">Request Details</h3>
|
||||||
<p class="text-sm text-gray-500">{props.log.method} at {new Date(props.log.created_at).toLocaleString()}</p>
|
<p class="text-sm text-gray-500">
|
||||||
|
{props.log.method} at {new Date(props.log.created_at).toLocaleString()}
|
||||||
|
</p>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent class="max-h-[70vh] overflow-y-auto">
|
<CardContent class="max-h-[70vh] overflow-y-auto">
|
||||||
@@ -88,11 +87,7 @@ const RequestDetails: Component<RequestDetailsProps> = (props) => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<CardFooter class="flex justify-end pt-4">
|
<CardFooter class="flex justify-end pt-4">
|
||||||
<Button
|
<Button type="button" variant="secondary" onClick={props.onClose}>
|
||||||
type="button"
|
|
||||||
variant="secondary"
|
|
||||||
onClick={props.onClose}
|
|
||||||
>
|
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { Component, createMemo, createResource, createSignal, For, Show } from 'solid-js';
|
import { Component, createMemo, createResource, createSignal, For, Show } from "solid-js";
|
||||||
import { logsApi } from '../api/client';
|
import { logsApi } from "../api/client";
|
||||||
import type { RequestLog as RequestLogType } from '../types';
|
import type { RequestLog as RequestLogType } from "../types";
|
||||||
import { Card } from './ui/Card';
|
import { Card } from "./ui/Card";
|
||||||
import { Input } from './ui/Input';
|
import { Input } from "./ui/Input";
|
||||||
import { Select } from './ui/Select';
|
import { Select } from "./ui/Select";
|
||||||
import RequestDetails from './RequestDetails';
|
import RequestDetails from "./RequestDetails";
|
||||||
|
|
||||||
const RequestLog: Component = () => {
|
const RequestLog: Component = () => {
|
||||||
const [search, setSearch] = createSignal('');
|
const [search, setSearch] = createSignal("");
|
||||||
const [method, setMethod] = createSignal('');
|
const [method, setMethod] = createSignal("");
|
||||||
const [selectedLog, setSelectedLog] = createSignal<RequestLogType | null>(null);
|
const [selectedLog, setSelectedLog] = createSignal<RequestLogType | null>(null);
|
||||||
|
|
||||||
const [logs] = createResource(
|
const [logs] = createResource(
|
||||||
@@ -17,12 +17,12 @@ const RequestLog: Component = () => {
|
|||||||
// Solid's createResource refetches when the source accessor changes.
|
// Solid's createResource refetches when the source accessor changes.
|
||||||
// We can add a debounce here if we want to avoid too many requests.
|
// We can add a debounce here if we want to avoid too many requests.
|
||||||
return logsApi.list(filters);
|
return logsApi.list(filters);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const methods = createMemo(() => {
|
const methods = createMemo(() => {
|
||||||
if (!logs()) return [];
|
if (!logs()) return [];
|
||||||
const allMethods = logs()!.map(log => log.method);
|
const allMethods = logs()!.map((log) => log.method);
|
||||||
return [...new Set(allMethods)];
|
return [...new Set(allMethods)];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -38,7 +38,6 @@ const RequestLog: Component = () => {
|
|||||||
return new Date(dateStr).toLocaleString();
|
return new Date(dateStr).toLocaleString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@@ -61,9 +60,7 @@ const RequestLog: Component = () => {
|
|||||||
class="min-w-[200px]"
|
class="min-w-[200px]"
|
||||||
>
|
>
|
||||||
<option value="">All Methods</option>
|
<option value="">All Methods</option>
|
||||||
<For each={methods()}>
|
<For each={methods()}>{(m) => <option value={m}>{m}</option>}</For>
|
||||||
{(m) => <option value={m}>{m}</option>}
|
|
||||||
</For>
|
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,22 +71,40 @@ const RequestLog: Component = () => {
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Method</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Method</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Time</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Time</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Request Body</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||||
|
Request Body
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-white divide-y divide-gray-200">
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
<Show when={!logs.loading} fallback={<tr><td colspan="3" class="text-center py-4">Loading...</td></tr>}>
|
<Show
|
||||||
<For each={logs()} fallback={
|
when={!logs.loading}
|
||||||
|
fallback={
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center py-4">
|
||||||
|
Loading...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<For
|
||||||
|
each={logs()}
|
||||||
|
fallback={
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" class="text-center py-8 text-gray-500">
|
<td colspan="3" class="text-center py-8 text-gray-500">
|
||||||
No logs found.
|
No logs found.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
{(log) => (
|
{(log) => (
|
||||||
<tr class="hover:bg-gray-50 cursor-pointer" onClick={() => handleLogClick(log)}>
|
<tr class="hover:bg-gray-50 cursor-pointer" onClick={() => handleLogClick(log)}>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{log.method}</td>
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{formatDate(log.created_at)}</td>
|
{log.method}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
{formatDate(log.created_at)}
|
||||||
|
</td>
|
||||||
<td class="px-6 py-4 text-sm text-gray-500 font-mono truncate max-w-lg">
|
<td class="px-6 py-4 text-sm text-gray-500 font-mono truncate max-w-lg">
|
||||||
{JSON.stringify(log.request_body)}
|
{JSON.stringify(log.request_body)}
|
||||||
</td>
|
</td>
|
||||||
@@ -102,9 +117,7 @@ const RequestLog: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
<Show when={selectedLog()}>
|
<Show when={selectedLog()}>{(log) => <RequestDetails log={log()} onClose={handleCloseDetails} />}</Show>
|
||||||
{(log) => <RequestDetails log={log()} onClose={handleCloseDetails} />}
|
|
||||||
</Show>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { Component, createSignal, createResource, For, Show } from 'solid-js';
|
import { Component, createSignal, createResource, For, Show } from "solid-js";
|
||||||
import { rulesApi } from '../api/client';
|
import { rulesApi } from "../api/client";
|
||||||
import type { InterceptionRule } from '../types';
|
import type { InterceptionRule } from "../types";
|
||||||
import { Button } from './ui/Button';
|
import { Button } from "./ui/Button";
|
||||||
import { Card } from './ui/Card';
|
import { Card } from "./ui/Card";
|
||||||
import { Input } from './ui/Input';
|
import { Input } from "./ui/Input";
|
||||||
import { Select } from './ui/Select';
|
import { Select } from "./ui/Select";
|
||||||
import { Textarea } from './ui/Textarea';
|
import { Textarea } from "./ui/Textarea";
|
||||||
|
|
||||||
const RulesList: Component = () => {
|
const RulesList: Component = () => {
|
||||||
const [rules, { refetch }] = createResource(rulesApi.list);
|
const [rules, { refetch }] = createResource(rulesApi.list);
|
||||||
const [showEditor, setShowEditor] = createSignal(false);
|
const [showEditor, setShowEditor] = createSignal(false);
|
||||||
const [editingId, setEditingId] = createSignal<number | null>(null);
|
const [editingId, setEditingId] = createSignal<number | null>(null);
|
||||||
const [editingMethod, setEditingMethod] = createSignal('');
|
const [editingMethod, setEditingMethod] = createSignal("");
|
||||||
const [editingAction, setEditingAction] = createSignal<'passthrough' | 'modify' | 'replace'>('passthrough');
|
const [editingAction, setEditingAction] = createSignal<"passthrough" | "modify" | "replace">("passthrough");
|
||||||
const [editingResponse, setEditingResponse] = createSignal('');
|
const [editingResponse, setEditingResponse] = createSignal("");
|
||||||
|
|
||||||
const toggleRule = async (rule: InterceptionRule) => {
|
const toggleRule = async (rule: InterceptionRule) => {
|
||||||
try {
|
try {
|
||||||
@@ -22,13 +22,13 @@ const RulesList: Component = () => {
|
|||||||
});
|
});
|
||||||
refetch();
|
refetch();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to toggle rule:', err);
|
console.error("Failed to toggle rule:", err);
|
||||||
alert(`Error: ${err}`);
|
alert(`Error: ${err}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteRule = async (id: number) => {
|
const deleteRule = async (id: number) => {
|
||||||
if (!confirm('Are you sure you want to delete this rule?')) return;
|
if (!confirm("Are you sure you want to delete this rule?")) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (id === editingId()) {
|
if (id === editingId()) {
|
||||||
@@ -37,14 +37,14 @@ const RulesList: Component = () => {
|
|||||||
await rulesApi.delete(id);
|
await rulesApi.delete(id);
|
||||||
refetch();
|
refetch();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to delete rule:', err);
|
console.error("Failed to delete rule:", err);
|
||||||
alert(`Error: ${err}`);
|
alert(`Error: ${err}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createNewRule = async () => {
|
const createNewRule = async () => {
|
||||||
if (!editingMethod()) {
|
if (!editingMethod()) {
|
||||||
alert('Please enter a method name');
|
alert("Please enter a method name");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,14 +52,14 @@ const RulesList: Component = () => {
|
|||||||
await rulesApi.create({
|
await rulesApi.create({
|
||||||
method_name: editingMethod(),
|
method_name: editingMethod(),
|
||||||
action: editingAction(),
|
action: editingAction(),
|
||||||
custom_response: editingAction() === 'replace' ? editingResponse() : undefined,
|
custom_response: editingAction() === "replace" ? editingResponse() : undefined,
|
||||||
});
|
});
|
||||||
setShowEditor(false);
|
setShowEditor(false);
|
||||||
setEditingMethod('');
|
setEditingMethod("");
|
||||||
setEditingResponse('');
|
setEditingResponse("");
|
||||||
refetch();
|
refetch();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to create rule:', err);
|
console.error("Failed to create rule:", err);
|
||||||
alert(`Error: ${err}`);
|
alert(`Error: ${err}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -68,15 +68,15 @@ const RulesList: Component = () => {
|
|||||||
setEditingId(rule.id);
|
setEditingId(rule.id);
|
||||||
setEditingMethod(rule.method_name);
|
setEditingMethod(rule.method_name);
|
||||||
setEditingAction(rule.action);
|
setEditingAction(rule.action);
|
||||||
setEditingResponse(rule.custom_response || '');
|
setEditingResponse(rule.custom_response || "");
|
||||||
setShowEditor(true);
|
setShowEditor(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelEdit = () => {
|
const cancelEdit = () => {
|
||||||
setEditingId(null);
|
setEditingId(null);
|
||||||
setEditingMethod('');
|
setEditingMethod("");
|
||||||
setEditingAction('passthrough');
|
setEditingAction("passthrough");
|
||||||
setEditingResponse('');
|
setEditingResponse("");
|
||||||
setShowEditor(false);
|
setShowEditor(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ const RulesList: Component = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!editingMethod()) {
|
if (!editingMethod()) {
|
||||||
alert('Please enter a method name');
|
alert("Please enter a method name");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,12 +96,12 @@ const RulesList: Component = () => {
|
|||||||
await rulesApi.update(id, {
|
await rulesApi.update(id, {
|
||||||
method_name: editingMethod(),
|
method_name: editingMethod(),
|
||||||
action: editingAction(),
|
action: editingAction(),
|
||||||
custom_response: editingAction() === 'replace' ? editingResponse() : undefined,
|
custom_response: editingAction() === "replace" ? editingResponse() : undefined,
|
||||||
});
|
});
|
||||||
cancelEdit();
|
cancelEdit();
|
||||||
refetch();
|
refetch();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to update rule:', err);
|
console.error("Failed to update rule:", err);
|
||||||
alert(`Error: ${err}`);
|
alert(`Error: ${err}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -119,20 +119,16 @@ const RulesList: Component = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showEditor() ? 'Cancel' : '+ New Rule'}
|
{showEditor() ? "Cancel" : "+ New Rule"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={showEditor()}>
|
<Show when={showEditor()}>
|
||||||
<Card class="p-6 mb-4">
|
<Card class="p-6 mb-4">
|
||||||
<h3 class="text-lg font-semibold mb-4">
|
<h3 class="text-lg font-semibold mb-4">{editingId() !== null ? "Edit Rule" : "Create New Rule"}</h3>
|
||||||
{editingId() !== null ? 'Edit Rule' : 'Create New Rule'}
|
|
||||||
</h3>
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
<label class="block text-sm font-medium text-gray-700 mb-1">Method Name</label>
|
||||||
Method Name
|
|
||||||
</label>
|
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={editingMethod()}
|
value={editingMethod()}
|
||||||
@@ -142,9 +138,7 @@ const RulesList: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
<label class="block text-sm font-medium text-gray-700 mb-1">Action</label>
|
||||||
Action
|
|
||||||
</label>
|
|
||||||
<Select
|
<Select
|
||||||
value={editingAction()}
|
value={editingAction()}
|
||||||
onChange={(e) => setEditingAction(e.currentTarget.value as any)}
|
onChange={(e) => setEditingAction(e.currentTarget.value as any)}
|
||||||
@@ -154,11 +148,9 @@ const RulesList: Component = () => {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={editingAction() === 'replace'}>
|
<Show when={editingAction() === "replace"}>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
<label class="block text-sm font-medium text-gray-700 mb-1">Custom Response (JSON)</label>
|
||||||
Custom Response (JSON)
|
|
||||||
</label>
|
|
||||||
<Textarea
|
<Textarea
|
||||||
value={editingResponse()}
|
value={editingResponse()}
|
||||||
onInput={(e) => setEditingResponse(e.currentTarget.value)}
|
onInput={(e) => setEditingResponse(e.currentTarget.value)}
|
||||||
@@ -168,11 +160,7 @@ const RulesList: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Button
|
<Button onClick={saveEdit}>{editingId() !== null ? "Update Rule" : "Create Rule"}</Button>
|
||||||
onClick={saveEdit}
|
|
||||||
>
|
|
||||||
{editingId() !== null ? 'Update Rule' : 'Create Rule'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Show>
|
</Show>
|
||||||
@@ -182,65 +170,64 @@ const RulesList: Component = () => {
|
|||||||
<table class="min-w-full divide-y divide-gray-200">
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
<thead class="bg-gray-50">
|
<thead class="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Method Name</th>
|
||||||
Method Name
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Action</th>
|
||||||
</th>
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">Actions</th>
|
||||||
Action
|
|
||||||
</th>
|
|
||||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
|
||||||
Status
|
|
||||||
</th>
|
|
||||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">
|
|
||||||
Actions
|
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-white divide-y divide-gray-200">
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
<Show when={!rules.loading} fallback={
|
<Show
|
||||||
<tr><td colspan="4" class="text-center py-4">Loading...</td></tr>
|
when={!rules.loading}
|
||||||
}>
|
fallback={
|
||||||
<For each={rules()} fallback={
|
<tr>
|
||||||
|
<td colspan="4" class="text-center py-4">
|
||||||
|
Loading...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<For
|
||||||
|
each={rules()}
|
||||||
|
fallback={
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="text-center py-8 text-gray-500">
|
<td colspan="4" class="text-center py-8 text-gray-500">
|
||||||
No rules configured yet. Click "+ New Rule" to create one.
|
No rules configured yet. Click "+ New Rule" to create one.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}>
|
}
|
||||||
|
>
|
||||||
{(rule) => (
|
{(rule) => (
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-6 py-4 text-sm font-medium text-gray-900">
|
<td class="px-6 py-4 text-sm font-medium text-gray-900">{rule.method_name}</td>
|
||||||
{rule.method_name}
|
|
||||||
</td>
|
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
<span class={`px-2 py-1 rounded-full text-xs ${rule.action === 'replace' ? 'bg-purple-100 text-purple-800' :
|
<span
|
||||||
rule.action === 'modify' ? 'bg-blue-100 text-blue-800' :
|
class={`px-2 py-1 rounded-full text-xs ${
|
||||||
'bg-gray-100 text-gray-800'
|
rule.action === "replace"
|
||||||
}`}>
|
? "bg-purple-100 text-purple-800"
|
||||||
|
: rule.action === "modify"
|
||||||
|
? "bg-blue-100 text-blue-800"
|
||||||
|
: "bg-gray-100 text-gray-800"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{rule.action}
|
{rule.action}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleRule(rule)}
|
onClick={() => toggleRule(rule)}
|
||||||
class={`px-3 py-1 rounded-md text-sm font-medium ${rule.is_enabled
|
class={`px-3 py-1 rounded-md text-sm font-medium ${
|
||||||
? 'bg-green-100 text-green-800'
|
rule.is_enabled ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"
|
||||||
: 'bg-gray-100 text-gray-800'
|
}`}
|
||||||
}`}>
|
>
|
||||||
{rule.is_enabled ? '✓ Enabled' : '✗ Disabled'}
|
{rule.is_enabled ? "✓ Enabled" : "✗ Disabled"}
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||||
<Button
|
<Button variant="ghost" onClick={() => startEdit(rule)} class="mr-4">
|
||||||
variant="ghost"
|
|
||||||
onClick={() => startEdit(rule)}
|
|
||||||
class="mr-4">
|
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button variant="link" onClick={() => deleteRule(rule.id)}>
|
||||||
variant="link"
|
|
||||||
onClick={() => deleteRule(rule.id)}
|
|
||||||
>
|
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,35 +1,37 @@
|
|||||||
import { Component, JSX, splitProps } from 'solid-js';
|
import { Component, JSX, splitProps } from "solid-js";
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed',
|
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
primary: 'bg-indigo-600 text-white hover:bg-indigo-700',
|
primary: "bg-indigo-600 text-white hover:bg-indigo-700",
|
||||||
secondary: 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50',
|
secondary: "bg-white text-gray-700 border border-gray-300 hover:bg-gray-50",
|
||||||
danger: 'bg-red-600 text-white hover:bg-red-700',
|
danger: "bg-red-600 text-white hover:bg-red-700",
|
||||||
success: 'bg-green-600 text-white hover:bg-green-700',
|
success: "bg-green-600 text-white hover:bg-green-700",
|
||||||
ghost: 'text-indigo-600 hover:text-indigo-900',
|
ghost: "text-indigo-600 hover:text-indigo-900",
|
||||||
link: 'text-red-600 hover:text-red-900',
|
link: "text-red-600 hover:text-red-900",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
sm: 'px-3 py-1',
|
sm: "px-3 py-1",
|
||||||
md: 'px-4 py-2',
|
md: "px-4 py-2",
|
||||||
lg: 'w-full px-4 py-2',
|
lg: "w-full px-4 py-2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: 'primary',
|
variant: "primary",
|
||||||
size: 'md',
|
size: "md",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {}
|
export interface ButtonProps
|
||||||
|
extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {}
|
||||||
|
|
||||||
const Button: Component<ButtonProps> = (props) => {
|
const Button: Component<ButtonProps> = (props) => {
|
||||||
const [local, others] = splitProps(props, ['variant', 'size', 'class']);
|
const [local, others] = splitProps(props, ["variant", "size", "class"]);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
class={buttonVariants({ variant: local.variant, size: local.size, class: local.class })}
|
class={buttonVariants({ variant: local.variant, size: local.size, class: local.class })}
|
||||||
|
|||||||
@@ -1,59 +1,39 @@
|
|||||||
import { Component, JSX, splitProps } from 'solid-js';
|
import { Component, JSX, splitProps } from "solid-js";
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
const cardVariants = cva('bg-white shadow rounded-lg', {
|
const cardVariants = cva("bg-white shadow rounded-lg", {
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
primary: 'overflow-hidden',
|
primary: "overflow-hidden",
|
||||||
withPadding: 'p-6',
|
withPadding: "p-6",
|
||||||
withPaddingAndDivider: 'p-4 divide-y divide-gray-200',
|
withPaddingAndDivider: "p-4 divide-y divide-gray-200",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: 'primary',
|
variant: "primary",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface CardProps extends JSX.HTMLAttributes<HTMLDivElement>, VariantProps<typeof cardVariants> {}
|
export interface CardProps extends JSX.HTMLAttributes<HTMLDivElement>, VariantProps<typeof cardVariants> {}
|
||||||
|
|
||||||
const Card: Component<CardProps> = (props) => {
|
const Card: Component<CardProps> = (props) => {
|
||||||
const [local, others] = splitProps(props, ['variant', 'class']);
|
const [local, others] = splitProps(props, ["variant", "class"]);
|
||||||
return (
|
return <div class={cardVariants({ variant: local.variant, class: local.class })} {...others} />;
|
||||||
<div
|
|
||||||
class={cardVariants({ variant: local.variant, class: local.class })}
|
|
||||||
{...others}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const CardHeader: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
|
const CardHeader: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
|
||||||
const [local, others] = splitProps(props, ['class']);
|
const [local, others] = splitProps(props, ["class"]);
|
||||||
return (
|
return <div class={`px-6 py-4 border-b border-gray-200 ${local.class}`} {...others} />;
|
||||||
<div
|
|
||||||
class={`px-6 py-4 border-b border-gray-200 ${local.class}`}
|
|
||||||
{...others}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const CardContent: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
|
const CardContent: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
|
||||||
const [local, others] = splitProps(props, ['class']);
|
const [local, others] = splitProps(props, ["class"]);
|
||||||
return (
|
return <div class={`px-6 py-4 space-y-4 ${local.class}`} {...others} />;
|
||||||
<div
|
|
||||||
class={`px-6 py-4 space-y-4 ${local.class}`}
|
|
||||||
{...others}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const CardFooter: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
|
const CardFooter: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
|
||||||
const [local, others] = splitProps(props, ['class']);
|
const [local, others] = splitProps(props, ["class"]);
|
||||||
return (
|
return <div class={`px-6 py-4 border-t border-gray-200 ${local.class}`} {...others} />;
|
||||||
<div
|
|
||||||
class={`px-6 py-4 border-t border-gray-200 ${local.class}`}
|
|
||||||
{...others}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Card, CardHeader, CardContent, CardFooter, cardVariants };
|
export { Card, CardHeader, CardContent, CardFooter, cardVariants };
|
||||||
|
|||||||
@@ -1,30 +1,27 @@
|
|||||||
import { Component, JSX, splitProps } from 'solid-js';
|
import { Component, JSX, splitProps } from "solid-js";
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
const inputVariants = cva(
|
const inputVariants = cva(
|
||||||
'w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500',
|
"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
primary: '',
|
primary: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: 'primary',
|
variant: "primary",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface InputProps extends JSX.InputHTMLAttributes<HTMLInputElement>, VariantProps<typeof inputVariants> {}
|
export interface InputProps
|
||||||
|
extends JSX.InputHTMLAttributes<HTMLInputElement>,
|
||||||
|
VariantProps<typeof inputVariants> {}
|
||||||
|
|
||||||
const Input: Component<InputProps> = (props) => {
|
const Input: Component<InputProps> = (props) => {
|
||||||
const [local, others] = splitProps(props, ['variant', 'class']);
|
const [local, others] = splitProps(props, ["variant", "class"]);
|
||||||
return (
|
return <input class={inputVariants({ variant: local.variant, class: local.class })} {...others} />;
|
||||||
<input
|
|
||||||
class={inputVariants({ variant: local.variant, class: local.class })}
|
|
||||||
{...others}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Input, inputVariants };
|
export { Input, inputVariants };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, JSX, splitProps } from 'solid-js';
|
import { Component, JSX, splitProps } from "solid-js";
|
||||||
|
|
||||||
const Modal: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
|
const Modal: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
|
||||||
const [local, others] = splitProps(props, ['class']);
|
const [local, others] = splitProps(props, ["class"]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={`fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50 ${local.class || ""}`}
|
class={`fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center z-50 ${local.class || ""}`}
|
||||||
@@ -11,13 +11,8 @@ const Modal: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ModalContent: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
|
const ModalContent: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
|
||||||
const [local, others] = splitProps(props, ['class']);
|
const [local, others] = splitProps(props, ["class"]);
|
||||||
return (
|
return <div class={`bg-white rounded-lg shadow-xl w-full mx-4 ${local.class || ""}`} {...others} />;
|
||||||
<div
|
|
||||||
class={`bg-white rounded-lg shadow-xl w-full mx-4 ${local.class || ""}`}
|
|
||||||
{...others}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Modal, ModalContent };
|
export { Modal, ModalContent };
|
||||||
|
|||||||
@@ -1,30 +1,24 @@
|
|||||||
import { Component, JSX, splitProps } from 'solid-js';
|
import { Component, JSX, splitProps } from "solid-js";
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
const selectVariants = cva(
|
const selectVariants = cva("w-full border border-gray-300 rounded-md px-3 py-2", {
|
||||||
'w-full border border-gray-300 rounded-md px-3 py-2',
|
|
||||||
{
|
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
primary: '',
|
primary: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: 'primary',
|
variant: "primary",
|
||||||
},
|
},
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export interface SelectProps extends JSX.SelectHTMLAttributes<HTMLSelectElement>, VariantProps<typeof selectVariants> {}
|
export interface SelectProps
|
||||||
|
extends JSX.SelectHTMLAttributes<HTMLSelectElement>,
|
||||||
|
VariantProps<typeof selectVariants> {}
|
||||||
|
|
||||||
const Select: Component<SelectProps> = (props) => {
|
const Select: Component<SelectProps> = (props) => {
|
||||||
const [local, others] = splitProps(props, ['variant', 'class']);
|
const [local, others] = splitProps(props, ["variant", "class"]);
|
||||||
return (
|
return <select class={selectVariants({ variant: local.variant, class: local.class })} {...others} />;
|
||||||
<select
|
|
||||||
class={selectVariants({ variant: local.variant, class: local.class })}
|
|
||||||
{...others}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Select, selectVariants };
|
export { Select, selectVariants };
|
||||||
|
|||||||
@@ -1,30 +1,24 @@
|
|||||||
import { Component, JSX, splitProps } from 'solid-js';
|
import { Component, JSX, splitProps } from "solid-js";
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
const textareaVariants = cva(
|
const textareaVariants = cva("w-full border border-gray-300 rounded-md px-3 py-2 font-mono text-sm", {
|
||||||
'w-full border border-gray-300 rounded-md px-3 py-2 font-mono text-sm',
|
|
||||||
{
|
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
primary: '',
|
primary: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: 'primary',
|
variant: "primary",
|
||||||
},
|
},
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export interface TextareaProps extends JSX.TextareaHTMLAttributes<HTMLTextAreaElement>, VariantProps<typeof textareaVariants> {}
|
export interface TextareaProps
|
||||||
|
extends JSX.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
||||||
|
VariantProps<typeof textareaVariants> {}
|
||||||
|
|
||||||
const Textarea: Component<TextareaProps> = (props) => {
|
const Textarea: Component<TextareaProps> = (props) => {
|
||||||
const [local, others] = splitProps(props, ['variant', 'class']);
|
const [local, others] = splitProps(props, ["variant", "class"]);
|
||||||
return (
|
return <textarea class={textareaVariants({ variant: local.variant, class: local.class })} {...others} />;
|
||||||
<textarea
|
|
||||||
class={textareaVariants({ variant: local.variant, class: local.class })}
|
|
||||||
{...others}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Textarea, textareaVariants };
|
export { Textarea, textareaVariants };
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { render } from 'solid-js/web';
|
import { render } from "solid-js/web";
|
||||||
import App from './App';
|
import App from "./App";
|
||||||
import './styles/index.css';
|
import "./styles/index.css";
|
||||||
|
|
||||||
const root = document.getElementById('root');
|
const root = document.getElementById("root");
|
||||||
|
|
||||||
if (!root) throw new Error('Root element not found');
|
if (!root) throw new Error("Root element not found");
|
||||||
|
|
||||||
render(() => <App />, root);
|
render(() => <App />, root);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export interface InterceptionRule {
|
export interface InterceptionRule {
|
||||||
id: number;
|
id: number;
|
||||||
method_name: string;
|
method_name: string;
|
||||||
action: 'passthrough' | 'modify' | 'replace';
|
action: "passthrough" | "modify" | "replace";
|
||||||
custom_response: any | null;
|
custom_response: any | null;
|
||||||
is_enabled: boolean;
|
is_enabled: boolean;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
@@ -11,7 +11,7 @@ export interface InterceptionRule {
|
|||||||
export interface Command {
|
export interface Command {
|
||||||
id: number;
|
id: number;
|
||||||
command: any;
|
command: any;
|
||||||
status: 'unverified' | 'verified' | 'rejected';
|
status: "unverified" | "verified" | "rejected";
|
||||||
received_at: string;
|
received_at: string;
|
||||||
processed_at?: string;
|
processed_at?: string;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
@@ -36,13 +36,13 @@ export interface UpdateCommandRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Request {
|
export interface Request {
|
||||||
headers: Record<string, {value: string, modified: boolean}>;
|
headers: Record<string, { value: string; modified: boolean }>;
|
||||||
body: {value: any, modified: boolean};
|
body: { value: any; modified: boolean };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Response {
|
export interface Response {
|
||||||
headers: Record<string, {value: string, modified: boolean}>;
|
headers: Record<string, { value: string; modified: boolean }>;
|
||||||
body: {value: any, modified: boolean};
|
body: { value: any; modified: boolean };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestLog {
|
export interface RequestLog {
|
||||||
@@ -50,7 +50,7 @@ export interface RequestLog {
|
|||||||
method: string;
|
method: string;
|
||||||
request_body: object;
|
request_body: object;
|
||||||
response_body: object;
|
response_body: object;
|
||||||
request_interception_action: string,
|
request_interception_action: string;
|
||||||
response_interception_action: string,
|
response_interception_action: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: [
|
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||||
"./index.html",
|
|
||||||
"./src/**/*.{js,ts,jsx,tsx}",
|
|
||||||
],
|
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from "vite";
|
||||||
import solid from 'vite-plugin-solid';
|
import solid from "vite-plugin-solid";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [solid()],
|
plugins: [solid()],
|
||||||
base: '/admin/',
|
base: "/admin/",
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: "dist",
|
||||||
assetsDir: 'assets',
|
assetsDir: "assets",
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
minify: 'terser',
|
minify: "terser",
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/admin/api': {
|
"/admin/api": {
|
||||||
target: 'http://localhost:8080',
|
target: "http://localhost:8080",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user