Add read-only OS package update status (apt) on the About page
New system package checks `apt list --upgradable` against the current package index (no apt update triggered) and the reboot-required marker file. Read-only - never installs or upgrades anything. Shown as a card on the About page with a manual "Check now" refresh. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
1d080904b0
commit
bdcf1ec60c
@@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/ngoduykhanh/wireguard-ui/firewall"
|
"github.com/ngoduykhanh/wireguard-ui/firewall"
|
||||||
"github.com/ngoduykhanh/wireguard-ui/model"
|
"github.com/ngoduykhanh/wireguard-ui/model"
|
||||||
"github.com/ngoduykhanh/wireguard-ui/store"
|
"github.com/ngoduykhanh/wireguard-ui/store"
|
||||||
|
"github.com/ngoduykhanh/wireguard-ui/system"
|
||||||
"github.com/ngoduykhanh/wireguard-ui/telegram"
|
"github.com/ngoduykhanh/wireguard-ui/telegram"
|
||||||
"github.com/ngoduykhanh/wireguard-ui/util"
|
"github.com/ngoduykhanh/wireguard-ui/util"
|
||||||
)
|
)
|
||||||
@@ -1770,3 +1771,11 @@ func AboutPage() echo.HandlerFunc {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSystemUpdateStatus reports pending OS package updates (Debian/Ubuntu
|
||||||
|
// via apt), read-only - nothing is installed or upgraded.
|
||||||
|
func GetSystemUpdateStatus() echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
return c.JSON(http.StatusOK, system.CheckAptUpdates())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ func main() {
|
|||||||
|
|
||||||
app.GET(util.BasePath+"/test-hash", handler.GetHashesChanges(db), handler.ValidSession)
|
app.GET(util.BasePath+"/test-hash", handler.GetHashesChanges(db), handler.ValidSession)
|
||||||
app.GET(util.BasePath+"/about", handler.AboutPage())
|
app.GET(util.BasePath+"/about", handler.AboutPage())
|
||||||
|
app.GET(util.BasePath+"/system/update-status", handler.GetSystemUpdateStatus(), handler.ValidSession, handler.NeedsAdmin)
|
||||||
app.GET(util.BasePath+"/_health", handler.Health())
|
app.GET(util.BasePath+"/_health", handler.Health())
|
||||||
app.GET(util.BasePath+"/favicon", handler.Favicon())
|
app.GET(util.BasePath+"/favicon", handler.Favicon())
|
||||||
app.POST(util.BasePath+"/new-client", handler.NewClient(db), handler.ValidSession, handler.ContentTypeJson)
|
app.POST(util.BasePath+"/new-client", handler.NewClient(db), handler.ValidSession, handler.ContentTypeJson)
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Package system checks the host OS for pending package updates. It never
|
||||||
|
// installs or upgrades anything - read-only status only, using the
|
||||||
|
// existing apt package index (no "apt update" is run automatically, since
|
||||||
|
// that touches network/package state on every page load).
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateStatus summarizes pending OS package updates on a Debian/Ubuntu host.
|
||||||
|
type UpdateStatus struct {
|
||||||
|
Supported bool `json:"supported"` // false if apt isn't available (e.g. non-Debian host)
|
||||||
|
UpgradableCount int `json:"upgradable_count"`
|
||||||
|
Packages []string `json:"packages"`
|
||||||
|
RebootRequired bool `json:"reboot_required"`
|
||||||
|
CheckedAt time.Time `json:"checked_at"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAptUpdates lists upgradable packages from the current apt index
|
||||||
|
// (`apt list --upgradable`) without refreshing it, and checks for the
|
||||||
|
// standard Debian/Ubuntu reboot-required marker file.
|
||||||
|
func CheckAptUpdates() UpdateStatus {
|
||||||
|
status := UpdateStatus{CheckedAt: time.Now().UTC()}
|
||||||
|
|
||||||
|
if _, err := exec.LookPath("apt"); err != nil {
|
||||||
|
status.Supported = false
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
status.Supported = true
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "apt", "list", "--upgradable")
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
status.Error = "cannot list upgradable packages: " + err.Error()
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" || strings.HasPrefix(line, "Listing...") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
status.Packages = append(status.Packages, line)
|
||||||
|
}
|
||||||
|
status.UpgradableCount = len(status.Packages)
|
||||||
|
|
||||||
|
if _, err := os.Stat("/var/run/reboot-required"); err == nil {
|
||||||
|
status.RebootRequired = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return status
|
||||||
|
}
|
||||||
@@ -45,6 +45,25 @@ About
|
|||||||
</div>
|
</div>
|
||||||
<!-- /.card -->
|
<!-- /.card -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- right column -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card card-info">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">System Updates (OS packages)</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted">Read-only check against the current apt package index. Nothing is installed automatically.</p>
|
||||||
|
<div id="_update_status_summary">Checking...</div>
|
||||||
|
<div id="_update_status_reboot" style="display:none;" class="text-danger mt-2">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i> A reboot is required to apply already-installed updates.
|
||||||
|
</div>
|
||||||
|
<pre id="_update_status_packages" style="max-height: 30vh; overflow:auto; margin-top: 10px;"></pre>
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn_check_updates">Check now</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /.card -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.row -->
|
<!-- /.row -->
|
||||||
</div>
|
</div>
|
||||||
@@ -52,4 +71,47 @@ About
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "bottom_js"}}
|
{{ define "bottom_js"}}
|
||||||
|
<script>
|
||||||
|
function loadUpdateStatus() {
|
||||||
|
$("#_update_status_summary").text("Checking...");
|
||||||
|
$("#_update_status_packages").text("");
|
||||||
|
$("#_update_status_reboot").hide();
|
||||||
|
$.ajax({
|
||||||
|
cache: false,
|
||||||
|
method: 'GET',
|
||||||
|
url: '{{.basePath}}/system/update-status',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (data) {
|
||||||
|
if (!data.supported) {
|
||||||
|
$("#_update_status_summary").text("Not supported on this host (apt not found).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.error) {
|
||||||
|
$("#_update_status_summary").text("Error: " + data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.upgradable_count === 0) {
|
||||||
|
$("#_update_status_summary").html('<span class="text-success"><i class="fas fa-check-circle"></i> System is up to date.</span>');
|
||||||
|
} else {
|
||||||
|
$("#_update_status_summary").html('<span class="text-warning"><i class="fas fa-arrow-circle-up"></i> ' +
|
||||||
|
data.upgradable_count + ' package(s) can be upgraded.</span>');
|
||||||
|
$("#_update_status_packages").text((data.packages || []).join("\n"));
|
||||||
|
}
|
||||||
|
if (data.reboot_required) {
|
||||||
|
$("#_update_status_reboot").show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
$("#_update_status_summary").text("Could not check update status.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
loadUpdateStatus();
|
||||||
|
$("#btn_check_updates").click(function () {
|
||||||
|
loadUpdateStatus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user