mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
web: added update notification on web-ui
Signed-off-by: Ejub Sabic <ejub1946@outlook.com>
This commit is contained in:
@@ -95,7 +95,7 @@ submodule infix-system-software {
|
||||
|
||||
container check-update {
|
||||
description
|
||||
"Policy for automatic firmware update checks.
|
||||
"Policy for automatic software update checks.
|
||||
|
||||
When 'enabled' and 'schedule' references a schedule, the system
|
||||
checks the configured URL for a newer release on each occurrence
|
||||
|
||||
@@ -11,7 +11,9 @@ import (
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -199,15 +201,15 @@ type hardwareWrapper struct {
|
||||
}
|
||||
|
||||
type hwComponentJSON struct {
|
||||
Name string `json:"name"`
|
||||
Class string `json:"class"`
|
||||
Description string `json:"description"`
|
||||
Parent string `json:"parent"`
|
||||
MfgName string `json:"mfg-name"`
|
||||
ModelName string `json:"model-name"`
|
||||
SerialNum string `json:"serial-num"`
|
||||
HardwareRev string `json:"hardware-rev"`
|
||||
PhysAddress string `json:"infix-hardware:phys-address"`
|
||||
Name string `json:"name"`
|
||||
Class string `json:"class"`
|
||||
Description string `json:"description"`
|
||||
Parent string `json:"parent"`
|
||||
MfgName string `json:"mfg-name"`
|
||||
ModelName string `json:"model-name"`
|
||||
SerialNum string `json:"serial-num"`
|
||||
HardwareRev string `json:"hardware-rev"`
|
||||
PhysAddress string `json:"infix-hardware:phys-address"`
|
||||
WiFiRadio *wifiRadioHWJSON `json:"infix-hardware:wifi-radio"`
|
||||
GPSReceiver *struct{} `json:"infix-hardware:gps-receiver"`
|
||||
SensorData *struct {
|
||||
@@ -226,26 +228,26 @@ type hwComponentJSON struct {
|
||||
|
||||
type dashboardData struct {
|
||||
PageData
|
||||
Hostname string
|
||||
Contact string
|
||||
Location string
|
||||
OSName string
|
||||
OSVersion string
|
||||
Machine string
|
||||
CurrentTime string
|
||||
Software string
|
||||
Uptime string
|
||||
MemTotal int64
|
||||
MemUsed int64
|
||||
MemPercent int
|
||||
MemClass string
|
||||
Load1 string
|
||||
Load5 string
|
||||
Load15 string
|
||||
CPUClass string
|
||||
Disks []diskEntry
|
||||
Board boardInfo
|
||||
KeyVitals []sensorEntry // Overview's at-a-glance subset: CPU/SoC + wifi-radio temperatures and fan RPMs. Status > Hardware has the full inventory.
|
||||
Hostname string
|
||||
Contact string
|
||||
Location string
|
||||
OSName string
|
||||
OSVersion string
|
||||
Machine string
|
||||
CurrentTime string
|
||||
Software string
|
||||
Uptime string
|
||||
MemTotal int64
|
||||
MemUsed int64
|
||||
MemPercent int
|
||||
MemClass string
|
||||
Load1 string
|
||||
Load5 string
|
||||
Load15 string
|
||||
CPUClass string
|
||||
Disks []diskEntry
|
||||
Board boardInfo
|
||||
KeyVitals []sensorEntry // Overview's at-a-glance subset: CPU/SoC + wifi-radio temperatures and fan RPMs. Status > Hardware has the full inventory.
|
||||
// Connectivity card.
|
||||
Gateways []gatewayEntry
|
||||
InternetProbe string // address pinged for the Internet reachability row
|
||||
@@ -254,7 +256,11 @@ type dashboardData struct {
|
||||
NTPSync string // "" / the selected NTP source address
|
||||
// Addresses card.
|
||||
Addresses []ifaceAddrEntry
|
||||
Error string
|
||||
// Software-update banner — shown only when an update is available.
|
||||
UpdateAvailable bool
|
||||
UpdateMessage string // verbatim CLI/login-banner notice
|
||||
UpdateURL string // release URL extracted from the notice
|
||||
Error string
|
||||
}
|
||||
|
||||
// gatewayEntry is a default route's next-hop.
|
||||
@@ -305,6 +311,31 @@ type diskEntry struct {
|
||||
// reachability row — a well-known, stable anycast resolver.
|
||||
const internetProbe = "1.1.1.1"
|
||||
|
||||
// updateNoticeFile holds the software-update notice written by
|
||||
// /usr/sbin/infix-check-update — the same text the CLI shows at login.
|
||||
// A package var so tests can point it elsewhere.
|
||||
var updateNoticeFile = "/run/infix-update"
|
||||
|
||||
var updateURLRe = regexp.MustCompile(`https?://\S+`)
|
||||
|
||||
// readUpdateNotice returns the software-update message verbatim (as shown in
|
||||
// the CLI login banner) and the release URL extracted from it. Both are empty
|
||||
// when no update is pending or the notice file is absent/empty.
|
||||
func readUpdateNotice() (msg, url string) {
|
||||
b, err := os.ReadFile(updateNoticeFile)
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
msg = strings.TrimSpace(string(b))
|
||||
if msg == "" {
|
||||
return "", ""
|
||||
}
|
||||
if u := updateURLRe.FindString(msg); u != "" {
|
||||
url = strings.TrimRight(u, ").,;")
|
||||
}
|
||||
return msg, url
|
||||
}
|
||||
|
||||
// DashboardHandler serves the main dashboard page.
|
||||
type DashboardHandler struct {
|
||||
Template *template.Template
|
||||
@@ -501,6 +532,14 @@ func (h *DashboardHandler) Index(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
data.Addresses = ifaceAddresses(ifaces)
|
||||
|
||||
// Software-update banner: surfaces the same notice as the CLI login
|
||||
// banner (written by infix-check-update to the notice file).
|
||||
if msg, url := readUpdateNotice(); msg != "" {
|
||||
data.UpdateAvailable = true
|
||||
data.UpdateMessage = msg
|
||||
data.UpdateURL = url
|
||||
}
|
||||
|
||||
tmplName := "dashboard.html"
|
||||
if r.Header.Get("HX-Request") == "true" {
|
||||
tmplName = "content"
|
||||
|
||||
@@ -579,6 +579,43 @@ details[open].nav-group-top > summary.nav-group-summary-top::before {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
/* Dashboard software-update banner — amber "attention" treatment, bright
|
||||
and theme-aware, but built from the same card primitives as everything
|
||||
else. Spans the full info-grid width and sits at the top. */
|
||||
.update-card {
|
||||
border-color: var(--warning-border);
|
||||
background: var(--warning-bg);
|
||||
box-shadow: 0 0 0 1px var(--warning-border), var(--shadow-sm);
|
||||
}
|
||||
.update-card .update-card-header {
|
||||
background: var(--warning);
|
||||
color: #fff;
|
||||
border-bottom-color: var(--warning-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.update-card .update-card-icon {
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
.update-card .update-card-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.update-card .update-card-msg {
|
||||
margin: 0;
|
||||
color: var(--warning-fg);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.update-card .update-card-action {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Tables
|
||||
|
||||
@@ -8,6 +8,21 @@
|
||||
{{end}}
|
||||
|
||||
<section class="info-grid">
|
||||
{{if .UpdateAvailable}}
|
||||
<div class="info-card info-grid-full update-card">
|
||||
<div class="card-header update-card-header">
|
||||
<span class="update-card-icon" aria-hidden="true">⬆</span> Software Update Available
|
||||
</div>
|
||||
<div class="card-body update-card-body">
|
||||
<p class="update-card-msg">{{.UpdateMessage}}</p>
|
||||
{{if .UpdateURL}}
|
||||
<a href="{{.UpdateURL}}" target="_blank" rel="noopener noreferrer"
|
||||
class="btn btn-primary update-card-action">View release →</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="info-card">
|
||||
<div class="card-header">System Information</div>
|
||||
<div class="card-body" style="padding:0">
|
||||
|
||||
Reference in New Issue
Block a user