feat: add Turkish and English localization files and initialize main application component
This commit is contained in:
+169
-119
@@ -68,7 +68,8 @@ import {
|
|||||||
Language as HostIcon,
|
Language as HostIcon,
|
||||||
Launch as LaunchIcon,
|
Launch as LaunchIcon,
|
||||||
Handyman as ToolsIcon,
|
Handyman as ToolsIcon,
|
||||||
Restore as ResetIcon
|
Restore as ResetIcon,
|
||||||
|
Search as SearchIcon
|
||||||
} from '@mui/icons-material'
|
} from '@mui/icons-material'
|
||||||
import Swal from 'sweetalert2'
|
import Swal from 'sweetalert2'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -117,6 +118,7 @@ function App(): JSX.Element {
|
|||||||
mariadb: { status: 'stopped' }
|
mariadb: { status: 'stopped' }
|
||||||
})
|
})
|
||||||
const [activeTab, setActiveTab] = useState('dashboard')
|
const [activeTab, setActiveTab] = useState('dashboard')
|
||||||
|
const [projectSearch, setProjectSearch] = useState('')
|
||||||
const [settingsTab, setSettingsTab] = useState(0)
|
const [settingsTab, setSettingsTab] = useState(0)
|
||||||
const [dashboardTab, setDashboardTab] = useState(0)
|
const [dashboardTab, setDashboardTab] = useState(0)
|
||||||
const [projects, setProjects] = useState<any[]>([])
|
const [projects, setProjects] = useState<any[]>([])
|
||||||
@@ -1536,10 +1538,40 @@ function App(): JSX.Element {
|
|||||||
|
|
||||||
{activeTab === 'projects' && (
|
{activeTab === 'projects' && (
|
||||||
<Box>
|
<Box>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', sm: 'row' }, gap: 2, alignItems: { xs: 'stretch', sm: 'center' }, mb: 3 }}>
|
||||||
<Typography variant="h5" sx={{ fontWeight: 'medium' }}>
|
<Typography variant="h5" sx={{ fontWeight: 'medium', flexGrow: 1 }}>
|
||||||
{t('projects.title')}
|
{t('projects.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
placeholder={t('projects.search')}
|
||||||
|
value={projectSearch}
|
||||||
|
onChange={(e) => setProjectSearch(e.target.value)}
|
||||||
|
sx={{
|
||||||
|
minWidth: { sm: 300 },
|
||||||
|
bgcolor: 'rgba(255, 255, 255, 0.03)',
|
||||||
|
borderRadius: 1,
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
color: '#fff',
|
||||||
|
'& fieldset': { borderColor: 'rgba(255, 255, 255, 0.1)' },
|
||||||
|
'&:hover fieldset': { borderColor: 'rgba(255, 255, 255, 0.2)' },
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<SearchIcon sx={{ color: 'rgba(255, 255, 255, 0.3)' }} />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
endAdornment: projectSearch && (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton size="small" onClick={() => setProjectSearch('')} sx={{ color: 'rgba(255, 255, 255, 0.3)' }}>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Button variant="contained" startIcon={<ProjectIcon />} onClick={() => setIsProjectDialogOpen(true)}>
|
<Button variant="contained" startIcon={<ProjectIcon />} onClick={() => setIsProjectDialogOpen(true)}>
|
||||||
{t('projects.add_new')}
|
{t('projects.add_new')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1549,126 +1581,144 @@ function App(): JSX.Element {
|
|||||||
<Paper sx={{ p: 4, textAlign: 'center', bgcolor: 'rgba(255, 255, 255, 0.03)' }}>
|
<Paper sx={{ p: 4, textAlign: 'center', bgcolor: 'rgba(255, 255, 255, 0.03)' }}>
|
||||||
<Typography variant="body1" color="text.secondary">{t('projects.empty')}</Typography>
|
<Typography variant="body1" color="text.secondary">{t('projects.empty')}</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
) : (
|
) : (() => {
|
||||||
<Grid container spacing={3}>
|
const filteredProjects = projects.filter(p =>
|
||||||
{projects.map((project) => (
|
p.name.toLowerCase().includes(projectSearch.toLowerCase()) ||
|
||||||
<Grid item xs={12} md={6} key={project.id}>
|
p.host.toLowerCase().includes(projectSearch.toLowerCase())
|
||||||
<Paper
|
);
|
||||||
elevation={0}
|
|
||||||
sx={{
|
if (filteredProjects.length === 0) {
|
||||||
p: 2.5,
|
return (
|
||||||
background: 'rgba(255, 255, 255, 0.03)',
|
<Paper sx={{ p: 6, textAlign: 'center', bgcolor: 'rgba(255, 255, 255, 0.02)', border: '1px dashed rgba(255, 255, 255, 0.1)' }}>
|
||||||
backdropFilter: 'blur(10px)',
|
<SearchIcon sx={{ fontSize: 48, color: 'rgba(255, 255, 255, 0.1)', mb: 2 }} />
|
||||||
border: '1px solid rgba(255, 255, 255, 0.05)',
|
<Typography variant="h6" sx={{ color: 'rgba(255, 255, 255, 0.4)' }}>
|
||||||
borderRadius: 3,
|
{t('common.not_found', 'Eşleşen proje bulunamadı')}
|
||||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
</Typography>
|
||||||
'&:hover': {
|
</Paper>
|
||||||
background: 'rgba(255, 255, 255, 0.06)',
|
);
|
||||||
transform: 'translateY(-4px)',
|
}
|
||||||
borderColor: 'rgba(57, 167, 255, 0.3)',
|
|
||||||
boxShadow: '0 12px 24px rgba(0,0,0,0.3)'
|
return (
|
||||||
}
|
<Grid container spacing={3}>
|
||||||
}}
|
{filteredProjects.map((project) => (
|
||||||
>
|
<Grid item xs={12} md={6} key={project.id}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2, gap: 1 }}>
|
<Paper
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, minWidth: 0 }}>
|
elevation={0}
|
||||||
<Avatar sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: '#39A7FF', flexShrink: 0 }}>
|
sx={{
|
||||||
<FolderIcon />
|
p: 2.5,
|
||||||
</Avatar>
|
background: 'rgba(255, 255, 255, 0.03)',
|
||||||
<Box sx={{ minWidth: 0, flexGrow: 1 }}>
|
backdropFilter: 'blur(10px)',
|
||||||
<Typography variant="h6" noWrap sx={{ fontWeight: 700, color: '#fff', lineHeight: 1.2, textOverflow: 'ellipsis' }}>
|
border: '1px solid rgba(255, 255, 255, 0.05)',
|
||||||
{project.name}
|
borderRadius: 3,
|
||||||
</Typography>
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
<Typography variant="caption" noWrap sx={{ color: 'rgba(255, 255, 255, 0.4)', fontFamily: 'monospace', display: 'block', textOverflow: 'ellipsis' }}>
|
'&:hover': {
|
||||||
{project.path.length > 40 ? '...' + project.path.slice(-37) : project.path}
|
background: 'rgba(255, 255, 255, 0.06)',
|
||||||
</Typography>
|
transform: 'translateY(-4px)',
|
||||||
|
borderColor: 'rgba(57, 167, 255, 0.3)',
|
||||||
|
boxShadow: '0 12px 24px rgba(0,0,0,0.3)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2, gap: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, minWidth: 0 }}>
|
||||||
|
<Avatar sx={{ bgcolor: 'rgba(57, 167, 255, 0.1)', color: '#39A7FF', flexShrink: 0 }}>
|
||||||
|
<FolderIcon />
|
||||||
|
</Avatar>
|
||||||
|
<Box sx={{ minWidth: 0, flexGrow: 1 }}>
|
||||||
|
<Typography variant="h6" noWrap sx={{ fontWeight: 700, color: '#fff', lineHeight: 1.2, textOverflow: 'ellipsis' }}>
|
||||||
|
{project.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" noWrap sx={{ color: 'rgba(255, 255, 255, 0.4)', fontFamily: 'monospace', display: 'block', textOverflow: 'ellipsis' }}>
|
||||||
|
{project.path.length > 40 ? '...' + project.path.slice(-37) : project.path}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
<Box sx={{ display: 'flex', gap: 0.5, flexShrink: 0 }}>
|
||||||
<Box sx={{ display: 'flex', gap: 0.5, flexShrink: 0 }}>
|
<Tooltip title={t('common.edit')}>
|
||||||
<Tooltip title={t('common.edit')}>
|
<IconButton size="small" onClick={() => {
|
||||||
<IconButton size="small" onClick={() => {
|
setNewProject(project)
|
||||||
setNewProject(project)
|
setIsProjectDialogOpen(true)
|
||||||
setIsProjectDialogOpen(true)
|
}} sx={{ color: 'rgba(255,255,255,0.4)', '&:hover': { color: '#39A7FF' } }}>
|
||||||
}} sx={{ color: 'rgba(255,255,255,0.4)', '&:hover': { color: '#39A7FF' } }}>
|
<EditIcon fontSize="small" />
|
||||||
<EditIcon fontSize="small" />
|
</IconButton>
|
||||||
</IconButton>
|
</Tooltip>
|
||||||
</Tooltip>
|
{project.serverType !== 'apache' && (
|
||||||
{project.serverType !== 'apache' && (
|
<Tooltip title={t('server.nginx.config')}>
|
||||||
<Tooltip title={t('server.nginx.config')}>
|
<IconButton size="small" onClick={() => handleOpenProjectNginxConfig(project.host)} sx={{ color: 'rgba(255,255,255,0.4)', '&:hover': { color: '#39A7FF' } }}>
|
||||||
<IconButton size="small" onClick={() => handleOpenProjectNginxConfig(project.host)} sx={{ color: 'rgba(255,255,255,0.4)', '&:hover': { color: '#39A7FF' } }}>
|
<TerminalIcon fontSize="small" />
|
||||||
<TerminalIcon fontSize="small" />
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<Tooltip title="Sil">
|
||||||
|
<IconButton size="small" onClick={() => handleRemoveProject(project.id)} sx={{ color: 'rgba(255,255,255,0.4)', '&:hover': { color: '#ff4444' } }}>
|
||||||
|
<DeleteIcon fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
|
||||||
<Tooltip title="Sil">
|
|
||||||
<IconButton size="small" onClick={() => handleRemoveProject(project.id)} sx={{ color: 'rgba(255,255,255,0.4)', '&:hover': { color: '#ff4444' } }}>
|
|
||||||
<DeleteIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Stack spacing={1.5}>
|
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
|
||||||
<Chip
|
|
||||||
size="small"
|
|
||||||
icon={<PhpIcon sx={{ fontSize: '1rem !important' }} />}
|
|
||||||
label={`PHP ${project.phpVersion}`}
|
|
||||||
sx={{ bgcolor: 'rgba(71, 74, 255, 0.1)', color: '#777bb4', fontWeight: 600, border: '1px solid rgba(71, 74, 255, 0.2)' }}
|
|
||||||
/>
|
|
||||||
<Chip
|
|
||||||
size="small"
|
|
||||||
icon={project.serverType === 'apache' ? <ServerIcon sx={{ fontSize: '1rem !important' }} /> : <WebIcon sx={{ fontSize: '1rem !important' }} />}
|
|
||||||
label={project.serverType === 'apache' ? 'Apache' : 'Nginx'}
|
|
||||||
sx={{ bgcolor: 'rgba(255, 255, 255, 0.05)', color: '#fff', fontWeight: 600 }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<ProjectResourceMonitor
|
|
||||||
serverType={project.serverType === 'apache' ? 'Apache' : 'Nginx'}
|
|
||||||
serverStats={project.serverType === 'apache' ? status.apache : status.nginx}
|
|
||||||
phpStats={status[`php:${project.phpVersion}`]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Paper variant="outlined" sx={{
|
|
||||||
p: 1.5,
|
|
||||||
bgcolor: 'rgba(0,0,0,0.2)',
|
|
||||||
borderColor: 'rgba(255,255,255,0.05)',
|
|
||||||
borderRadius: 2,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between'
|
|
||||||
}}>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
||||||
<HostIcon sx={{ fontSize: '1rem', color: 'rgba(255,255,255,0.3)' }} />
|
|
||||||
<Typography variant="body2" sx={{ color: '#fff', fontWeight: 500, fontFamily: 'monospace' }}>
|
|
||||||
{project.host}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
</Box>
|
||||||
size="small"
|
|
||||||
endIcon={<LaunchIcon />}
|
<Stack spacing={1.5}>
|
||||||
onClick={() => {
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
const port = project.serverType === 'apache' ? settings.apachePort : settings.nginxPort
|
<Chip
|
||||||
window.open(`http://localhost:${port}/${project.host}/`, '_blank')
|
size="small"
|
||||||
}}
|
icon={<PhpIcon sx={{ fontSize: '1rem !important' }} />}
|
||||||
sx={{
|
label={`PHP ${project.phpVersion}`}
|
||||||
color: '#39A7FF',
|
sx={{ bgcolor: 'rgba(71, 74, 255, 0.1)', color: '#777bb4', fontWeight: 600, border: '1px solid rgba(71, 74, 255, 0.2)' }}
|
||||||
textTransform: 'none',
|
/>
|
||||||
fontSize: '0.75rem',
|
<Chip
|
||||||
p: '2px 8px',
|
size="small"
|
||||||
'&:hover': { bgcolor: 'rgba(57, 167, 255, 0.1)' }
|
icon={project.serverType === 'apache' ? <ServerIcon sx={{ fontSize: '1rem !important' }} /> : <WebIcon sx={{ fontSize: '1rem !important' }} />}
|
||||||
}}
|
label={project.serverType === 'apache' ? 'Apache' : 'Nginx'}
|
||||||
>
|
sx={{ bgcolor: 'rgba(255, 255, 255, 0.05)', color: '#fff', fontWeight: 600 }}
|
||||||
{t('projects.browse')}
|
/>
|
||||||
</Button>
|
</Box>
|
||||||
</Paper>
|
|
||||||
</Stack>
|
<ProjectResourceMonitor
|
||||||
</Paper>
|
serverType={project.serverType === 'apache' ? 'Apache' : 'Nginx'}
|
||||||
</Grid>
|
serverStats={project.serverType === 'apache' ? status.apache : status.nginx}
|
||||||
))}
|
phpStats={status[`php:${project.phpVersion}`]}
|
||||||
</Grid>
|
/>
|
||||||
)}
|
|
||||||
|
<Paper variant="outlined" sx={{
|
||||||
|
p: 1.5,
|
||||||
|
bgcolor: 'rgba(0,0,0,0.2)',
|
||||||
|
borderColor: 'rgba(255,255,255,0.05)',
|
||||||
|
borderRadius: 2,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
}}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<HostIcon sx={{ fontSize: '1rem', color: 'rgba(255,255,255,0.3)' }} />
|
||||||
|
<Typography variant="body2" sx={{ color: '#fff', fontWeight: 500, fontFamily: 'monospace' }}>
|
||||||
|
{project.host}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
endIcon={<LaunchIcon />}
|
||||||
|
onClick={() => {
|
||||||
|
const port = project.serverType === 'apache' ? settings.apachePort : settings.nginxPort
|
||||||
|
window.open(`http://localhost:${port}/${project.host}/`, '_blank')
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
color: '#39A7FF',
|
||||||
|
textTransform: 'none',
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
p: '2px 8px',
|
||||||
|
'&:hover': { bgcolor: 'rgba(57, 167, 255, 0.1)' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('projects.browse')}
|
||||||
|
</Button>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"projects": {
|
"projects": {
|
||||||
"title": "My Projects",
|
"title": "My Projects",
|
||||||
"add_new": "Add New Project",
|
"add_new": "Add New Project",
|
||||||
|
"search": "Search for project or host...",
|
||||||
"edit": "Edit Project",
|
"edit": "Edit Project",
|
||||||
"empty": "No projects added yet.",
|
"empty": "No projects added yet.",
|
||||||
"name": "Project Name",
|
"name": "Project Name",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"projects": {
|
"projects": {
|
||||||
"title": "Projelerim",
|
"title": "Projelerim",
|
||||||
"add_new": "Yeni Proje Ekle",
|
"add_new": "Yeni Proje Ekle",
|
||||||
|
"search": "Proje veya host ara...",
|
||||||
"edit": "Proje Düzenle",
|
"edit": "Proje Düzenle",
|
||||||
"empty": "Henüz bir proje eklenmemiş.",
|
"empty": "Henüz bir proje eklenmemiş.",
|
||||||
"name": "Proje Adı",
|
"name": "Proje Adı",
|
||||||
@@ -77,7 +78,8 @@
|
|||||||
"added": "Proje başarıyla eklendi.",
|
"added": "Proje başarıyla eklendi.",
|
||||||
"updated": "Proje başarıyla güncellendi.",
|
"updated": "Proje başarıyla güncellendi.",
|
||||||
"deleted": "Proje silindi.",
|
"deleted": "Proje silindi.",
|
||||||
"delete_confirm": "Bu projeyi silmek istediğinize emin misiniz? Dosyalarınız silinmez, sadece listeden kaldırılır."
|
"delete_confirm": "Bu projeyi silmek istediğinize emin misiniz? Dosyalarınız silinmez, sadece listeden kaldırılır.",
|
||||||
|
"browse" : "Gözat"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"general": "Genel Ayarlar",
|
"general": "Genel Ayarlar",
|
||||||
|
|||||||
Reference in New Issue
Block a user