mirror of https://github.com/ospab/ostp.git
fix: resolve TS errors in ostp-control for CI build
This commit is contained in:
parent
8eef85ceca
commit
fe6fdd20bd
|
|
@ -1,6 +1,7 @@
|
||||||
import { HashRouter as Router, Routes, Route, Link, Navigate, useLocation } from 'react-router-dom';
|
import { HashRouter as Router, Routes, Route, Link, Navigate, useLocation } from 'react-router-dom';
|
||||||
import { Activity, Users, Settings, Shield, MoreVertical, RefreshCw, BookOpen, Wrench, History, Globe, LogOut } from 'lucide-react';
|
import { Activity, Users, Settings, Shield, MoreVertical, RefreshCw, BookOpen, Wrench, History, Globe, LogOut } from 'lucide-react';
|
||||||
import { useState, useEffect, ReactNode } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Dashboard from './pages/Dashboard';
|
import Dashboard from './pages/Dashboard';
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@ export interface ApiResponse<T> {
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers for localStorage state
|
|
||||||
const API_URL_KEY = 'ostp_api_url';
|
|
||||||
const API_TOKEN_KEY = 'ostp_api_token';
|
const API_TOKEN_KEY = 'ostp_api_token';
|
||||||
|
|
||||||
export function getApiSettings() {
|
export function getApiSettings() {
|
||||||
|
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { Shield, Globe, Key, CheckCircle, XCircle, RefreshCw, ChevronRight } from 'lucide-react';
|
|
||||||
import { saveApiSettings, api } from '../lib/api';
|
|
||||||
import { useLanguage } from '../lib/LanguageContext';
|
|
||||||
import { addAuditLog } from '../lib/audit';
|
|
||||||
|
|
||||||
interface ConnectionSetupProps {
|
|
||||||
onSetupComplete: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ConnectionSetup({ onSetupComplete }: ConnectionSetupProps) {
|
|
||||||
const { t, language } = useLanguage();
|
|
||||||
|
|
||||||
const [apiUrl, setApiUrl] = useState('http://localhost:9090');
|
|
||||||
const [apiToken, setApiToken] = useState('');
|
|
||||||
const [isTesting, setIsTesting] = useState(false);
|
|
||||||
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
|
||||||
|
|
||||||
const handleTestAndSave = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!apiUrl) return;
|
|
||||||
|
|
||||||
setIsTesting(true);
|
|
||||||
setTestResult(null);
|
|
||||||
|
|
||||||
const formattedUrl = apiUrl.trim().replace(/\/$/, '');
|
|
||||||
const formattedToken = apiToken.trim();
|
|
||||||
|
|
||||||
const oldUrl = localStorage.getItem('ostp_api_url');
|
|
||||||
const oldToken = localStorage.getItem('ostp_api_token');
|
|
||||||
|
|
||||||
try {
|
|
||||||
saveApiSettings(formattedUrl, formattedToken);
|
|
||||||
const status = await api.getServerStatus();
|
|
||||||
|
|
||||||
setTestResult({
|
|
||||||
success: true,
|
|
||||||
message: language === 'ru'
|
|
||||||
? `Успешно подключено! Версия сервера: v${status.version}, активных сессий: ${status.active_users}`
|
|
||||||
: `Successfully connected! Server version: v${status.version}, active sessions: ${status.active_users}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
addAuditLog(
|
|
||||||
`Initial setup connected to API at ${formattedUrl} (Version: v${status.version})`,
|
|
||||||
`Первоначальная настройка успешно подключена к API по адресу ${formattedUrl} (Версия: v${status.version})`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
onSetupComplete();
|
|
||||||
}, 1000);
|
|
||||||
} catch (err: any) {
|
|
||||||
if (oldUrl) localStorage.setItem('ostp_api_url', oldUrl);
|
|
||||||
else localStorage.removeItem('ostp_api_url');
|
|
||||||
if (oldToken !== null) localStorage.setItem('ostp_api_token', oldToken);
|
|
||||||
else localStorage.removeItem('ostp_api_token');
|
|
||||||
|
|
||||||
const errorMsgStr = err.message || err;
|
|
||||||
setTestResult({
|
|
||||||
success: false,
|
|
||||||
message: `${t('conn_setup_error')}${errorMsgStr}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
addAuditLog(
|
|
||||||
`Initial setup failed to connect to ${formattedUrl}: ${errorMsgStr}`,
|
|
||||||
`Первоначальная настройка не смогла подключиться к ${formattedUrl}: ${errorMsgStr}`,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setIsTesting(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background p-4 overflow-y-auto">
|
|
||||||
{/* Background blobs */}
|
|
||||||
<div className="absolute top-[10%] left-[10%] w-[50%] h-[50%] rounded-full bg-primary/10 blur-[150px] pointer-events-none"></div>
|
|
||||||
<div className="absolute bottom-[10%] right-[10%] w-[50%] h-[50%] rounded-full bg-secondary/5 blur-[150px] pointer-events-none"></div>
|
|
||||||
|
|
||||||
<div className="relative w-full max-w-lg glass-panel rounded-3xl p-8 space-y-6 shadow-2xl border border-white/10 my-8">
|
|
||||||
<div className="text-center space-y-2">
|
|
||||||
<div className="inline-flex p-4 bg-primary/10 rounded-2xl mb-2 text-primary border border-primary/20">
|
|
||||||
<Shield className="w-12 h-12" />
|
|
||||||
</div>
|
|
||||||
<h1 className="text-3xl font-extrabold tracking-tight text-white">OSTP<span className="text-primary">CORE</span></h1>
|
|
||||||
<p className="text-text-muted text-sm">{t('conn_setup_sub')}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-white/5 border border-white/5 p-4 rounded-2xl space-y-2 text-white">
|
|
||||||
<h2 className="text-sm font-semibold">{t('conn_setup_header')}</h2>
|
|
||||||
<p className="text-xs text-text-muted leading-relaxed">
|
|
||||||
{t('conn_setup_desc')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleTestAndSave} className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-xs font-semibold text-text-muted uppercase tracking-wider flex items-center gap-1.5">
|
|
||||||
<Globe className="w-3.5 h-3.5 text-primary" /> {t('conn_setup_url_label')}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-text-muted focus:outline-none focus:border-primary transition-colors font-mono text-sm"
|
|
||||||
placeholder="e.g. http://localhost:9090"
|
|
||||||
value={apiUrl}
|
|
||||||
onChange={(e) => setApiUrl(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-xs font-semibold text-text-muted uppercase tracking-wider flex items-center gap-1.5">
|
|
||||||
<Key className="w-3.5 h-3.5 text-primary" /> {t('conn_setup_token_label')}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
required
|
|
||||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-text-muted focus:outline-none focus:border-primary transition-colors font-mono text-sm"
|
|
||||||
placeholder={t('conn_setup_token_placeholder')}
|
|
||||||
value={apiToken}
|
|
||||||
onChange={(e) => setApiToken(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={isTesting || !apiUrl || !apiToken}
|
|
||||||
className="w-full flex items-center justify-center gap-2 bg-primary hover:bg-primary/90 disabled:opacity-50 disabled:hover:bg-primary text-white py-3.5 rounded-xl font-semibold transition-colors mt-2 shadow-[0_0_20px_rgba(108,114,255,0.4)]"
|
|
||||||
>
|
|
||||||
{isTesting ? (
|
|
||||||
<RefreshCw className="w-5 h-5 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{t('conn_setup_btn')} <ChevronRight className="w-5 h-5" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{testResult && (
|
|
||||||
<div className={`p-4 rounded-xl flex items-start gap-3 border animate-in fade-in slide-in-from-bottom-2 duration-200 ${
|
|
||||||
testResult.success
|
|
||||||
? 'bg-secondary/10 border-secondary/20 text-secondary'
|
|
||||||
: 'bg-red-500/10 border-red-500/20 text-red-400'
|
|
||||||
}`}>
|
|
||||||
{testResult.success ? (
|
|
||||||
<CheckCircle className="w-5 h-5 shrink-0 mt-0.5" />
|
|
||||||
) : (
|
|
||||||
<XCircle className="w-5 h-5 shrink-0 mt-0.5" />
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<p className="font-semibold text-sm">{testResult.success ? t('conn_setup_success') : 'Connection Error'}</p>
|
|
||||||
<p className="text-xs mt-1 opacity-90 font-mono break-all">{testResult.message}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +1,17 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Settings as SettingsIcon, Globe, Key, CheckCircle, XCircle,
|
Settings as SettingsIcon, Globe, CheckCircle, XCircle,
|
||||||
RefreshCw, Save, Sliders, Code2, AlertTriangle
|
RefreshCw, Save, Sliders, Code2, AlertTriangle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { getApiSettings, saveApiSettings, api } from '../lib/api';
|
import { api } from '../lib/api';
|
||||||
import { useLanguage } from '../lib/LanguageContext';
|
import { useLanguage } from '../lib/LanguageContext';
|
||||||
import { addAuditLog } from '../lib/audit';
|
import { addAuditLog } from '../lib/audit';
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const { t, language } = useLanguage();
|
const { t, language } = useLanguage();
|
||||||
|
|
||||||
// Tabs: 'connection' | 'interactive' | 'raw'
|
// Tabs: 'interactive' | 'raw'
|
||||||
const [activeTab, setActiveTab] = useState<'connection' | 'interactive' | 'raw'>('interactive');
|
const [activeTab, setActiveTab] = useState<'interactive' | 'raw'>('interactive');
|
||||||
|
|
||||||
// Connection settings state
|
|
||||||
const [panelApiUrl, setPanelApiUrl] = useState('');
|
|
||||||
const [panelApiToken, setPanelApiToken] = useState('');
|
|
||||||
const [isTestingConnection, setIsTestingConnection] = useState(false);
|
|
||||||
const [connectionTestResult, setConnectionTestResult] = useState<{ success: boolean; message: string } | null>(null);
|
|
||||||
|
|
||||||
// Full Server Config JSON state
|
// Full Server Config JSON state
|
||||||
const [config, setConfig] = useState<any>(null);
|
const [config, setConfig] = useState<any>(null);
|
||||||
|
|
@ -28,9 +22,6 @@ export default function Settings() {
|
||||||
|
|
||||||
// Initial load
|
// Initial load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { url, token } = getApiSettings();
|
|
||||||
setPanelApiUrl(url);
|
|
||||||
setPanelApiToken(token);
|
|
||||||
fetchServerConfig();
|
fetchServerConfig();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -50,53 +41,7 @@ export default function Settings() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTestConnection = async () => {
|
|
||||||
setIsTestingConnection(true);
|
|
||||||
setConnectionTestResult(null);
|
|
||||||
const oldUrl = localStorage.getItem('ostp_api_url');
|
|
||||||
const oldToken = localStorage.getItem('ostp_api_token');
|
|
||||||
|
|
||||||
try {
|
|
||||||
saveApiSettings(panelApiUrl, panelApiToken);
|
|
||||||
const status = await api.getServerStatus();
|
|
||||||
setConnectionTestResult({
|
|
||||||
success: true,
|
|
||||||
message: t('st_conn_success', { version: status.version, users: status.active_users }),
|
|
||||||
});
|
|
||||||
addAuditLog(
|
|
||||||
`Tested connection to Management API at ${panelApiUrl} (Success)`,
|
|
||||||
`Успешно протестировано подключение к API по адресу ${panelApiUrl}`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
} catch (err: any) {
|
|
||||||
if (oldUrl) localStorage.setItem('ostp_api_url', oldUrl);
|
|
||||||
if (oldToken !== null) localStorage.setItem('ostp_api_token', oldToken);
|
|
||||||
|
|
||||||
const errorMsgStr = err.message || err;
|
|
||||||
setConnectionTestResult({
|
|
||||||
success: false,
|
|
||||||
message: t('st_conn_error', { error: errorMsgStr }),
|
|
||||||
});
|
|
||||||
addAuditLog(
|
|
||||||
`Tested connection to Management API at ${panelApiUrl} (Failed: ${errorMsgStr})`,
|
|
||||||
`Ошибка при тесте подключения к API по адресу ${panelApiUrl} (${errorMsgStr})`,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setIsTestingConnection(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveConnection = () => {
|
|
||||||
saveApiSettings(panelApiUrl, panelApiToken);
|
|
||||||
alert(language === 'ru' ? 'Настройки подключения сохранены!' : 'Connection settings saved!');
|
|
||||||
addAuditLog(
|
|
||||||
`Saved API connection settings: ${panelApiUrl}`,
|
|
||||||
`Сохранены настройки подключения к API: ${panelApiUrl}`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
window.location.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Save Config to Server
|
// Save Config to Server
|
||||||
const handleSaveConfig = async (configToSave: any) => {
|
const handleSaveConfig = async (configToSave: any) => {
|
||||||
|
|
@ -205,14 +150,6 @@ export default function Settings() {
|
||||||
>
|
>
|
||||||
<Code2 className="w-4 h-4" /> {t('st_tab_json')}
|
<Code2 className="w-4 h-4" /> {t('st_tab_json')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
onClick={() => setActiveTab('connection')}
|
|
||||||
className={`flex items-center gap-2 px-5 py-3 font-medium transition-colors border-b-2 text-sm ${
|
|
||||||
activeTab === 'connection' ? 'border-primary text-white' : 'border-transparent text-text-muted hover:text-white'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Globe className="w-4 h-4" /> {t('st_tab_conn')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Global save notifications */}
|
{/* Global save notifications */}
|
||||||
|
|
@ -235,75 +172,7 @@ export default function Settings() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ── TAB: CONNECTION SETTINGS ── */}
|
|
||||||
{activeTab === 'connection' && (
|
|
||||||
<div className="glass-panel rounded-2xl p-6 space-y-6">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-xl font-semibold mb-2">{t('st_conn_title')}</h2>
|
|
||||||
<p className="text-sm text-text-muted">
|
|
||||||
{t('st_conn_desc')}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium flex items-center gap-2">
|
|
||||||
<Globe className="w-4 h-4 text-primary" /> {t('st_conn_url')}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-text-muted focus:outline-none focus:border-primary transition-colors font-mono"
|
|
||||||
placeholder="e.g. http://localhost:9090"
|
|
||||||
value={panelApiUrl}
|
|
||||||
onChange={(e) => setPanelApiUrl(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label className="text-sm font-medium flex items-center gap-2">
|
|
||||||
<Key className="w-4 h-4 text-primary" /> {t('st_conn_token')}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-text-muted focus:outline-none focus:border-primary transition-colors font-mono"
|
|
||||||
placeholder={t('st_conn_token_sub')}
|
|
||||||
value={panelApiToken}
|
|
||||||
onChange={(e) => setPanelApiToken(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-4 pt-4 border-t border-white/5">
|
|
||||||
<button
|
|
||||||
onClick={handleTestConnection}
|
|
||||||
disabled={isTestingConnection || !panelApiUrl}
|
|
||||||
className="flex items-center gap-2 bg-white/5 hover:bg-white/10 text-white px-5 py-2.5 rounded-xl font-medium transition-colors border border-white/10"
|
|
||||||
>
|
|
||||||
<RefreshCw className={`w-5 h-5 ${isTestingConnection ? 'animate-spin text-primary' : ''}`} /> {t('st_conn_test')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={handleSaveConnection}
|
|
||||||
disabled={!panelApiUrl}
|
|
||||||
className="flex items-center gap-2 bg-primary hover:bg-primary/90 text-white px-6 py-2.5 rounded-xl font-medium transition-colors shadow-[0_0_15px_rgba(108,114,255,0.3)]"
|
|
||||||
>
|
|
||||||
<Save className="w-5 h-5" /> {t('st_conn_save')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{connectionTestResult && (
|
|
||||||
<div className={`mt-4 p-4 rounded-xl flex items-start gap-3 border ${
|
|
||||||
connectionTestResult.success ? 'bg-secondary/10 border-secondary/20 text-secondary' : 'bg-red-500/10 border-red-500/20 text-red-400'
|
|
||||||
}`}>
|
|
||||||
{connectionTestResult.success ? <CheckCircle className="w-5 h-5 shrink-0 mt-0.5" /> : <XCircle className="w-5 h-5 shrink-0 mt-0.5" />}
|
|
||||||
<div>
|
|
||||||
<p className="font-semibold text-sm">{connectionTestResult.success ? 'Success' : 'Failed'}</p>
|
|
||||||
<p className="text-xs mt-1 opacity-90 font-mono break-all">{connectionTestResult.message}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ── TAB: RAW JSON EDITOR ── */}
|
{/* ── TAB: RAW JSON EDITOR ── */}
|
||||||
{activeTab === 'raw' && config && (
|
{activeTab === 'raw' && config && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue