mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-08-09 02:54:37 +00:00
clean up
This commit is contained in:
+395
-117
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
import * as React from 'react';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { ServerConfigSettings } from '@/components/settings/server-config-settings';
|
||||
import { AdminSettings } from '@/components/settings/admin-settings';
|
||||
import { SiteHeader } from '@/components/site-header';
|
||||
import { AppSidebar } from '@/components/app-sidebar';
|
||||
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
|
||||
@@ -22,10 +23,14 @@ export default function SettingsPage() {
|
||||
<Tabs defaultValue='server' className='w-full'>
|
||||
<TabsList className='mb-4'>
|
||||
<TabsTrigger value='server'>Server Configuration</TabsTrigger>
|
||||
<TabsTrigger value='admin'>Admin Settings</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value='server'>
|
||||
<ServerConfigSettings />
|
||||
</TabsContent>
|
||||
<TabsContent value='admin'>
|
||||
<AdminSettings />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { AdminService } from '@/lib/api/services/admin';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
} from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { AlertCircle, Save } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export function AdminSettings() {
|
||||
const [settings, setSettings] = useState<string>('{}');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
loadSettings();
|
||||
}, []);
|
||||
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
const data = await AdminService.getSettings();
|
||||
setSettings(JSON.stringify(data, null, 2));
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to load settings';
|
||||
setError(message);
|
||||
toast.error(message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
setSaving(true);
|
||||
setError('');
|
||||
|
||||
let payload: Record<string, unknown>;
|
||||
try {
|
||||
payload = JSON.parse(settings);
|
||||
} catch (e) {
|
||||
const message = 'Invalid JSON: ' + (e instanceof Error ? e.message : String(e));
|
||||
setError(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
['upstream_api_key', 'admin_password', 'nsec'].forEach((k) => {
|
||||
if (payload && payload[k] === '[REDACTED]') {
|
||||
delete payload[k];
|
||||
}
|
||||
});
|
||||
|
||||
const updatedData = await AdminService.updateSettings(payload);
|
||||
setSettings(JSON.stringify(updatedData, null, 2));
|
||||
toast.success('Settings saved successfully');
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to save settings';
|
||||
setError(message);
|
||||
toast.error(message);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='mb-6'>
|
||||
<h2 className='text-xl font-semibold tracking-tight'>
|
||||
Admin Settings
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Server Configuration (JSON)</CardTitle>
|
||||
<CardDescription>
|
||||
Edit server settings in JSON format. Values shown as "[REDACTED]" will remain unchanged if left as-is.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-4'>
|
||||
{loading ? (
|
||||
<div className='flex items-center justify-center py-8 text-muted-foreground'>
|
||||
Loading settings...
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{error && (
|
||||
<Alert variant='destructive'>
|
||||
<AlertCircle className='h-4 w-4' />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
<textarea
|
||||
value={settings}
|
||||
onChange={(e) => setSettings(e.target.value)}
|
||||
className='w-full min-h-[400px] font-mono text-sm bg-muted p-4 rounded-md border focus:outline-none focus:ring-2 focus:ring-ring'
|
||||
placeholder='{}'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter className='flex justify-between'>
|
||||
<Button
|
||||
variant='outline'
|
||||
onClick={loadSettings}
|
||||
disabled={loading || saving}
|
||||
>
|
||||
Reload
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={loading || saving}
|
||||
>
|
||||
<Save className='mr-2 h-4 w-4' />
|
||||
{saving ? 'Saving...' : 'Save Settings'}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+101
-32
@@ -303,6 +303,20 @@ export class AdminService {
|
||||
};
|
||||
}
|
||||
|
||||
static async getModel(
|
||||
modelId: string,
|
||||
providerId: number | null = null
|
||||
): Promise<AdminModel> {
|
||||
const model = await apiClient.post<AdminModel>('/admin/api/models/get', {
|
||||
model_id: modelId,
|
||||
provider_id: providerId,
|
||||
});
|
||||
return {
|
||||
...model,
|
||||
pricing: this.convertPricingToPerMillionTokens(model.pricing),
|
||||
};
|
||||
}
|
||||
|
||||
static async updateProviderModel(
|
||||
providerId: number,
|
||||
modelId: string,
|
||||
@@ -437,9 +451,9 @@ export class AdminService {
|
||||
static async createModel(
|
||||
data: Record<string, unknown>
|
||||
): Promise<AdminModelAsModel> {
|
||||
if (!data.provider_id) {
|
||||
throw new Error('provider_id is required to create a model');
|
||||
}
|
||||
const providerId = data.provider_id
|
||||
? parseInt(data.provider_id as string)
|
||||
: null;
|
||||
|
||||
const pricing = {
|
||||
prompt: (data.input_cost as number) / 1000000,
|
||||
@@ -450,8 +464,11 @@ export class AdminService {
|
||||
internal_reasoning: 0,
|
||||
};
|
||||
|
||||
const adminModel: AdminModel = {
|
||||
id: (data.id as string) || (data.full_name as string),
|
||||
const modelId = (data.id as string) || (data.full_name as string);
|
||||
|
||||
const payload = {
|
||||
model_id: modelId,
|
||||
provider_id: providerId,
|
||||
name: (data.name as string) || (data.full_name as string),
|
||||
description: (data.description as string) || '',
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
@@ -463,28 +480,35 @@ export class AdminService {
|
||||
tokenizer: '',
|
||||
instruct_type: null,
|
||||
},
|
||||
pricing,
|
||||
pricing: this.convertPricingToPerToken(pricing),
|
||||
per_request_limits: null,
|
||||
top_provider: null,
|
||||
upstream_provider_id: parseInt(data.provider_id as string),
|
||||
enabled: data.isEnabled !== false,
|
||||
};
|
||||
|
||||
const providerId = parseInt(data.provider_id as string);
|
||||
const created = await this.createProviderModel(providerId, adminModel);
|
||||
return this.transformAdminModelToModel(created, data.provider as string);
|
||||
const created = await apiClient.post<AdminModel>(
|
||||
'/admin/api/models/create',
|
||||
payload
|
||||
);
|
||||
|
||||
return this.transformAdminModelToModel(
|
||||
{
|
||||
...created,
|
||||
pricing: this.convertPricingToPerMillionTokens(created.pricing),
|
||||
},
|
||||
data.provider as string
|
||||
);
|
||||
}
|
||||
|
||||
static async updateModel(
|
||||
modelId: string,
|
||||
data: Record<string, unknown>
|
||||
): Promise<AdminModelAsModel> {
|
||||
if (!data.provider_id) {
|
||||
throw new Error('provider_id is required to update a model');
|
||||
}
|
||||
const providerId = data.provider_id
|
||||
? parseInt(data.provider_id as string)
|
||||
: null;
|
||||
|
||||
const providerId = parseInt(data.provider_id as string);
|
||||
const existingModel = await this.getProviderModel(providerId, modelId);
|
||||
const existingModel = await this.getModel(modelId, providerId);
|
||||
|
||||
const pricing = {
|
||||
prompt: (data.input_cost as number) / 1000000,
|
||||
@@ -495,9 +519,13 @@ export class AdminService {
|
||||
internal_reasoning: 0,
|
||||
};
|
||||
|
||||
const payload: AdminModel = {
|
||||
const payload: AdminModel & {
|
||||
model_id: string;
|
||||
provider_id: number | null;
|
||||
} = {
|
||||
...existingModel,
|
||||
id: modelId,
|
||||
model_id: modelId,
|
||||
provider_id: providerId,
|
||||
pricing,
|
||||
};
|
||||
|
||||
@@ -508,22 +536,32 @@ export class AdminService {
|
||||
if (data.isEnabled !== undefined)
|
||||
payload.enabled = data.isEnabled as boolean;
|
||||
|
||||
const updated = await this.updateProviderModel(
|
||||
providerId,
|
||||
modelId,
|
||||
payload
|
||||
const updated = await apiClient.post<AdminModel>(
|
||||
'/admin/api/models/update',
|
||||
{
|
||||
...payload,
|
||||
pricing: this.convertPricingToPerToken(payload.pricing),
|
||||
}
|
||||
);
|
||||
|
||||
return this.transformAdminModelToModel(
|
||||
{
|
||||
...updated,
|
||||
pricing: this.convertPricingToPerMillionTokens(updated.pricing),
|
||||
},
|
||||
data.provider as string
|
||||
);
|
||||
return this.transformAdminModelToModel(updated, data.provider as string);
|
||||
}
|
||||
|
||||
static async deleteModel(
|
||||
modelId: string,
|
||||
providerId?: string
|
||||
): Promise<{ message: string }> {
|
||||
if (!providerId) {
|
||||
throw new Error('provider_id is required to delete a model');
|
||||
}
|
||||
await this.deleteProviderModel(parseInt(providerId), modelId);
|
||||
const providerIdNum = providerId ? parseInt(providerId) : null;
|
||||
await apiClient.post('/admin/api/models/delete', {
|
||||
model_id: modelId,
|
||||
provider_id: providerIdNum,
|
||||
});
|
||||
return { message: 'Model deleted successfully' };
|
||||
}
|
||||
|
||||
@@ -531,15 +569,17 @@ export class AdminService {
|
||||
modelId: string,
|
||||
providerId?: string
|
||||
): Promise<{ message: string }> {
|
||||
if (!providerId) {
|
||||
throw new Error('provider_id is required to soft delete a model');
|
||||
}
|
||||
const providerIdNum = parseInt(providerId);
|
||||
const model = await this.getProviderModel(providerIdNum, modelId);
|
||||
await this.updateProviderModel(providerIdNum, modelId, {
|
||||
const providerIdNum = providerId ? parseInt(providerId) : null;
|
||||
const model = await this.getModel(modelId, providerIdNum);
|
||||
|
||||
await apiClient.post('/admin/api/models/update', {
|
||||
model_id: modelId,
|
||||
provider_id: providerIdNum,
|
||||
...model,
|
||||
enabled: false,
|
||||
pricing: this.convertPricingToPerToken(model.pricing),
|
||||
});
|
||||
|
||||
return { message: 'Model soft deleted successfully' };
|
||||
}
|
||||
|
||||
@@ -693,4 +733,33 @@ export class AdminService {
|
||||
pricing: this.convertPricingToPerMillionTokens(m.pricing),
|
||||
}));
|
||||
}
|
||||
|
||||
static async getSettings(): Promise<Record<string, unknown>> {
|
||||
return await apiClient.get<Record<string, unknown>>('/admin/api/settings');
|
||||
}
|
||||
|
||||
static async updateSettings(
|
||||
settings: Record<string, unknown>
|
||||
): Promise<Record<string, unknown>> {
|
||||
return await apiClient.patch<Record<string, unknown>>(
|
||||
'/admin/api/settings',
|
||||
settings
|
||||
);
|
||||
}
|
||||
|
||||
static async login(password: string): Promise<{
|
||||
ok: boolean;
|
||||
token: string;
|
||||
expires_in: number;
|
||||
}> {
|
||||
return await apiClient.post<{
|
||||
ok: boolean;
|
||||
token: string;
|
||||
expires_in: number;
|
||||
}>('/admin/api/login', { password });
|
||||
}
|
||||
|
||||
static async logout(): Promise<{ ok: boolean }> {
|
||||
return await apiClient.post<{ ok: boolean }>('/admin/api/logout', {});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user