new changes

This commit is contained in:
Niranjan
2026-04-07 13:30:25 +05:30
parent 6dea3b4307
commit 7b394d6446
10 changed files with 197 additions and 7 deletions

View File

@@ -523,11 +523,14 @@ export async function updateDatabasePassword(dbId: number, password: string) {
})
}
export type SslAlertSite = { site: string; primary: string; days_left: number | null | undefined }
export async function getDashboardStats() {
return apiRequest<{
site_count: number
ftp_count: number
database_count: number
ssl_alerts: { expired: SslAlertSite[]; expiring: SslAlertSite[] }
system: {
cpu_percent: number
memory_percent: number
@@ -536,10 +539,20 @@ export async function getDashboardStats() {
disk_percent: number
disk_used_gb: number
disk_total_gb: number
inode_percent: number | null
inode_used: number | null
inode_total: number | null
}
}>('/dashboard/stats')
}
export async function getFirewallBackendStatus() {
return apiRequest<{
ufw: { detected: boolean; active: boolean | null; summary_line: string }
firewalld: { detected: boolean; running: boolean; state: string | null }
}>('/firewall/status')
}
export async function applyCrontab() {
return apiRequest<{ status: boolean; msg: string; count: number }>('/crontab/apply', { method: 'POST' })
}

View File

@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { getDashboardStats } from '../api/client'
import { PageHeader, AdminCard, AdminAlert } from '../components/admin'
@@ -6,6 +7,7 @@ interface Stats {
site_count: number
ftp_count: number
database_count: number
ssl_alerts?: { expired: { site: string; primary: string; days_left?: number | null }[]; expiring: { site: string; primary: string; days_left?: number | null }[] }
system: {
cpu_percent: number
memory_percent: number
@@ -14,6 +16,9 @@ interface Stats {
disk_percent: number
disk_used_gb: number
disk_total_gb: number
inode_percent?: number | null
inode_used?: number | null
inode_total?: number | null
}
}
@@ -47,9 +52,39 @@ export function DashboardPage() {
)
}
const ssl = stats.ssl_alerts || { expired: [], expiring: [] }
const inodePct = stats.system.inode_percent
const inodeWarn = typeof inodePct === 'number' && inodePct >= 85
return (
<>
<PageHeader />
{ssl.expired.length > 0 ? (
<AdminAlert variant="danger" className="mb-3">
<strong>Expired certificates:</strong>{' '}
{ssl.expired.map((s) => `${s.site} (${s.primary})`).join('; ')}.{' '}
<Link to="/ssl_domain" className="alert-link">
Renew in Domains &amp; SSL
</Link>
</AdminAlert>
) : null}
{ssl.expiring.length > 0 ? (
<AdminAlert variant="warning" className="mb-3">
<strong>Certificates expiring within 14 days:</strong>{' '}
{ssl.expiring.map((s) => `${s.site} (${s.days_left ?? '?'}d)`).join('; ')}.{' '}
<Link to="/ssl_domain" className="alert-link">
Open Domains &amp; SSL
</Link>
</AdminAlert>
) : null}
{inodeWarn ? (
<AdminAlert variant="warning" className="mb-3">
Root filesystem inode usage is high ({inodePct}%). Large numbers of small files can exhaust inodes before
disk space consider cleanup or archiving.
</AdminAlert>
) : null}
<div className="row g-3 mb-4">
<div className="col-md-4 d-flex">
<StatCard iconClass="ti ti-world" title="Websites" value={stats.site_count} />
@@ -78,7 +113,11 @@ export function DashboardPage() {
iconClass="ti ti-database-export"
title="Disk"
value={`${stats.system.disk_percent}%`}
subtitle={`${stats.system.disk_used_gb} / ${stats.system.disk_total_gb} GB`}
subtitle={
typeof stats.system.inode_percent === 'number'
? `${stats.system.disk_used_gb} / ${stats.system.disk_total_gb} GB · inodes ${stats.system.inode_percent}%`
: `${stats.system.disk_used_gb} / ${stats.system.disk_total_gb} GB`
}
/>
</div>
</div>

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'
import Modal from 'react-bootstrap/Modal'
import { apiRequest, applyFirewallRules } from '../api/client'
import { apiRequest, applyFirewallRules, getFirewallBackendStatus } from '../api/client'
import { PageHeader, AdminButton, AdminAlert, AdminTable, EmptyState } from '../components/admin'
interface FirewallRule {
@@ -11,6 +11,11 @@ interface FirewallRule {
ps: string
}
interface FirewallBackendStatus {
ufw: { detected: boolean; active: boolean | null; summary_line: string }
firewalld: { detected: boolean; running: boolean; state: string | null }
}
export function FirewallPage() {
const [rules, setRules] = useState<FirewallRule[]>([])
const [loading, setLoading] = useState(true)
@@ -19,11 +24,18 @@ export function FirewallPage() {
const [creating, setCreating] = useState(false)
const [creatingError, setCreatingError] = useState('')
const [applying, setApplying] = useState(false)
const [backend, setBackend] = useState<FirewallBackendStatus | null>(null)
const loadRules = () => {
setLoading(true)
apiRequest<FirewallRule[]>('/firewall/list')
.then(setRules)
Promise.all([
apiRequest<FirewallRule[]>('/firewall/list'),
getFirewallBackendStatus().catch(() => null),
])
.then(([list, st]) => {
setRules(list)
setBackend(st)
})
.catch((err) => setError(err.message))
.finally(() => setLoading(false))
}
@@ -107,6 +119,44 @@ export function FirewallPage() {
{error ? <AdminAlert className="mb-3">{error}</AdminAlert> : null}
{backend ? (
<div className="card border-0 shadow-sm mb-3">
<div className="card-body py-3">
<h6 className="text-muted text-uppercase small mb-2">Host firewall (live)</h6>
<div className="d-flex flex-wrap gap-3 align-items-center">
<div>
<span className="text-muted small me-2">UFW</span>
{!backend.ufw.detected ? (
<span className="badge text-bg-secondary">Not detected</span>
) : backend.ufw.active === true ? (
<span className="badge text-bg-success">Active</span>
) : backend.ufw.active === false ? (
<span className="badge text-bg-warning text-dark">Inactive</span>
) : (
<span className="badge text-bg-secondary">Unknown</span>
)}
{backend.ufw.summary_line ? (
<span className="small text-muted ms-2 font-monospace">{backend.ufw.summary_line}</span>
) : null}
</div>
<div>
<span className="text-muted small me-2">firewalld</span>
{!backend.firewalld.detected ? (
<span className="badge text-bg-secondary">Not detected</span>
) : backend.firewalld.running ? (
<span className="badge text-bg-info text-dark">Running</span>
) : (
<span className="badge text-bg-secondary">Not running</span>
)}
</div>
</div>
<p className="small text-muted mb-0 mt-2">
Panel &quot;Apply to UFW&quot; runs <code>ufw</code> only. If you use firewalld, sync rules there separately.
</p>
</div>
</div>
) : null}
<div className="alert alert-warning small mb-3">
Rules are stored in the panel. Click &quot;Apply to UFW&quot; to run <code className="font-monospace">ufw allow/deny</code> for each rule.
</div>