fix: resolve TS errors in ostp-control for CI build

This commit is contained in:
ospab 2026-05-26 21:45:02 +03:00
parent 8eef85ceca
commit fe6fdd20bd
4 changed files with 6 additions and 298 deletions

View File

@ -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';

View File

@ -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() {

View File

@ -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>
);
}

View File

@ -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 && (