feat: implement MariaDB management, PHP extension control, and project details UI components
This commit is contained in:
+90
-85
@@ -472,27 +472,27 @@ function App(): JSX.Element {
|
||||
const html = `
|
||||
<div class="swal-service-list">
|
||||
${services.map(srv => {
|
||||
const label = getDisplayLabel(srv);
|
||||
const res = results[srv];
|
||||
let iconClass = 'swal-status-icon';
|
||||
let noteClass = 'swal-status-note';
|
||||
let note = operation === 'start' ? t('common.ready') : t('common.running');
|
||||
const label = getDisplayLabel(srv);
|
||||
const res = results[srv];
|
||||
let iconClass = 'swal-status-icon';
|
||||
let noteClass = 'swal-status-note';
|
||||
let note = operation === 'start' ? t('common.ready') : t('common.running');
|
||||
|
||||
if (res?.success) {
|
||||
iconClass += ' success';
|
||||
noteClass += ' success';
|
||||
note = operation === 'start' ? t('common.started') : t('common.stopped');
|
||||
} else if (res?.error) {
|
||||
iconClass += ' error';
|
||||
noteClass += ' error';
|
||||
note = t('common.error');
|
||||
} else if (srv === currentSrv) {
|
||||
iconClass += ' loading swal2-loading-dots';
|
||||
noteClass += ' loading';
|
||||
note = operation === 'start' ? t('common.starting') : t('common.stopping');
|
||||
}
|
||||
if (res?.success) {
|
||||
iconClass += ' success';
|
||||
noteClass += ' success';
|
||||
note = operation === 'start' ? t('common.started') : t('common.stopped');
|
||||
} else if (res?.error) {
|
||||
iconClass += ' error';
|
||||
noteClass += ' error';
|
||||
note = t('common.error');
|
||||
} else if (srv === currentSrv) {
|
||||
iconClass += ' loading swal2-loading-dots';
|
||||
noteClass += ' loading';
|
||||
note = operation === 'start' ? t('common.starting') : t('common.stopping');
|
||||
}
|
||||
|
||||
return `
|
||||
return `
|
||||
<div class="swal-service-item">
|
||||
<span class="swal-service-label">
|
||||
<span class="${iconClass}">●</span> ${label}
|
||||
@@ -500,7 +500,7 @@ function App(): JSX.Element {
|
||||
<span class="${noteClass}">${note.toUpperCase()}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('')}
|
||||
}).join('')}
|
||||
</div>
|
||||
`;
|
||||
Swal.update({ html });
|
||||
@@ -523,17 +523,17 @@ function App(): JSX.Element {
|
||||
updateSwal(srv);
|
||||
try {
|
||||
const res = operation === 'start' ? await window.api.startService(srv) : await window.api.stopService(srv);
|
||||
|
||||
|
||||
if (res.success) {
|
||||
// Poll for status confirmation
|
||||
let attempts = 0;
|
||||
const maxAttempts = 15; // ~7.5 seconds
|
||||
let confirmed = false;
|
||||
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
const statusRes = await window.api.invoke('service:status', srv);
|
||||
|
||||
|
||||
if (operation === 'start' && statusRes === 'running') {
|
||||
confirmed = true;
|
||||
break;
|
||||
@@ -545,7 +545,7 @@ function App(): JSX.Element {
|
||||
attempts++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
results[srv] = { success: confirmed, error: !confirmed ? t('common.timeout') : null };
|
||||
} else {
|
||||
results[srv] = { success: false, error: res.message };
|
||||
@@ -558,7 +558,7 @@ function App(): JSX.Element {
|
||||
|
||||
|
||||
const anyError = Object.values(results).some(r => r.error);
|
||||
|
||||
|
||||
Swal.update({
|
||||
icon: anyError ? 'warning' : 'success',
|
||||
showConfirmButton: true,
|
||||
@@ -571,7 +571,7 @@ function App(): JSX.Element {
|
||||
if (Swal.isVisible()) Swal.close();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
|
||||
fetchStatus();
|
||||
};
|
||||
|
||||
@@ -784,10 +784,10 @@ function App(): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
open={!!selectedQuery}
|
||||
onClose={() => setSelectedQuery(null)}
|
||||
maxWidth="lg"
|
||||
<Dialog
|
||||
open={!!selectedQuery}
|
||||
onClose={() => setSelectedQuery(null)}
|
||||
maxWidth="lg"
|
||||
fullWidth
|
||||
PaperProps={{
|
||||
className: 'mui-custom-dialog-paper'
|
||||
@@ -802,24 +802,24 @@ function App(): JSX.Element {
|
||||
<DialogContent sx={{ p: 0 }}>
|
||||
<Box sx={{ p: 3, maxHeight: '70vh', overflow: 'auto' }}>
|
||||
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
|
||||
<code
|
||||
<code
|
||||
className="language-sql"
|
||||
dangerouslySetInnerHTML={{
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: Prism.highlight(
|
||||
(selectedQuery || '')
|
||||
.replace(/\\r\\n/g, '\n')
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\\t/g, ' '),
|
||||
Prism.languages.sql,
|
||||
Prism.languages.sql,
|
||||
'sql'
|
||||
)
|
||||
}}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</pre>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ borderTop: '1px solid rgba(255,255,255,0.1)', p: 2 }}>
|
||||
<Button
|
||||
<Button
|
||||
startIcon={<ContentCopyIcon />}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(selectedQuery || '')
|
||||
@@ -854,17 +854,17 @@ function App(): JSX.Element {
|
||||
>
|
||||
<Toolbar sx={{ gap: 1.5, minHeight: '56px !important' }}>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
color="success"
|
||||
onClick={handleStartAll}
|
||||
startIcon={<StartIcon sx={{ fontSize: '1rem !important' }} />}
|
||||
sx={{
|
||||
borderRadius: 1.5,
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 800,
|
||||
startIcon={<StartIcon sx={{ fontSize: 'var(--font-size-base) !important' }} />}
|
||||
sx={{
|
||||
borderRadius: 'var(--radius-md)',
|
||||
fontSize: 'var(--font-size-xs)',
|
||||
fontWeight: 'var(--font-weight-black)',
|
||||
textTransform: 'none',
|
||||
borderColor: 'rgba(76, 175, 80, 0.3)',
|
||||
px: 1.5,
|
||||
@@ -874,16 +874,16 @@ function App(): JSX.Element {
|
||||
{t('dashboard.start_all')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
color="error"
|
||||
onClick={handleStopAll}
|
||||
startIcon={<StopIcon sx={{ fontSize: '1rem !important' }} />}
|
||||
sx={{
|
||||
borderRadius: 1.5,
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 800,
|
||||
startIcon={<StopIcon sx={{ fontSize: 'var(--font-size-base) !important' }} />}
|
||||
sx={{
|
||||
borderRadius: 'var(--radius-md)',
|
||||
fontSize: 'var(--font-size-xs)',
|
||||
fontWeight: 'var(--font-weight-black)',
|
||||
textTransform: 'none',
|
||||
borderColor: 'rgba(244, 67, 54, 0.3)',
|
||||
px: 1.5,
|
||||
@@ -894,7 +894,7 @@ function App(): JSX.Element {
|
||||
</Button>
|
||||
|
||||
<Divider orientation="vertical" flexItem sx={{ mx: 0.5, borderColor: 'rgba(255,255,255,0.1)', height: 20, mt: 1.5 }} />
|
||||
|
||||
|
||||
<FormControl size="small" variant="outlined" sx={{ minWidth: 70 }}>
|
||||
<Select
|
||||
value={i18n.language}
|
||||
@@ -912,10 +912,10 @@ function App(): JSX.Element {
|
||||
}}
|
||||
>
|
||||
{supportedLngs.map(lang => (
|
||||
<MenuItem key={lang} value={lang} sx={{ fontSize: '0.75rem' }}>
|
||||
<MenuItem key={lang} value={lang} className="text-sm">
|
||||
{lang.toUpperCase()}
|
||||
</MenuItem>
|
||||
))}
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
@@ -924,7 +924,7 @@ function App(): JSX.Element {
|
||||
<TerminalIcon sx={{ fontSize: '1.2rem' }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
|
||||
<IconButton size="small" color="inherit" onClick={() => setActiveTab('settings')} sx={{ p: 0.5, opacity: 0.7, '&:hover': { opacity: 1 } }}>
|
||||
<SettingsIcon sx={{ fontSize: '1.2rem' }} />
|
||||
</IconButton>
|
||||
@@ -937,7 +937,12 @@ function App(): JSX.Element {
|
||||
sx={{
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box', bgcolor: 'rgba(18, 18, 18, 0.95)', borderRight: '1px solid rgba(255, 255, 255, 0.05)' },
|
||||
[`& .MuiDrawer-paper`]: {
|
||||
width: drawerWidth,
|
||||
boxSizing: 'border-box',
|
||||
bgcolor: 'rgba(18, 18, 18, 0.95)',
|
||||
borderRight: '1px solid var(--color-border-soft)'
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '120px' }}>
|
||||
@@ -949,37 +954,37 @@ function App(): JSX.Element {
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'dashboard'} onClick={() => setActiveTab('dashboard')} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon sx={{ minWidth: 40 }}><DashboardIcon fontSize="small" color={activeTab === 'dashboard' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary={t('dashboard.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||
<ListItemText primary={t('dashboard.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'projects'} onClick={() => setActiveTab('projects')} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon sx={{ minWidth: 40 }}><ProjectIcon fontSize="small" color={activeTab === 'projects' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary={t('projects.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||
<ListItemText primary={t('projects.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'tuning'} onClick={() => setActiveTab('tuning')} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon sx={{ minWidth: 40 }}><TuningIcon fontSize="small" color={activeTab === 'tuning' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary={t('tuning.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||
<ListItemText primary={t('tuning.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'tools'} onClick={() => setActiveTab('tools')} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon sx={{ minWidth: 40 }}><ToolsIcon fontSize="small" color={activeTab === 'tools' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary={t('common.tools')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||
<ListItemText primary={t('common.tools')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'db_manager'} onClick={() => setActiveTab('db_manager')} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon sx={{ minWidth: 40 }}><DbIcon fontSize="small" color={activeTab === 'db_manager' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary={t('mariadb_manager.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||
<ListItemText primary={t('mariadb_manager.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'db_monitor'} onClick={() => setActiveTab('db_monitor')} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon sx={{ minWidth: 40 }}><TerminalIcon fontSize="small" color={activeTab === 'db_monitor' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary={t('mariadb_process_monitor.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||
<ListItemText primary={t('mariadb_process_monitor.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
@@ -988,19 +993,19 @@ function App(): JSX.Element {
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'user_guide'} onClick={() => setActiveTab('user_guide')} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon sx={{ minWidth: 40 }}><GuideIcon fontSize="small" color={activeTab === 'user_guide' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary={t('common.user_guide')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||
<ListItemText primary={t('common.user_guide')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'settings'} onClick={() => setActiveTab('settings')} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon sx={{ minWidth: 40 }}><SettingsIcon fontSize="small" color={activeTab === 'settings' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary={t('settings.general')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||
<ListItemText primary={t('settings.general')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton selected={activeTab === 'event_monitor'} onClick={() => setActiveTab('event_monitor')} sx={{ py: 0.5 }}>
|
||||
<ListItemIcon sx={{ minWidth: 40 }}><HistoryIcon fontSize="small" color={activeTab === 'event_monitor' ? 'primary' : 'inherit'} /></ListItemIcon>
|
||||
<ListItemText primary={t('service_monitor.title')} primaryTypographyProps={{ fontSize: '0.75rem', fontWeight: 500 }} />
|
||||
<ListItemText primary={t('service_monitor.title')} primaryTypographyProps={{ className: 'text-sm font-medium' }} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</List>
|
||||
@@ -1284,13 +1289,13 @@ function App(): JSX.Element {
|
||||
<Box>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={cliConfig.enabled}
|
||||
<Switch
|
||||
checked={cliConfig.enabled}
|
||||
onChange={async (e) => {
|
||||
const newConf = { ...cliConfig, enabled: e.target.checked }
|
||||
setCliConfig(newConf)
|
||||
await window.api.invoke('cli:set-config', newConf)
|
||||
}}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
@@ -1353,9 +1358,9 @@ function App(): JSX.Element {
|
||||
{cliConfig.binDir || '...'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={async () => {
|
||||
await window.api.invoke('cli:sync')
|
||||
@@ -1420,7 +1425,7 @@ function App(): JSX.Element {
|
||||
)}
|
||||
|
||||
{activeTab === 'dashboard' && (
|
||||
<DashboardManager
|
||||
<DashboardManager
|
||||
status={status}
|
||||
settings={settings}
|
||||
projects={projects}
|
||||
@@ -1453,7 +1458,7 @@ function App(): JSX.Element {
|
||||
value={selectedMariaDbId}
|
||||
onChange={(e) => setSelectedMariaDbId(e.target.value)}
|
||||
label={t('settings.mariadb_versions')}
|
||||
sx={{
|
||||
sx={{
|
||||
color: '#fff',
|
||||
'.MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.1)' }
|
||||
}}
|
||||
@@ -1502,9 +1507,9 @@ function App(): JSX.Element {
|
||||
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: proc.State === 'Query' ? '#39A7FF' : 'rgba(255,255,255,0.5)' }}>{proc.State || '-'}</TableCell>
|
||||
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.7)', maxWidth: 300, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
<Tooltip title={t('mariadb_process_monitor.click_to_view_sql')}>
|
||||
<Box
|
||||
<Box
|
||||
onClick={() => setSelectedQuery(proc.Info)}
|
||||
sx={{
|
||||
sx={{
|
||||
cursor: proc.Info ? 'pointer' : 'default',
|
||||
'&:hover': { color: proc.Info ? '#39A7FF' : 'inherit' },
|
||||
transition: 'color 0.2s'
|
||||
@@ -1635,7 +1640,7 @@ function App(): JSX.Element {
|
||||
)}
|
||||
|
||||
{activeTab === 'projects' && (
|
||||
<ProjectManager
|
||||
<ProjectManager
|
||||
projects={projects}
|
||||
status={status}
|
||||
settings={settings}
|
||||
@@ -1931,9 +1936,9 @@ function App(): JSX.Element {
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Button
|
||||
size="small"
|
||||
startIcon={isProcessListLoading ? <CircularProgress size={16} /> : <RefreshIcon />}
|
||||
<Button
|
||||
size="small"
|
||||
startIcon={isProcessListLoading ? <CircularProgress size={16} /> : <RefreshIcon />}
|
||||
onClick={() => selectedMariaDbId && fetchProcessList(selectedMariaDbId)}
|
||||
disabled={isProcessListLoading}
|
||||
>
|
||||
@@ -1987,10 +1992,10 @@ function App(): JSX.Element {
|
||||
{p.Progress ? `${parseFloat(p.Progress).toFixed(2)}%` : '-'}
|
||||
</TableCell>
|
||||
<TableCell sx={{ borderColor: 'rgba(255,255,255,0.05)', textAlign: 'right' }}>
|
||||
<Button
|
||||
size="small"
|
||||
color="error"
|
||||
variant="text"
|
||||
<Button
|
||||
size="small"
|
||||
color="error"
|
||||
variant="text"
|
||||
onClick={() => selectedMariaDbId && handleKillProcess(selectedMariaDbId, p.Id)}
|
||||
sx={{ fontSize: '0.65rem', p: 0, minWidth: 'auto' }}
|
||||
>
|
||||
|
||||
@@ -178,31 +178,32 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
|
||||
<Stack direction="row" spacing={2} alignItems="center" justifyContent="space-between" sx={{ mb: 3 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<DbIcon color="primary" />
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>{t('mariadb_manager.title')}</Typography>
|
||||
<Typography variant="h6" className="font-bold">{t('mariadb_manager.title')}</Typography>
|
||||
</Box>
|
||||
<Stack direction="row" spacing={1.5}>
|
||||
<FormControl size="small" sx={{ minWidth: 160 }}>
|
||||
<InputLabel id="version-select-label" sx={{ fontSize: '0.75rem' }}>{t('mariadb.version')}</InputLabel>
|
||||
<InputLabel id="version-select-label" className="text-sm">{t('mariadb.version')}</InputLabel>
|
||||
<Select
|
||||
labelId="version-select-label"
|
||||
value={selectedVersion}
|
||||
label={t('mariadb.version')}
|
||||
onChange={(e) => setSelectedVersion(e.target.value)}
|
||||
sx={{ fontSize: '0.75rem', height: 32 }}
|
||||
sx={{ height: 32 }}
|
||||
className="text-sm"
|
||||
>
|
||||
{installedVersions.map(v => (
|
||||
<MenuItem key={v.id} value={v.id} sx={{ fontSize: '0.75rem' }}>MariaDB {v.version}</MenuItem>
|
||||
<MenuItem key={v.id} value={v.id} className="text-sm">MariaDB {v.version}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button variant="outlined" size="small" startIcon={<RefreshIcon sx={{ fontSize: '1rem' }} />} onClick={fetchData} disabled={loading} sx={{ fontSize: '0.7rem', height: 32, borderRadius: 2 }}>
|
||||
<Button variant="outlined" size="small" startIcon={<RefreshIcon sx={{ fontSize: 'var(--font-size-base)' }} />} onClick={fetchData} disabled={loading} sx={{ height: 32 }} className="text-xs rounded-md">
|
||||
{t('common.refresh')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{!selectedVersion && (
|
||||
<Alert severity="info" sx={{ mt: 2, fontSize: '0.75rem' }}>{t('mariadb_manager.no_active_version')}</Alert>
|
||||
<Alert severity="info" sx={{ mt: 2 }} className="text-sm">{t('mariadb_manager.no_active_version')}</Alert>
|
||||
)}
|
||||
|
||||
{selectedVersion && (
|
||||
@@ -210,10 +211,10 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
|
||||
{/* Databases Section */}
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1.5 }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 800, color: 'primary.light', fontSize: '0.8rem' }}>
|
||||
<Typography variant="subtitle2" sx={{ color: 'primary.light' }} className="font-black text-md">
|
||||
{t('mariadb_manager.databases_title')}
|
||||
</Typography>
|
||||
<Button variant="outlined" size="small" startIcon={<AddIcon sx={{ fontSize: '1rem' }} />} onClick={handleCreateDb} sx={{ fontSize: '0.7rem', height: 28, borderRadius: 1.5 }}>
|
||||
<Button variant="outlined" size="small" startIcon={<AddIcon sx={{ fontSize: 'var(--font-size-base)' }} />} onClick={handleCreateDb} sx={{ height: 28 }} className="text-xs rounded-md">
|
||||
{t('mariadb_manager.create_db_btn')}
|
||||
</Button>
|
||||
</Box>
|
||||
@@ -221,20 +222,20 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
|
||||
<Table stickyHeader size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>{t('mariadb_manager.db_name')}</TableCell>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>{t('mariadb_manager.db_size')}</TableCell>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1, textAlign: 'right' }}>{t('common.actions')}</TableCell>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', py: 1 }} className="font-black text-xs">{t('mariadb_manager.db_name')}</TableCell>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', py: 1 }} className="font-black text-xs">{t('mariadb_manager.db_size')}</TableCell>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', py: 1, textAlign: 'right' }} className="font-black text-xs">{t('common.actions')}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow><TableCell colSpan={3} align="center"><CircularProgress size={20} sx={{ my: 1 }} /></TableCell></TableRow>
|
||||
) : databases.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={3} align="center" sx={{ py: 2, color: 'text.secondary', fontSize: '0.7rem' }}>{t('common.no_data')}</TableCell></TableRow>
|
||||
<TableRow><TableCell colSpan={3} align="center" sx={{ py: 2 }} className="text-secondary text-xs">{t('common.no_data')}</TableCell></TableRow>
|
||||
) : databases.map(db => (
|
||||
<TableRow key={db.name}>
|
||||
<TableCell sx={{ fontWeight: 600, fontSize: '0.75rem' }}>{db.name}</TableCell>
|
||||
<TableCell sx={{ fontSize: '0.7rem', opacity: 0.8 }}>{db.size.toFixed(2)} MB</TableCell>
|
||||
<TableCell className="font-semibold text-sm">{db.name}</TableCell>
|
||||
<TableCell className="text-xs" sx={{ opacity: 0.8 }}>{db.size.toFixed(2)} MB</TableCell>
|
||||
<TableCell align="right" sx={{ py: 0.5 }}>
|
||||
<Tooltip title={t('common.delete')}>
|
||||
<IconButton color="error" size="small" onClick={() => handleDeleteDb(db.name)} sx={{ p: 0.5 }}>
|
||||
@@ -257,7 +258,7 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 800, color: 'secondary.light', fontSize: '0.8rem' }}>
|
||||
{t('mariadb_manager.users_title')}
|
||||
</Typography>
|
||||
<Button variant="outlined" color="secondary" size="small" startIcon={<AddIcon sx={{ fontSize: '1rem' }} />} onClick={handleCreateUser} sx={{ fontSize: '0.7rem', height: 28, borderRadius: 1.5 }}>
|
||||
<Button variant="outlined" color="secondary" size="small" startIcon={<AddIcon sx={{ fontSize: 'var(--font-size-base)' }} />} onClick={handleCreateUser} sx={{ height: 28 }} className="text-xs rounded-md">
|
||||
{t('mariadb_manager.create_user_btn')}
|
||||
</Button>
|
||||
</Box>
|
||||
@@ -265,27 +266,27 @@ const MariaDbDatabaseManager: React.FC<MariaDbDatabaseManagerProps> = ({ version
|
||||
<Table stickyHeader size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>{t('mariadb_manager.user_label')}</TableCell>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1 }}>Host</TableCell>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', fontWeight: 800, fontSize: '0.7rem', py: 1, textAlign: 'right' }}>{t('common.actions')}</TableCell>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', py: 1 }} className="font-black text-xs">{t('mariadb_manager.user_label')}</TableCell>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', py: 1 }} className="font-black text-xs">Host</TableCell>
|
||||
<TableCell sx={{ bgcolor: '#1a1d21', py: 1, textAlign: 'right' }} className="font-black text-xs">{t('common.actions')}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow><TableCell colSpan={3} align="center"><CircularProgress size={20} sx={{ my: 1 }} /></TableCell></TableRow>
|
||||
) : users.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={3} align="center" sx={{ py: 2, color: 'text.secondary', fontSize: '0.7rem' }}>{t('common.no_data')}</TableCell></TableRow>
|
||||
<TableRow><TableCell colSpan={3} align="center" sx={{ py: 2 }} className="text-secondary text-xs">{t('common.no_data')}</TableCell></TableRow>
|
||||
) : users.map(user => (
|
||||
<TableRow key={`${user.user}-${user.host}`}>
|
||||
<TableCell sx={{ fontWeight: 600, fontSize: '0.75rem' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<UserIcon sx={{ opacity: 0.5, fontSize: '0.9rem' }} />
|
||||
<TableCell className="font-semibold text-sm">
|
||||
<Box className="flex-align-center" sx={{ gap: 1 }}>
|
||||
<UserIcon sx={{ fontSize: 'var(--font-size-md)' }} className="text-secondary" />
|
||||
{user.user}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontSize: '0.7rem', opacity: 0.8 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<HostIcon sx={{ opacity: 0.5, fontSize: '0.9rem' }} />
|
||||
<TableCell className="text-xs" sx={{ opacity: 0.8 }}>
|
||||
<Box className="flex-align-center" sx={{ gap: 1 }}>
|
||||
<HostIcon sx={{ fontSize: 'var(--font-size-md)' }} className="text-secondary" />
|
||||
{user.host}
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
@@ -219,9 +219,9 @@ const MariaDbExportWizard: React.FC<MariaDbExportWizardProps> = ({ open, onClose
|
||||
<Box sx={{ mt: 2 }}>
|
||||
{!exportResult ? (
|
||||
<>
|
||||
<Paper sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.05)', borderRadius: 2 }}>
|
||||
<Typography variant="subtitle2" color="primary" sx={{ fontWeight: 'bold' }}>{t('mariadb.import.summary')}</Typography>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Paper sx={{ p: 2, bgcolor: 'var(--color-bg-soft)', border: '1px solid var(--color-border-soft)' }} className="rounded-md">
|
||||
<Typography variant="subtitle2" color="primary" className="font-bold">{t('mariadb.import.summary')}</Typography>
|
||||
<Divider sx={{ my: 1, opacity: 0.05 }} />
|
||||
<Stack spacing={1}>
|
||||
<Typography variant="body2"><strong>{t('mariadb.import.server')}:</strong> {selectedVersion}</Typography>
|
||||
<Typography variant="body2"><strong>{t('mariadb.import.db')}:</strong> {selectedDb}</Typography>
|
||||
@@ -230,7 +230,7 @@ const MariaDbExportWizard: React.FC<MariaDbExportWizardProps> = ({ open, onClose
|
||||
</Stack>
|
||||
</Paper>
|
||||
{loading && (
|
||||
<Box sx={{ mt: 3, p: 2, bgcolor: '#000', borderRadius: 2, maxHeight: '200px', overflowY: 'auto', fontFamily: 'monospace' }}>
|
||||
<Box sx={{ mt: 3, p: 2, bgcolor: 'var(--color-bg-medium)', maxHeight: '200px', overflowY: 'auto', fontFamily: 'monospace' }} className="rounded-md border-soft">
|
||||
{logs.map((log, i) => (
|
||||
<Typography key={i} variant="caption" sx={{ display: 'block', color: log.includes('error') ? 'error.main' : 'success.main' }}>
|
||||
{log}
|
||||
|
||||
@@ -260,9 +260,9 @@ const MariaDbImportWizard: React.FC<MariaDbImportWizardProps> = ({ open, onClose
|
||||
<Box sx={{ mt: 2 }}>
|
||||
{!importResult ? (
|
||||
<>
|
||||
<Paper sx={{ p: 2, bgcolor: 'rgba(255,255,255,0.05)', borderRadius: 2 }}>
|
||||
<Typography variant="subtitle2" color="primary" sx={{ fontWeight: 'bold' }}>{t('mariadb.import.summary')}</Typography>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Paper sx={{ p: 2, bgcolor: 'var(--color-bg-soft)', border: '1px solid var(--color-border-soft)' }} className="rounded-md">
|
||||
<Typography variant="subtitle2" color="primary" className="font-bold">{t('mariadb.import.summary')}</Typography>
|
||||
<Divider sx={{ my: 1, opacity: 0.05 }} />
|
||||
<Stack spacing={1}>
|
||||
<Typography variant="body2"><strong>{t('mariadb.import.file')}:</strong> {filePath.split(/[/\\]/).pop()}</Typography>
|
||||
<Typography variant="body2"><strong>{t('mariadb.import.server')}:</strong> {selectedVersion}</Typography>
|
||||
@@ -271,7 +271,7 @@ const MariaDbImportWizard: React.FC<MariaDbImportWizardProps> = ({ open, onClose
|
||||
</Stack>
|
||||
</Paper>
|
||||
{loading && (
|
||||
<Box sx={{ mt: 3, p: 2, bgcolor: '#000', borderRadius: 2, maxHeight: '200px', overflowY: 'auto', fontFamily: 'monospace' }}>
|
||||
<Box sx={{ mt: 3, p: 2, bgcolor: 'var(--color-bg-medium)', maxHeight: '200px', overflowY: 'auto', fontFamily: 'monospace' }} className="rounded-md border-soft">
|
||||
{logs.map((log, i) => (
|
||||
<Typography key={i} variant="caption" sx={{ display: 'block', color: log.includes('fail') ? 'error.main' : 'success.main' }}>
|
||||
{log}
|
||||
@@ -288,8 +288,8 @@ const MariaDbImportWizard: React.FC<MariaDbImportWizardProps> = ({ open, onClose
|
||||
<SuccessIcon color="success" sx={{ fontSize: 60 }} />
|
||||
<Typography variant="h6" sx={{ mt: 2 }}>{t('mariadb.import.success_title')}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">{importResult.message}</Typography>
|
||||
<Paper sx={{ p: 2, mt: 3, textAlign: 'left', bgcolor: 'rgba(0,180,0,0.1)' }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 'bold' }}>{t('mariadb.import.access_info')}:</Typography>
|
||||
<Paper sx={{ p: 2, mt: 3, textAlign: 'left', bgcolor: 'rgba(0,180,0,0.1)', border: '1px solid rgba(0,180,0,0.2)' }} className="rounded-md">
|
||||
<Typography variant="caption" className="font-bold">{t('mariadb.import.access_info')}:</Typography>
|
||||
<Typography variant="body2"><strong>{t('mariadb.import.host')}:</strong> localhost</Typography>
|
||||
<Typography variant="body2"><strong>{t('mariadb.import.db')}:</strong> {dbConfig.dbName}</Typography>
|
||||
<Typography variant="body2"><strong>{t('mariadb.import.user')}:</strong> {dbConfig.user}</Typography>
|
||||
|
||||
@@ -168,8 +168,8 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
||||
>
|
||||
<DialogTitle sx={{ m: 0, p: 1.5, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 800 }}>{t('php.extensions.title', { version: phpVersion })}</Typography>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.65rem' }}>{t('php.extensions.desc')}</Typography>
|
||||
<Typography variant="subtitle2" className="font-black">{t('php.extensions.title', { version: phpVersion })}</Typography>
|
||||
<Typography variant="caption" color="text.secondary" className="text-xs">{t('php.extensions.desc')}</Typography>
|
||||
</Box>
|
||||
<IconButton onClick={onClose} size="small" sx={{ color: 'text.secondary' }}>
|
||||
<CloseIcon fontSize="small" />
|
||||
@@ -186,18 +186,20 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon sx={{ color: 'text.secondary', fontSize: '1rem' }} />
|
||||
<SearchIcon sx={{ color: 'text.secondary', fontSize: 'var(--font-size-base)' }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
sx: { color: '#fff', bgcolor: 'rgba(0,0,0,0.2)', fontSize: '0.75rem', height: 32 }
|
||||
className: 'text-sm',
|
||||
sx: { color: '#fff', bgcolor: 'rgba(0,0,0,0.2)', height: 32 }
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<RawIcon sx={{ fontSize: '1rem' }} />}
|
||||
startIcon={<RawIcon sx={{ fontSize: 'var(--font-size-base)' }} />}
|
||||
onClick={fetchRawConfig}
|
||||
sx={{ whiteSpace: 'nowrap', borderColor: 'rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.7)', fontSize: '0.7rem', height: 32, borderRadius: 1.5 }}
|
||||
sx={{ whiteSpace: 'nowrap', borderColor: 'var(--color-border-medium)', color: 'var(--color-text-secondary)', height: 32 }}
|
||||
className="text-xs rounded-md"
|
||||
>
|
||||
{t('php.extensions.raw_editor')}
|
||||
</Button>
|
||||
@@ -213,7 +215,7 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={searchTerm ? t('php.extensions.not_found') : t('php.extensions.none')}
|
||||
primaryTypographyProps={{ sx: { textAlign: 'center', color: 'text.secondary', fontSize: '0.75rem' } }}
|
||||
primaryTypographyProps={{ className: 'text-sm text-secondary', sx: { textAlign: 'center' } }}
|
||||
/>
|
||||
</ListItem>
|
||||
) : (
|
||||
@@ -238,13 +240,13 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
||||
>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, fontSize: '0.75rem', fontWeight: 600 }}>
|
||||
<Box className="flex-align-center font-semibold text-sm" sx={{ gap: 1 }}>
|
||||
{ext.name}
|
||||
{ext.enabled && <Chip label={t('php.extensions.active')} size="small" color="success" sx={{ height: 16, fontSize: '0.55rem', fontWeight: 900 }} />}
|
||||
{ext.enabled && <Chip label={t('php.extensions.active')} size="small" color="success" className="font-black text-xs" sx={{ height: 16 }} />}
|
||||
</Box>
|
||||
}
|
||||
secondary={ext.dll}
|
||||
secondaryTypographyProps={{ sx: { color: 'text.secondary', fontSize: '0.65rem', opacity: 0.5 } }}
|
||||
secondaryTypographyProps={{ className: 'text-xs text-secondary', sx: { opacity: 0.5 } }}
|
||||
/>
|
||||
</ListItem>
|
||||
))
|
||||
@@ -253,10 +255,10 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 1.5, bgcolor: 'rgba(255,255,255,0.01)' }}>
|
||||
<Button onClick={fetchExtensions} size="small" startIcon={<RefreshIcon sx={{ fontSize: '0.9rem' }} />} sx={{ mr: 'auto', fontSize: '0.7rem' }}>
|
||||
<Button onClick={fetchExtensions} size="small" startIcon={<RefreshIcon sx={{ fontSize: 'var(--font-size-md)' }} />} className="text-xs" sx={{ mr: 'auto' }}>
|
||||
{t('common.refresh').toUpperCase()}
|
||||
</Button>
|
||||
<Button onClick={onClose} variant="outlined" size="small" sx={{ fontSize: '0.7rem', borderRadius: 1.5 }}>
|
||||
<Button onClick={onClose} variant="outlined" size="small" className="text-xs rounded-md">
|
||||
{t('common.close').toUpperCase()}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
@@ -273,18 +275,18 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
||||
>
|
||||
<DialogTitle sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, p: 1.5 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 800 }}>{t('php.extensions.ini_edit', { version: phpVersion })}</Typography>
|
||||
<Typography variant="subtitle2" className="font-black">{t('php.extensions.ini_edit', { version: phpVersion })}</Typography>
|
||||
<IconButton onClick={() => setIsRawEditorOpen(false)} size="small" sx={{ color: 'rgba(255,255,255,0.5)' }}>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Typography
|
||||
variant="caption"
|
||||
className="text-xs"
|
||||
sx={{
|
||||
color: 'primary.main',
|
||||
wordBreak: 'break-all',
|
||||
cursor: 'pointer',
|
||||
fontSize: '0.6rem',
|
||||
opacity: 0.7,
|
||||
'&:hover': { textDecoration: 'underline', color: 'primary.light' }
|
||||
}}
|
||||
@@ -304,15 +306,15 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon sx={{ color: 'text.secondary', fontSize: '1rem' }} />
|
||||
<SearchIcon sx={{ color: 'text.secondary', fontSize: 'var(--font-size-base)' }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
className: 'text-sm',
|
||||
sx: {
|
||||
color: '#fff',
|
||||
bgcolor: 'rgba(255,255,255,0.03)',
|
||||
fontSize: '0.75rem',
|
||||
height: 32,
|
||||
'& fieldset': { borderColor: 'rgba(255,255,255,0.05)' }
|
||||
'& fieldset': { borderColor: 'var(--color-border-soft)' }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -324,7 +326,7 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 1.5, bgcolor: 'rgba(0,0,0,0.1)' }}>
|
||||
<Button onClick={() => setIsRawEditorOpen(false)} size="small" sx={{ color: 'rgba(255,255,255,0.4)', fontSize: '0.7rem' }}>
|
||||
<Button onClick={() => setIsRawEditorOpen(false)} size="small" className="text-secondary text-xs">
|
||||
{t('common.cancel').toUpperCase()}
|
||||
</Button>
|
||||
<Button
|
||||
@@ -332,9 +334,10 @@ const PhpExtensionManager: React.FC<PhpExtensionManagerProps> = ({ open, onClose
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
size="small"
|
||||
startIcon={isSavingRaw ? <CircularProgress size={14} color="inherit" /> : <SaveIcon sx={{ fontSize: '1rem' }} />}
|
||||
startIcon={isSavingRaw ? <CircularProgress size={14} color="inherit" /> : <SaveIcon sx={{ fontSize: 'var(--font-size-base)' }} />}
|
||||
disabled={isSavingRaw}
|
||||
sx={{ fontSize: '0.7rem', borderRadius: 1.5, px: 2 }}
|
||||
className="text-xs rounded-md"
|
||||
sx={{ px: 2 }}
|
||||
>
|
||||
{isSavingRaw ? t('php.extensions.saving_btn') : t('php.extensions.save_restart_btn')}
|
||||
</Button>
|
||||
|
||||
@@ -201,7 +201,7 @@ export default function ProjectDetails({
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', opacity: 0.3 }}>
|
||||
<ProjectIcon sx={{ fontSize: 80, mb: 2 }} />
|
||||
<Typography variant="h6">{t('projects.no_project_selected')}</Typography>
|
||||
<Typography variant="h6" className="font-semibold">{t('projects.no_project_selected')}</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -218,14 +218,14 @@ export default function ProjectDetails({
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', bgcolor: 'rgba(255,255,255,0.01)', height: '100%', overflow: 'hidden', position: 'relative' }}>
|
||||
{/* Header */}
|
||||
<Box sx={{ p: 4, borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', alignItems: 'flex-start', gap: 3, position: 'relative' }}>
|
||||
<Avatar sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'primary.main', width: 64, height: 64, borderRadius: 3, border: '1px solid rgba(57, 167, 255, 0.2)' }}>
|
||||
<Avatar sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: 'primary.main', width: 64, height: 64, border: '1px solid var(--color-border-soft)' }} className="rounded-lg">
|
||||
<ProjectIcon sx={{ fontSize: 32 }} />
|
||||
</Avatar>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Box sx={{ mb: 2.5 }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 800, letterSpacing: '-0.02em', mb: 0.5 }}>{p.name}</Typography>
|
||||
<Typography variant="h4" sx={{ letterSpacing: '-0.02em', mb: 0.5 }} className="font-black">{p.name}</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.4)', fontFamily: 'monospace' }}>
|
||||
<Typography variant="body2" sx={{ fontFamily: 'monospace' }} className="text-secondary">
|
||||
{p.path}
|
||||
</Typography>
|
||||
<Tooltip title={t('projects.open_folder')}>
|
||||
@@ -263,7 +263,8 @@ export default function ProjectDetails({
|
||||
size="small"
|
||||
startIcon={<LaunchIcon sx={{ fontSize: '1rem !important' }} />}
|
||||
onClick={() => onOpenSite(isHostMapped)}
|
||||
sx={{ px: 2, height: 32, borderRadius: 1.5, fontWeight: 600, fontSize: '0.75rem', borderColor: 'rgba(255,255,255,0.1)', '&:hover': { borderColor: 'primary.main', bgcolor: 'rgba(57, 167, 255, 0.05)' } }}
|
||||
sx={{ px: 2, height: 32, borderColor: 'var(--color-border-medium)', '&:hover': { borderColor: 'primary.main', bgcolor: 'rgba(57, 167, 255, 0.05)' } }}
|
||||
className="rounded-md font-semibold text-sm"
|
||||
>
|
||||
{t('projects.open_site').toUpperCase()}
|
||||
</Button>
|
||||
@@ -320,9 +321,9 @@ export default function ProjectDetails({
|
||||
{/* Tabs */}
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'rgba(255,255,255,0.05)', px: 4, bgcolor: 'rgba(255,255,255,0.01)' }}>
|
||||
<Tabs value={projectDetailTab} onChange={(_e, v) => setProjectDetailTab(v)} textColor="primary" indicatorColor="primary">
|
||||
<Tab label={t('projects.overview')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120, fontSize: '0.8rem' }} />
|
||||
<Tab label={t('projects.database')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120, fontSize: '0.8rem' }} />
|
||||
<Tab label={t('projects.server_settings')} sx={{ fontWeight: 700, py: 2.5, minWidth: 120, fontSize: '0.8rem' }} />
|
||||
<Tab label={t('projects.overview').toUpperCase()} className="font-bold text-md" sx={{ py: 2.5, minWidth: 120 }} />
|
||||
<Tab label={t('projects.database').toUpperCase()} className="font-bold text-md" sx={{ py: 2.5, minWidth: 120 }} />
|
||||
<Tab label={t('projects.server_settings').toUpperCase()} className="font-bold text-md" sx={{ py: 2.5, minWidth: 120 }} />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -258,10 +258,10 @@ export default function ServerTuning() {
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 800, mb: 1, color: 'primary.light' }}>
|
||||
<Typography variant="h4" sx={{ mb: 1, color: 'primary.light' }} className="font-black">
|
||||
{t('tuning.title')}
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ color: 'rgba(255,255,255,0.6)' }}>
|
||||
<Typography variant="body1" className="text-secondary">
|
||||
{t('tuning.desc')}
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -269,22 +269,22 @@ export default function ServerTuning() {
|
||||
<Grid container spacing={4}>
|
||||
{/* PHP Section */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper sx={{ p: 3, height: '100%', bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||
<Paper sx={{ p: 3, height: '100%', bgcolor: 'var(--color-bg-soft)', border: '1px solid var(--color-border-soft)' }} className="rounded-xl">
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3, gap: 2 }}>
|
||||
<Box sx={{ p: 1.5, borderRadius: 2, bgcolor: 'primary.main', display: 'flex' }}>
|
||||
<PhpIcon sx={{ color: 'white' }} />
|
||||
</Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>PHP Tuning</Typography>
|
||||
<Typography variant="h6" className="font-bold">PHP Tuning</Typography>
|
||||
<Chip label={phpVersions.length} size="small" variant="outlined" />
|
||||
</Box>
|
||||
|
||||
<Stack spacing={2}>
|
||||
{phpVersions.map(v => (
|
||||
<Card key={v.id} sx={{ bgcolor: 'rgba(255,255,255,0.03)', backgroundImage: 'none', border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||
<Card key={v.id} sx={{ bgcolor: 'var(--color-bg-soft)', backgroundImage: 'none', border: '1px solid var(--color-border-soft)' }}>
|
||||
<CardActionArea onClick={() => handleOpenTuning(v, 'php')}>
|
||||
<CardContent sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Box>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>PHP {v.version}</Typography>
|
||||
<Typography variant="subtitle1" className="font-bold">PHP {v.version}</Typography>
|
||||
<Typography variant="caption" color="text.secondary">php.ini optimization</Typography>
|
||||
</Box>
|
||||
<Button variant="outlined" size="small" startIcon={<TuneIcon />}>
|
||||
@@ -300,22 +300,22 @@ export default function ServerTuning() {
|
||||
|
||||
{/* MariaDB Section */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper sx={{ p: 3, height: '100%', bgcolor: 'rgba(255,255,255,0.02)', borderRadius: 4, border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||
<Paper sx={{ p: 3, height: '100%', bgcolor: 'var(--color-bg-soft)', border: '1px solid var(--color-border-soft)' }} className="rounded-xl">
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3, gap: 2 }}>
|
||||
<Box sx={{ p: 1.5, borderRadius: 2, bgcolor: 'secondary.main', display: 'flex' }}>
|
||||
<DbIcon sx={{ color: 'white' }} />
|
||||
</Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>MariaDB Tuning</Typography>
|
||||
<Typography variant="h6" className="font-bold">MariaDB Tuning</Typography>
|
||||
<Chip label={mariaDbVersions.length} size="small" variant="outlined" />
|
||||
</Box>
|
||||
|
||||
<Stack spacing={2}>
|
||||
{mariaDbVersions.map(v => (
|
||||
<Card key={v.id} sx={{ bgcolor: 'rgba(255,255,255,0.03)', backgroundImage: 'none', border: '1px solid rgba(255,255,255,0.05)' }}>
|
||||
<Card key={v.id} sx={{ bgcolor: 'var(--color-bg-soft)', backgroundImage: 'none', border: '1px solid var(--color-border-soft)' }}>
|
||||
<CardActionArea onClick={() => handleOpenTuning(v, 'mariadb')}>
|
||||
<CardContent sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Box>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 'bold' }}>MariaDB {v.version}</Typography>
|
||||
<Typography variant="subtitle1" className="font-bold">MariaDB {v.version}</Typography>
|
||||
<Typography variant="caption" color="text.secondary">Performance variables</Typography>
|
||||
</Box>
|
||||
<Button variant="outlined" color="secondary" size="small" startIcon={<TuneIcon />}>
|
||||
@@ -343,7 +343,7 @@ export default function ServerTuning() {
|
||||
<DialogTitle sx={{ borderBottom: '1px solid rgba(255,255,255,0.1)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||
<TuneIcon color={selectedVersion?.type === 'php' ? 'primary' : 'secondary'} />
|
||||
<Typography variant="h6" sx={{ fontWeight: 800 }}>
|
||||
<Typography variant="h6" className="font-black">
|
||||
{selectedVersion?.type === 'php' ? `PHP ${selectedVersion?.version}` : `MariaDB ${selectedVersion?.version}`} Tuning
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -362,7 +362,7 @@ export default function ServerTuning() {
|
||||
<Box key={p.key}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1, alignItems: 'center' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 'bold' }}>{p.label}</Typography>
|
||||
<Typography variant="subtitle2" className="font-bold">{p.label}</Typography>
|
||||
<Tooltip title={t(p.description)}>
|
||||
<InfoIcon sx={{ fontSize: 16, color: 'rgba(255,255,255,0.3)', cursor: 'help' }} />
|
||||
</Tooltip>
|
||||
@@ -372,7 +372,7 @@ export default function ServerTuning() {
|
||||
value={tuningValues[p.key]}
|
||||
onChange={(e) => setTuningValues({ ...tuningValues, [p.key]: parseInt(e.target.value) || p.min })}
|
||||
InputProps={{
|
||||
endAdornment: <Typography variant="caption" sx={{ ml: 1, opacity: 0.5 }}>{p.unit}</Typography>
|
||||
endAdornment: <Typography variant="caption" sx={{ ml: 1 }} className="text-secondary">{p.unit}</Typography>
|
||||
} as any}
|
||||
sx={{ width: 120, '& .MuiOutlinedInput-root': { height: 32 } }}
|
||||
/>
|
||||
@@ -391,7 +391,7 @@ export default function ServerTuning() {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', mt: -1, display: 'block' }}>
|
||||
<Typography variant="caption" className="text-muted" sx={{ mt: -1, display: 'block' }}>
|
||||
{t(p.description)}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
@@ -1,6 +1,54 @@
|
||||
import { GlobalStyles as MuiGlobalStyles } from '@mui/material'
|
||||
|
||||
const styles = {
|
||||
':root': {
|
||||
'--font-size-xs': '0.65rem',
|
||||
'--font-size-sm': '1rem',
|
||||
'--font-size-md': '0.85rem',
|
||||
'--font-size-base': '1rem',
|
||||
'--font-size-lg': '1.25rem',
|
||||
'--font-size-xl': '1.5rem',
|
||||
'--font-weight-thin': '300',
|
||||
'--font-weight-normal': '400',
|
||||
'--font-weight-medium': '500',
|
||||
'--font-weight-semibold': '600',
|
||||
'--font-weight-bold': '700',
|
||||
'--font-weight-black': '800',
|
||||
'--radius-sm': '4px',
|
||||
'--radius-md': '8px',
|
||||
'--radius-lg': '12px',
|
||||
'--radius-xl': '16px',
|
||||
'--color-border-soft': 'rgba(255, 255, 255, 0.05)',
|
||||
'--color-border-medium': 'rgba(255, 255, 255, 0.1)',
|
||||
'--color-text-secondary': 'rgba(255, 255, 255, 0.5)',
|
||||
'--color-text-muted': 'rgba(255, 255, 255, 0.3)',
|
||||
'--color-bg-soft': 'rgba(255, 255, 255, 0.03)',
|
||||
'--color-bg-medium': 'rgba(0, 0, 0, 0.2)',
|
||||
},
|
||||
'.text-xs': { fontSize: 'var(--font-size-xs) !important' },
|
||||
'.text-sm': { fontSize: 'var(--font-size-sm) !important' },
|
||||
'.text-md': { fontSize: 'var(--font-size-md) !important' },
|
||||
'.text-base': { fontSize: 'var(--font-size-base) !important' },
|
||||
'.text-lg': { fontSize: 'var(--font-size-lg) !important' },
|
||||
'.text-xl': { fontSize: 'var(--font-size-xl) !important' },
|
||||
'.font-normal': { fontWeight: 'var(--font-weight-normal) !important' },
|
||||
'.font-medium': { fontWeight: 'var(--font-weight-medium) !important' },
|
||||
'.font-semibold': { fontWeight: 'var(--font-weight-semibold) !important' },
|
||||
'.font-bold': { fontWeight: 'var(--font-weight-bold) !important' },
|
||||
'.font-black': { fontWeight: 'var(--font-weight-black) !important' },
|
||||
'.text-secondary': { color: 'var(--color-text-secondary) !important' },
|
||||
'.text-muted': { color: 'var(--color-text-muted) !important' },
|
||||
'.border-soft': { border: '1px solid var(--color-border-soft) !important' },
|
||||
'.border-medium': { border: '1px solid var(--color-border-medium) !important' },
|
||||
'.bg-soft': { backgroundColor: 'var(--color-bg-soft) !important' },
|
||||
'.bg-medium': { backgroundColor: 'var(--color-bg-medium) !important' },
|
||||
'.rounded-sm': { borderRadius: 'var(--radius-sm) !important' },
|
||||
'.rounded-md': { borderRadius: 'var(--radius-md) !important' },
|
||||
'.rounded-lg': { borderRadius: 'var(--radius-lg) !important' },
|
||||
'.rounded-xl': { borderRadius: 'var(--radius-xl) !important' },
|
||||
'.flex-center': { display: 'flex', alignItems: 'center', justifyContent: 'center' },
|
||||
'.flex-between': { display: 'flex', alignItems: 'center', justifyContent: 'space-between' },
|
||||
'.flex-align-center': { display: 'flex', alignItems: 'center' },
|
||||
'html, body, #root': {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
|
||||
Reference in New Issue
Block a user