Compare commits
12 Commits
06dd1c20d0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 813f6d4c75 | |||
| 7f1c110f8f | |||
| f83ecf864a | |||
| 2d5ffac56c | |||
| bdec163fb0 | |||
| c50274452c | |||
| 145d37193c | |||
| 20877abbc7 | |||
| 9fcb0447ee | |||
| df3e022049 | |||
| dda335d501 | |||
| 791c99097c |
@@ -1,7 +1,7 @@
|
|||||||
import { NextRequest } from 'next/server';
|
import { NextRequest } from 'next/server';
|
||||||
import { createSupervisorClient } from '@/lib/supervisor/client';
|
import { createSupervisorClient } from '@/lib/supervisor/client';
|
||||||
import { createApiLogger, generateRequestId } from '@/lib/utils/api-logger';
|
import { createApiLogger } from '@/lib/utils/api-logger';
|
||||||
import { formatError } from '@/lib/utils/logger';
|
import { formatError, generateRequestId } from '@/lib/utils/logger';
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const dynamic = 'force-dynamic';
|
|||||||
export const GET = withLogging(async (request: NextRequest) => {
|
export const GET = withLogging(async (request: NextRequest) => {
|
||||||
const searchParams = request.nextUrl.searchParams;
|
const searchParams = request.nextUrl.searchParams;
|
||||||
const offset = parseInt(searchParams.get('offset') || '-4096', 10);
|
const offset = parseInt(searchParams.get('offset') || '-4096', 10);
|
||||||
const length = parseInt(searchParams.get('length') || '4096', 10);
|
const length = parseInt(searchParams.get('length') || '0', 10);
|
||||||
|
|
||||||
const client = createSupervisorClient();
|
const client = createSupervisorClient();
|
||||||
const logs = await client.readLog(offset, length);
|
const logs = await client.readLog(offset, length);
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ export default function ProcessesPage() {
|
|||||||
) : viewMode === 'grouped' ? (
|
) : viewMode === 'grouped' ? (
|
||||||
<GroupView processes={displayedProcesses} />
|
<GroupView processes={displayedProcesses} />
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-4 md:gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-4 md:gap-6 md:grid-cols-2 lg:grid-cols-3 auto-rows-fr">
|
||||||
{displayedProcesses.map((process, index) => {
|
{displayedProcesses.map((process, index) => {
|
||||||
const fullName = `${process.group}:${process.name}`;
|
const fullName = `${process.group}:${process.name}`;
|
||||||
const isFocused = index === focusedIndex;
|
const isFocused = index === focusedIndex;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { ProcessInfo, ProcessState } from '@/lib/supervisor/types';
|
import { ProcessInfo, ProcessState } from '@/lib/supervisor/types';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
|
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
|
||||||
|
import { chartColors } from '@/lib/utils/chartColors';
|
||||||
|
|
||||||
interface GroupStatisticsProps {
|
interface GroupStatisticsProps {
|
||||||
processes: ProcessInfo[];
|
processes: ProcessInfo[];
|
||||||
@@ -50,11 +51,19 @@ export function GroupStatistics({ processes }: GroupStatisticsProps) {
|
|||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<XAxis dataKey="name" />
|
<XAxis dataKey="name" />
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Tooltip />
|
<Tooltip
|
||||||
|
contentStyle={{
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: '#ffffff',
|
||||||
|
}}
|
||||||
|
itemStyle={{ color: '#ffffff' }}
|
||||||
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar dataKey="running" stackId="a" fill="hsl(var(--success))" name="Running" />
|
<Bar dataKey="running" stackId="a" fill={chartColors.running} name="Running" />
|
||||||
<Bar dataKey="stopped" stackId="a" fill="hsl(var(--muted-foreground))" name="Stopped" />
|
<Bar dataKey="stopped" stackId="a" fill={chartColors.stopped} name="Stopped" />
|
||||||
<Bar dataKey="fatal" stackId="a" fill="hsl(var(--destructive))" name="Fatal" />
|
<Bar dataKey="fatal" stackId="a" fill={chartColors.fatal} name="Fatal" />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { ProcessInfo, ProcessState } from '@/lib/supervisor/types';
|
import { ProcessInfo, ProcessState } from '@/lib/supervisor/types';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts';
|
import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts';
|
||||||
|
import { chartColors } from '@/lib/utils/chartColors';
|
||||||
|
|
||||||
interface ProcessStateChartProps {
|
interface ProcessStateChartProps {
|
||||||
processes: ProcessInfo[];
|
processes: ProcessInfo[];
|
||||||
@@ -23,12 +24,12 @@ export function ProcessStateChart({ processes }: ProcessStateChartProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{ name: 'Running', value: stateCounts.running, color: 'hsl(var(--success))' },
|
{ name: 'Running', value: stateCounts.running, color: chartColors.running },
|
||||||
{ name: 'Stopped', value: stateCounts.stopped, color: 'hsl(var(--muted-foreground))' },
|
{ name: 'Stopped', value: stateCounts.stopped, color: chartColors.stopped },
|
||||||
{ name: 'Fatal', value: stateCounts.fatal, color: 'hsl(var(--destructive))' },
|
{ name: 'Fatal', value: stateCounts.fatal, color: chartColors.fatal },
|
||||||
{ name: 'Starting', value: stateCounts.starting, color: 'hsl(var(--warning))' },
|
{ name: 'Starting', value: stateCounts.starting, color: chartColors.starting },
|
||||||
{ name: 'Stopping', value: stateCounts.stopping, color: 'hsl(var(--accent))' },
|
{ name: 'Stopping', value: stateCounts.stopping, color: chartColors.stopping },
|
||||||
{ name: 'Other', value: stateCounts.other, color: 'hsl(var(--muted))' },
|
{ name: 'Other', value: stateCounts.other, color: chartColors.muted },
|
||||||
].filter((item) => item.value > 0);
|
].filter((item) => item.value > 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -53,7 +54,15 @@ export function ProcessStateChart({ processes }: ProcessStateChartProps) {
|
|||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||||
))}
|
))}
|
||||||
</Pie>
|
</Pie>
|
||||||
<Tooltip />
|
<Tooltip
|
||||||
|
contentStyle={{
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: '#ffffff',
|
||||||
|
}}
|
||||||
|
itemStyle={{ color: '#ffffff' }}
|
||||||
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
</PieChart>
|
</PieChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { ProcessInfo, ProcessState } from '@/lib/supervisor/types';
|
import { ProcessInfo, ProcessState } from '@/lib/supervisor/types';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
|
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
|
||||||
|
import { chartColors } from '@/lib/utils/chartColors';
|
||||||
|
|
||||||
interface ProcessUptimeChartProps {
|
interface ProcessUptimeChartProps {
|
||||||
processes: ProcessInfo[];
|
processes: ProcessInfo[];
|
||||||
@@ -56,9 +57,16 @@ export function ProcessUptimeChart({ processes }: ProcessUptimeChartProps) {
|
|||||||
const minutes = Math.floor((value - hours) * 60);
|
const minutes = Math.floor((value - hours) * 60);
|
||||||
return `${hours}h ${minutes}m`;
|
return `${hours}h ${minutes}m`;
|
||||||
}}
|
}}
|
||||||
|
contentStyle={{
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: '#ffffff',
|
||||||
|
}}
|
||||||
|
itemStyle={{ color: '#ffffff' }}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar dataKey="uptime" fill="hsl(var(--success))" name="Uptime (hours)" />
|
<Bar dataKey="uptime" fill={chartColors.success} name="Uptime (hours)" />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ export function ConfigTable({ configs }: ConfigTableProps) {
|
|||||||
Autostart <SortIcon field="autostart" />
|
Autostart <SortIcon field="autostart" />
|
||||||
</th>
|
</th>
|
||||||
<th className="text-center p-3">Priority</th>
|
<th className="text-center p-3">Priority</th>
|
||||||
<th className="text-center p-3">Processes</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -117,8 +116,7 @@ export function ConfigTable({ configs }: ConfigTableProps) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-3 text-center text-sm">{config.priority}</td>
|
<td className="p-3 text-center text-sm">{config.process_prio}</td>
|
||||||
<td className="p-3 text-center text-sm">{config.numprocs}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -159,15 +157,9 @@ export function ConfigTable({ configs }: ConfigTableProps) {
|
|||||||
<div className="mt-1 text-xs break-all">{config.directory}</div>
|
<div className="mt-1 text-xs break-all">{config.directory}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-4 pt-2">
|
<div className="pt-2">
|
||||||
<div>
|
<span className="text-muted-foreground">Priority:</span>
|
||||||
<span className="text-muted-foreground">Priority:</span>
|
<span className="ml-2 font-mono">{config.process_prio}</span>
|
||||||
<span className="ml-2 font-mono">{config.priority}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-muted-foreground">Processes:</span>
|
|
||||||
<span className="ml-2 font-mono">{config.numprocs}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function GroupCard({ groupName, processes }: GroupCardProps) {
|
|||||||
return (
|
return (
|
||||||
<Card className="overflow-hidden">
|
<Card className="overflow-hidden">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex flex-col md:flex-row items-start md:items-center gap-3 md:justify-between">
|
||||||
<div className="flex items-center gap-3 flex-1">
|
<div className="flex items-center gap-3 flex-1">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -80,13 +80,13 @@ export function GroupCard({ groupName, processes }: GroupCardProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex flex-wrap gap-2 w-full md:w-auto">
|
||||||
<Button
|
<Button
|
||||||
variant="success"
|
variant="success"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleStart}
|
onClick={handleStart}
|
||||||
disabled={isLoading || stats.stopped === 0}
|
disabled={isLoading || stats.stopped === 0}
|
||||||
className="gap-2"
|
className="gap-2 flex-1 sm:flex-initial"
|
||||||
>
|
>
|
||||||
<Play className="h-4 w-4" />
|
<Play className="h-4 w-4" />
|
||||||
Start All
|
Start All
|
||||||
@@ -96,7 +96,7 @@ export function GroupCard({ groupName, processes }: GroupCardProps) {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleStop}
|
onClick={handleStop}
|
||||||
disabled={isLoading || stats.running === 0}
|
disabled={isLoading || stats.running === 0}
|
||||||
className="gap-2"
|
className="gap-2 flex-1 sm:flex-initial"
|
||||||
>
|
>
|
||||||
<Square className="h-4 w-4" />
|
<Square className="h-4 w-4" />
|
||||||
Stop All
|
Stop All
|
||||||
@@ -106,7 +106,7 @@ export function GroupCard({ groupName, processes }: GroupCardProps) {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleRestart}
|
onClick={handleRestart}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="gap-2"
|
className="gap-2 flex-1 sm:flex-initial"
|
||||||
>
|
>
|
||||||
<RotateCw className={cn('h-4 w-4', isLoading && 'animate-spin')} />
|
<RotateCw className={cn('h-4 w-4', isLoading && 'animate-spin')} />
|
||||||
Restart All
|
Restart All
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function Navbar() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
<nav className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||||
<div className="container flex h-16 items-center px-4 md:px-6">
|
<div className="container mx-auto flex h-16 items-center px-4 md:px-6">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<Link href="/" className="flex items-center gap-2 mr-4 md:mr-8">
|
<Link href="/" className="flex items-center gap-2 mr-4 md:mr-8">
|
||||||
<Activity className="h-6 w-6 text-primary" />
|
<Activity className="h-6 w-6 text-primary" />
|
||||||
@@ -116,9 +116,9 @@ export function Navbar() {
|
|||||||
|
|
||||||
{/* Mobile Menu Drawer */}
|
{/* Mobile Menu Drawer */}
|
||||||
{mobileMenuOpen && (
|
{mobileMenuOpen && (
|
||||||
<div className="md:hidden fixed inset-0 top-16 z-50 bg-background/95 backdrop-blur-sm">
|
<div className="md:hidden fixed inset-0 top-16 z-50">
|
||||||
<div className="container px-4 py-6">
|
<div className="container">
|
||||||
<nav className="flex flex-col gap-2">
|
<nav className="flex flex-col gap-2 bg-background/95 backdrop-blur-md rounded-lg p-4">
|
||||||
{navItems.map((item) => (
|
{navItems.map((item) => (
|
||||||
<Link key={item.href} href={item.href}>
|
<Link key={item.href} href={item.href}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function LogControls({
|
|||||||
isClearing = false,
|
isClearing = false,
|
||||||
}: LogControlsProps) {
|
}: LogControlsProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant={isPlaying ? 'destructive' : 'success'}
|
variant={isPlaying ? 'destructive' : 'success'}
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ interface LogSearchProps {
|
|||||||
|
|
||||||
export function LogSearch({ value, onChange, placeholder = 'Search logs...' }: LogSearchProps) {
|
export function LogSearch({ value, onChange, placeholder = 'Search logs...' }: LogSearchProps) {
|
||||||
return (
|
return (
|
||||||
<div className="relative flex-1 max-w-md">
|
<div className="relative w-full sm:flex-1 sm:max-w-md">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export function BatchActions({ selectedProcesses, processes, onClearSelection }:
|
|||||||
className="gap-2 flex-1 sm:flex-initial"
|
className="gap-2 flex-1 sm:flex-initial"
|
||||||
>
|
>
|
||||||
<Play className="h-4 w-4" />
|
<Play className="h-4 w-4" />
|
||||||
<span className="hidden sm:inline">Start Selected</span>
|
<span>Start</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
@@ -92,7 +92,7 @@ export function BatchActions({ selectedProcesses, processes, onClearSelection }:
|
|||||||
className="gap-2 flex-1 sm:flex-initial"
|
className="gap-2 flex-1 sm:flex-initial"
|
||||||
>
|
>
|
||||||
<Square className="h-4 w-4" />
|
<Square className="h-4 w-4" />
|
||||||
<span className="hidden sm:inline">Stop Selected</span>
|
<span>Stop</span>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -102,7 +102,7 @@ export function BatchActions({ selectedProcesses, processes, onClearSelection }:
|
|||||||
className="gap-2 flex-1 sm:flex-initial"
|
className="gap-2 flex-1 sm:flex-initial"
|
||||||
>
|
>
|
||||||
<RotateCw className={cn('h-4 w-4', isLoading && 'animate-spin')} />
|
<RotateCw className={cn('h-4 w-4', isLoading && 'animate-spin')} />
|
||||||
<span className="hidden sm:inline">Restart Selected</span>
|
<span>Restart</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function ProcessCard({ process, isSelected = false, isFocused = false, on
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
className={cn(
|
||||||
'transition-all hover:shadow-lg animate-fade-in',
|
'transition-all hover:shadow-lg animate-fade-in h-full flex flex-col',
|
||||||
onSelectionChange && 'cursor-pointer',
|
onSelectionChange && 'cursor-pointer',
|
||||||
isSelected && 'ring-2 ring-primary ring-offset-2',
|
isSelected && 'ring-2 ring-primary ring-offset-2',
|
||||||
isFocused && 'ring-2 ring-accent ring-offset-2 shadow-xl'
|
isFocused && 'ring-2 ring-accent ring-offset-2 shadow-xl'
|
||||||
@@ -82,7 +82,7 @@ export function ProcessCard({ process, isSelected = false, isFocused = false, on
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4 flex-1 flex flex-col justify-between">
|
||||||
{/* Metrics */}
|
{/* Metrics */}
|
||||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -22,11 +22,6 @@ export function Providers({ children }: { children: ReactNode }) {
|
|||||||
retry: 1,
|
retry: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
logger: {
|
|
||||||
log: (message) => clientLogger.debug(message),
|
|
||||||
warn: (message) => clientLogger.warn(message),
|
|
||||||
error: (error) => clientLogger.error('React Query error', error),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ export function useRestartProcess() {
|
|||||||
|
|
||||||
// Log Management
|
// Log Management
|
||||||
|
|
||||||
async function fetchMainLog(offset: number = -4096, length: number = 4096): Promise<{ logs: string }> {
|
async function fetchMainLog(offset: number = -4096, length: number = 0): Promise<{ logs: string }> {
|
||||||
const response = await fetch(`/api/supervisor/logs?offset=${offset}&length=${length}`);
|
const response = await fetch(`/api/supervisor/logs?offset=${offset}&length=${length}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
|
|||||||
@@ -62,38 +62,39 @@ export const ConfigInfoSchema = z.object({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
group: z.string(),
|
group: z.string(),
|
||||||
autostart: z.boolean(),
|
autostart: z.boolean(),
|
||||||
directory: z.union([z.string(), z.null()]),
|
autorestart: z.string().optional(), // "auto", "none", or "unexpected"
|
||||||
|
directory: z.string(),
|
||||||
command: z.string(),
|
command: z.string(),
|
||||||
environment: z.union([z.string(), z.null()]),
|
|
||||||
exitcodes: z.array(z.number()),
|
exitcodes: z.array(z.number()),
|
||||||
|
group_prio: z.number(),
|
||||||
|
inuse: z.boolean(),
|
||||||
|
killasgroup: z.boolean(),
|
||||||
|
process_prio: z.number(),
|
||||||
redirect_stderr: z.boolean(),
|
redirect_stderr: z.boolean(),
|
||||||
|
serverurl: z.string(),
|
||||||
|
startretries: z.number(),
|
||||||
|
startsecs: z.number(),
|
||||||
stderr_capture_maxbytes: z.number(),
|
stderr_capture_maxbytes: z.number(),
|
||||||
stderr_events_enabled: z.boolean(),
|
stderr_events_enabled: z.boolean(),
|
||||||
stderr_logfile: z.string(),
|
stderr_logfile: z.string(),
|
||||||
stderr_logfile_backups: z.number(),
|
stderr_logfile_backups: z.number(),
|
||||||
stderr_logfile_maxbytes: z.number(),
|
stderr_logfile_maxbytes: z.number(),
|
||||||
|
stderr_syslog: z.boolean(),
|
||||||
|
stopsignal: z.number(), // Signal number (e.g., 15 for SIGTERM)
|
||||||
|
stopwaitsecs: z.number(),
|
||||||
stdout_capture_maxbytes: z.number(),
|
stdout_capture_maxbytes: z.number(),
|
||||||
stdout_events_enabled: z.boolean(),
|
stdout_events_enabled: z.boolean(),
|
||||||
stdout_logfile: z.string(),
|
stdout_logfile: z.string(),
|
||||||
stdout_logfile_backups: z.number(),
|
stdout_logfile_backups: z.number(),
|
||||||
stdout_logfile_maxbytes: z.number(),
|
stdout_logfile_maxbytes: z.number(),
|
||||||
stopsignal: z.string(),
|
stdout_syslog: z.boolean(),
|
||||||
stopwaitsecs: z.number(),
|
uid: z.string(), // Username string
|
||||||
priority: z.number(),
|
|
||||||
startretries: z.number(),
|
|
||||||
startsecs: z.number(),
|
|
||||||
process_name: z.string(),
|
|
||||||
numprocs: z.number(),
|
|
||||||
numprocs_start: z.number(),
|
|
||||||
uid: z.union([z.number(), z.null()]),
|
|
||||||
username: z.union([z.string(), z.null()]),
|
|
||||||
inuse: z.boolean(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ReloadConfigResultSchema = z.object({
|
export const ReloadConfigResultSchema = z.object({
|
||||||
added: z.array(z.array(z.string())),
|
added: z.array(z.array(z.string())).optional().default([]),
|
||||||
changed: z.array(z.array(z.string())),
|
changed: z.array(z.array(z.string())).optional().default([]),
|
||||||
removed: z.array(z.array(z.string())),
|
removed: z.array(z.array(z.string())).optional().default([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
// TypeScript Types
|
// TypeScript Types
|
||||||
|
|||||||
53
lib/utils/chartColors.ts
Normal file
53
lib/utils/chartColors.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Chart color utilities for Recharts compatibility
|
||||||
|
*
|
||||||
|
* Recharts doesn't properly parse CSS custom properties or OKLCH colors,
|
||||||
|
* so we provide static hex colors that work reliably across themes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const chartColors = {
|
||||||
|
primary: '#3b82f6', // Blue
|
||||||
|
success: '#22c55e', // Green
|
||||||
|
warning: '#eab308', // Yellow
|
||||||
|
destructive: '#ef4444', // Red
|
||||||
|
accent: '#06b6d4', // Cyan
|
||||||
|
muted: '#9ca3af', // Gray
|
||||||
|
running: '#22c55e', // Same as success
|
||||||
|
stopped: '#6b7280', // Darker gray
|
||||||
|
fatal: '#ef4444', // Same as destructive
|
||||||
|
starting: '#eab308', // Same as warning
|
||||||
|
backoff: '#f97316', // Orange
|
||||||
|
stopping: '#fb923c', // Light orange
|
||||||
|
exited: '#9ca3af', // Same as muted
|
||||||
|
unknown: '#64748b', // Slate
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get color for process state
|
||||||
|
*/
|
||||||
|
export function getStateColor(state: string): string {
|
||||||
|
const stateMap: Record<string, string> = {
|
||||||
|
running: chartColors.running,
|
||||||
|
stopped: chartColors.stopped,
|
||||||
|
fatal: chartColors.fatal,
|
||||||
|
starting: chartColors.starting,
|
||||||
|
backoff: chartColors.backoff,
|
||||||
|
stopping: chartColors.stopping,
|
||||||
|
exited: chartColors.exited,
|
||||||
|
unknown: chartColors.unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
return stateMap[state.toLowerCase()] || chartColors.muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color palette for multiple data series
|
||||||
|
*/
|
||||||
|
export const colorPalette = [
|
||||||
|
chartColors.primary,
|
||||||
|
chartColors.success,
|
||||||
|
chartColors.warning,
|
||||||
|
chartColors.accent,
|
||||||
|
chartColors.destructive,
|
||||||
|
chartColors.muted,
|
||||||
|
];
|
||||||
Reference in New Issue
Block a user