feat(panel): 15-minute CPU average and dark-mode IPv6 select
- Widen the CPU average window from 5 to 15 minutes across the admin and user panels (List, Show, BatchUsers, PanelResources) and relabel the columns/cards accordingly. - Replace the hardcoded white background of the create-user IPv6 dropdown with a themed class so it matches the dark color scheme. Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
1 parent
f9114c86f3
commit
5cc2f10ae4
6 files changed
+14
-12
No files matched your search
@@ -63,6 +63,8 @@ form.inline { display: inline; }
|
||||
@media (prefers-color-scheme: dark) { .field label { color: #9aa0a8; } }
|
||||
.field input { width: 100%; padding: 8px 10px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 13px; background: #fff; color: inherit; }
|
||||
@media (prefers-color-scheme: dark) { .field input { background: #15171b; border-color: #3a3f47; } }
|
||||
.field select.field-select { width: 100%; padding: 8px 10px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 13px; background: #fff; color: inherit; }
|
||||
@media (prefers-color-scheme: dark) { .field select.field-select { background: #15171b; border-color: #3a3f47; } }
|
||||
.cpu-row { display: flex; gap: 4px; align-items: stretch; }
|
||||
.cpu-row input { flex: 1; min-width: 0; }
|
||||
.cpu-row .step { display: flex; flex-direction: column; }
|
||||
@@ -158,7 +160,7 @@ a.copy.copied { border-color: #86efac; background: #dcfce7; color: #15803d; }
|
||||
<div class="field"><label>{{if eq .Lang "zh"}}流量配额 GiB{{else}}Bandwidth GiB{{end}}</label><input name="bandwidth" id="cfBandwidth" type="number" min="0" step="1" value="0" title="{{if eq .Lang "zh"}}每月流量上限(上传+下载),0 = 不限;超限后限速 1Mbps{{else}}monthly quota (upload+download), 0 = unlimited; throttled to 1Mbps when exceeded{{end}}"></div>
|
||||
{{if .IPv6PoolMode}}
|
||||
<div class="field"><label>{{if eq .Lang "zh"}}IPv6 地址{{else}}IPv6 address{{end}}</label>
|
||||
<select name="ipv6" id="cfIpv6" style="width:100%;padding:8px 10px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;background:#fff;color:inherit;">
|
||||
<select name="ipv6" id="cfIpv6" class="field-select">
|
||||
<option value="auto" selected>{{if eq .Lang "zh"}}自动分配(第一个空闲){{else}}Auto (first free){{end}}</option>
|
||||
{{range .PoolFree}}<option value="{{.}}">{{.}}</option>{{end}}
|
||||
<option value="none">{{if eq .Lang "zh"}}不分配 V6{{else}}No IPv6{{end}}</option>
|
||||
@@ -180,7 +182,7 @@ a.copy.copied { border-color: #86efac; background: #dcfce7; color: #15803d; }
|
||||
<th>{{if eq .Lang "zh"}}状态{{else}}State{{end}}</th>
|
||||
<th>{{if eq .Lang "zh"}}端口/SSH{{else}}Ports/SSH{{end}}</th>
|
||||
<th>{{if eq .Lang "zh"}}配额 CPU/内存/磁盘{{else}}Quota CPU/Mem/Disk{{end}}</th>
|
||||
<th class="sortable" data-type="number">{{if eq .Lang "zh"}}5分钟CPU{{else}}5m CPU{{end}}<span class="ind"></span></th>
|
||||
<th class="sortable" data-type="number">{{if eq .Lang "zh"}}15分钟CPU{{else}}15m CPU{{end}}<span class="ind"></span></th>
|
||||
<th class="sortable" data-type="number">{{if eq .Lang "zh"}}内存{{else}}Mem{{end}}<span class="ind"></span></th>
|
||||
<th class="sortable" data-type="number">{{if eq .Lang "zh"}}进程{{else}}Procs{{end}}<span class="ind"></span></th>
|
||||
<th class="sortable" data-type="number">{{if eq .Lang "zh"}}磁盘{{else}}Disk{{end}}<span class="ind"></span></th>
|
||||
@@ -371,7 +373,7 @@ function del(name){
|
||||
var qc = document.getElementById('quotaCpu');
|
||||
if (qc) qc.addEventListener('input', function(){ syncCpuStep(qc); });
|
||||
})();
|
||||
// Clickable column headers for Name / 5m CPU / Mem / Procs / Disk: cycle
|
||||
// Clickable column headers for Name / 15m CPU / Mem / Procs / Disk: cycle
|
||||
// ascending -> descending -> default. Only one column sorts at a time; the
|
||||
// default state restores the original render order.
|
||||
(function(){
|
||||
|
||||
@@ -130,7 +130,7 @@ func readUptime() time.Duration {
|
||||
}
|
||||
|
||||
// UserStatus is one row of the admin user table: the DB user record plus the
|
||||
// latest persisted resource sample and five-minute CPU average.
|
||||
// latest persisted resource sample and fifteen-minute CPU average.
|
||||
type UserStatus struct {
|
||||
User *db.User
|
||||
State string
|
||||
@@ -154,7 +154,7 @@ func (m *Manager) BatchUsers() ([]*UserStatus, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
average, err := m.db.AverageCPU(time.Now().Add(-5 * time.Minute).Unix())
|
||||
average, err := m.db.AverageCPU(time.Now().Add(-15 * time.Minute).Unix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -716,7 +716,7 @@ func (m *Manager) List() ([]*Result, error) {
|
||||
return nil, err
|
||||
}
|
||||
latest, _ := m.db.LatestResourceSamples()
|
||||
average, _ := m.db.AverageCPU(time.Now().Add(-5 * time.Minute).Unix())
|
||||
average, _ := m.db.AverageCPU(time.Now().Add(-15 * time.Minute).Unix())
|
||||
live := m.liveStatusFallback(users, latest)
|
||||
out := make([]*Result, 0, len(users))
|
||||
for _, u := range users {
|
||||
@@ -735,7 +735,7 @@ func (m *Manager) Show(name string) (*Result, error) {
|
||||
return nil, err
|
||||
}
|
||||
latest, _ := m.db.LatestResourceSamples()
|
||||
average, _ := m.db.AverageCPU(time.Now().Add(-5 * time.Minute).Unix())
|
||||
average, _ := m.db.AverageCPU(time.Now().Add(-15 * time.Minute).Unix())
|
||||
sample, hasSample := latest[u.ID]
|
||||
live := m.liveStatusFallback([]*db.User{u}, latest)
|
||||
avg, hasAverage := average[u.ID]
|
||||
|
||||
@@ -229,14 +229,14 @@ func decorateStoredUsage(r *Result, sample db.ResourceSample, hasSample bool, av
|
||||
}
|
||||
|
||||
// PanelResources returns the persisted usage for a user's panel page: the
|
||||
// five-minute CPU average and the latest memory/disk usage, formatted for
|
||||
// fifteen-minute CPU average and the latest memory/disk usage, formatted for
|
||||
// display. No Incus call is made — the data comes from the background sampler.
|
||||
func (m *Manager) PanelResources(userID int64) (cpuUse, memUse, diskUse string) {
|
||||
latest, err := m.db.LatestResourceSamples()
|
||||
if err != nil {
|
||||
return "-", "-", "-"
|
||||
}
|
||||
avg, err := m.db.AverageCPU(time.Now().Add(-5 * time.Minute).Unix())
|
||||
avg, err := m.db.AverageCPU(time.Now().Add(-15 * time.Minute).Unix())
|
||||
if err != nil {
|
||||
avg = map[int64]float64{}
|
||||
}
|
||||
|
||||
@@ -293,8 +293,8 @@ func (s *Server) buildData(u *db.User, msg, errMsg string) pageData {
|
||||
Msg: msg,
|
||||
Err: errMsg,
|
||||
}
|
||||
// Resource usage comes from the persisted sampler snapshot (five-minute CPU
|
||||
// average plus latest memory/disk), never from a live Incus sample.
|
||||
// Resource usage comes from the persisted sampler snapshot (fifteen-minute
|
||||
// CPU average plus latest memory/disk), never from a live Incus sample.
|
||||
d.CPUUse, d.MemUse, d.DiskUsed = s.mgr.PanelResources(u.ID)
|
||||
// One `incus list` call only for the container status (must be live).
|
||||
// Bandwidth is read from the DB — the background sampler writes it every 60s.
|
||||
|
||||
@@ -150,7 +150,7 @@ footer a{color:var(--muted);text-decoration:underline}
|
||||
<div class="qitem"><span class="qlabel">{{if eq .Lang "zh"}}磁盘{{else}}Disk{{end}}</span><span class="qval">{{.QuotaDisk}}</span></div>
|
||||
</div>
|
||||
<div class="qgrid">
|
||||
<div class="qitem"><span class="qlabel">{{if eq .Lang "zh"}}5分钟CPU{{else}}5m CPU{{end}}</span><span class="qval">{{.CPUUse}}</span></div>
|
||||
<div class="qitem"><span class="qlabel">{{if eq .Lang "zh"}}15分钟CPU{{else}}15m CPU{{end}}</span><span class="qval">{{.CPUUse}}</span></div>
|
||||
<div class="qitem"><span class="qlabel">{{if eq .Lang "zh"}}已用内存{{else}}Mem used{{end}}</span><span class="qval">{{.MemUse}}</span></div>
|
||||
<div class="qitem"><span class="qlabel">{{if eq .Lang "zh"}}已用磁盘{{else}}Disk used{{end}}</span><span class="qval">{{.DiskUsed}}</span></div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user