Remove the RepoISO integration; keep generic support for shares of other services
build / build (push) Successful in 1m5s
build / build (push) Successful in 1m5s
RepoISO now manages the NAS itself, so nasmon no longer polls it (settings field, dashboard card, menu link and repoiso:* alerts are gone). Drop-in SMB includes, read-only listing and keeping smbd/nfs-server running for other software's shares stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"nasmon/internal/shares"
|
||||
)
|
||||
|
||||
func TestSharesPageListsExternal(t *testing.T) {
|
||||
srv, _, _ := newTestServer(t)
|
||||
f := &shareRunner{}
|
||||
withShareRunner(srv, f)
|
||||
dir := t.TempDir()
|
||||
smbDir, nfsDir := filepath.Join(dir, "smb.conf.d"), filepath.Join(dir, "exports.d")
|
||||
_ = os.MkdirAll(smbDir, 0o755)
|
||||
_ = os.MkdirAll(nfsDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(smbDir, "repoiso.conf"), []byte("[iso-pve]\n path = /tank/iso/export/pve\n"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(nfsDir, "repoiso.exports"), []byte("\"/tank/iso/export/pve\" 10.10.0.0/24(ro)\n"), 0o644)
|
||||
srv.shm.IncludeDir, srv.shm.ExportsDir, srv.shm.Exports = smbDir, nfsDir, filepath.Join(nfsDir, shares.DefaultExports)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.ServeHTTP(rec, httptest.NewRequest("GET", "/shares", nil))
|
||||
body := rec.Body.String()
|
||||
for _, want := range []string{`id="external"`, "iso-pve", "/tank/iso/export/pve", "10.10.0.0/24(ro)", ">repoiso<"} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("shares page lacks %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,7 +465,6 @@ func formToSettings(r *http.Request, old config.Settings) config.Settings {
|
||||
c.SelfTests.ShortDays = num("st_short_days", old.SelfTests.ShortDays)
|
||||
c.SelfTests.LongDays = num("st_long_days", old.SelfTests.LongDays)
|
||||
c.SelfTests.Hour = num("st_hour", old.SelfTests.Hour)
|
||||
c.Integrations.RepoISOURL = strings.TrimSpace(r.FormValue("repoiso_url"))
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"nasmon/internal/collect"
|
||||
"nasmon/internal/shares"
|
||||
)
|
||||
|
||||
func TestBrowserURL(t *testing.T) {
|
||||
cases := []struct{ cfg, host, want string }{
|
||||
{"", "nas:8090", ""},
|
||||
{"http://127.0.0.1", "10.10.0.129:8090", "http://10.10.0.129"},
|
||||
{"http://localhost:8080/", "nas.lan:8090", "http://nas.lan:8080/"},
|
||||
{"https://repo.example.com", "10.0.0.1:8090", "https://repo.example.com"},
|
||||
{"http://127.0.0.1", "[fe80::1]:8090", "http://[fe80::1]"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := browserURL(c.cfg, c.host); got != c.want {
|
||||
t.Errorf("browserURL(%q, %q) = %q, want %q", c.cfg, c.host, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoISOIntegrationUI(t *testing.T) {
|
||||
srv, sc, _ := newTestServer(t)
|
||||
// settings: save the URL, menu link appears
|
||||
rec := postForm(srv, "/settings", url.Values{"smtp_port": {"587"}, "smtp_security": {"starttls"},
|
||||
"smart_interval": {"30"}, "repoiso_url": {"http://127.0.0.1/"}})
|
||||
if rec.Code != 200 || srv.cfg.Get().Integrations.RepoISOURL != "http://127.0.0.1" {
|
||||
t.Fatalf("settings not saved: %d %q", rec.Code, srv.cfg.Get().Integrations.RepoISOURL)
|
||||
}
|
||||
rec = postForm(srv, "/settings", url.Values{"smtp_port": {"587"}, "smtp_security": {"starttls"},
|
||||
"smart_interval": {"30"}, "repoiso_url": {"ftp://x"}})
|
||||
if !strings.Contains(rec.Body.String(), "RepoISO URL must start") {
|
||||
t.Errorf("bad URL accepted")
|
||||
}
|
||||
|
||||
st := sc.Status()
|
||||
st.RepoISO = &collect.RepoISO{URL: "http://127.0.0.1", Reachable: true, WorkerAlive: true, Version: "0.1.3",
|
||||
Images: 61, ImagesBytes: 350 << 30, Publications: 2, JobsFailed24h: 1, Exports: 1}
|
||||
sc.SetStatus(st)
|
||||
rec = httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.Host = "10.10.0.129:8090"
|
||||
srv.ServeHTTP(rec, req)
|
||||
body := rec.Body.String()
|
||||
for _, want := range []string{`href="http://10.10.0.129"`, "RepoISO ↗", `id="repoiso"`, "v0.1.3", "1 failed in 24 h"} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("dashboard lacks %q", want)
|
||||
}
|
||||
}
|
||||
|
||||
st.RepoISO = &collect.RepoISO{URL: "http://127.0.0.1", Error: "connection refused"}
|
||||
sc.SetStatus(st)
|
||||
rec = httptest.NewRecorder()
|
||||
srv.ServeHTTP(rec, httptest.NewRequest("GET", "/", nil))
|
||||
if !strings.Contains(rec.Body.String(), "not responding") {
|
||||
t.Errorf("unreachable RepoISO not shown")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSharesPageListsExternal(t *testing.T) {
|
||||
srv, _, _ := newTestServer(t)
|
||||
f := &shareRunner{}
|
||||
withShareRunner(srv, f)
|
||||
dir := t.TempDir()
|
||||
smbDir, nfsDir := filepath.Join(dir, "smb.conf.d"), filepath.Join(dir, "exports.d")
|
||||
_ = os.MkdirAll(smbDir, 0o755)
|
||||
_ = os.MkdirAll(nfsDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(smbDir, "repoiso.conf"), []byte("[iso-pve]\n path = /tank/iso/export/pve\n"), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(nfsDir, "repoiso.exports"), []byte("\"/tank/iso/export/pve\" 10.10.0.0/24(ro)\n"), 0o644)
|
||||
srv.shm.IncludeDir, srv.shm.ExportsDir, srv.shm.Exports = smbDir, nfsDir, filepath.Join(nfsDir, shares.DefaultExports)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.ServeHTTP(rec, httptest.NewRequest("GET", "/shares", nil))
|
||||
body := rec.Body.String()
|
||||
for _, want := range []string{`id="external"`, "iso-pve", "/tank/iso/export/pve", "10.10.0.0/24(ro)", "RepoISO"} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("shares page lacks %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,7 @@ import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -107,7 +105,6 @@ type pageData struct {
|
||||
Data any
|
||||
Flash string
|
||||
Error string
|
||||
RepoISO string // browser URL of the RepoISO portal ("" = integration off)
|
||||
}
|
||||
|
||||
// render writes a full page, or only the "content" block when ?partial=1 is
|
||||
@@ -115,7 +112,6 @@ type pageData struct {
|
||||
func (s *Server) render(w http.ResponseWriter, r *http.Request, name string, pd pageData) {
|
||||
pd.Page = name
|
||||
pd.Partial = r.URL.Query().Get("partial") == "1"
|
||||
pd.RepoISO = browserURL(s.cfg.Get().Integrations.RepoISOURL, r.Host)
|
||||
if pd.Alerts == 0 {
|
||||
pd.Alerts = len(s.sch.Status().Alerts)
|
||||
}
|
||||
@@ -292,33 +288,3 @@ func sparkline(vals []float64, max float64, class string) template.HTML {
|
||||
`<svg class="spark %s" viewBox="0 0 %d %d" preserveAspectRatio="none"><polygon points="0,%d %s %d,%d"/><polyline points="%s"/></svg>`,
|
||||
class, int(w), int(h), int(h), p, int(w), int(h), p))
|
||||
}
|
||||
|
||||
// browserURL turns the configured RepoISO address into one the user's browser
|
||||
// can open: a loopback host is replaced by the host the browser used for nasmon.
|
||||
func browserURL(cfg, reqHost string) string {
|
||||
if cfg == "" {
|
||||
return ""
|
||||
}
|
||||
u, err := url.Parse(cfg)
|
||||
if err != nil || u.Host == "" {
|
||||
return cfg
|
||||
}
|
||||
switch u.Hostname() {
|
||||
case "127.0.0.1", "localhost", "::1":
|
||||
host := reqHost
|
||||
if h, _, err := net.SplitHostPort(reqHost); err == nil {
|
||||
host = h
|
||||
}
|
||||
if host == "" {
|
||||
return cfg
|
||||
}
|
||||
if strings.Contains(host, ":") {
|
||||
host = "[" + host + "]"
|
||||
}
|
||||
if p := u.Port(); p != "" {
|
||||
host += ":" + p
|
||||
}
|
||||
u.Host = host
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
@@ -104,26 +104,6 @@
|
||||
</table></div>
|
||||
</section>
|
||||
|
||||
{{with .S.RepoISO}}
|
||||
<section class="card" id="repoiso">
|
||||
<h2>RepoISO {{if $.RepoISO}}<a class="muted small" href="{{$.RepoISO}}" target="_blank" rel="noopener">open ↗</a>{{end}}
|
||||
{{if not .Reachable}}<span class="badge crit">not responding</span>
|
||||
{{else if not .WorkerAlive}}<span class="badge warn">worker stopped</span>
|
||||
{{else}}<span class="badge ok">running</span>{{end}}
|
||||
{{if .Version}}<span class="muted small">v{{.Version}}</span>{{end}}</h2>
|
||||
{{if not .Reachable}}<div class="flash crit">{{.URL}}/api/integration/status: {{.Error}}</div>{{else}}
|
||||
<div class="tablewrap"><table class="compact">
|
||||
<tbody>
|
||||
<tr><td class="muted">Images</td><td class="num">{{.Images}}</td><td>{{bytes .ImagesBytes}}{{if .ImagesPending}} · <span class="muted">{{.ImagesPending}} being checked</span>{{end}}{{if .ImagesError}} · <span class="crit">{{.ImagesError}} with errors</span>{{end}}</td></tr>
|
||||
<tr><td class="muted">Publications</td><td class="num">{{.Publications}}</td><td>{{if .PublicationsError}}<span class="badge warn">{{.PublicationsError}} not mounted</span>{{else}}<span class="muted">HTTP repositories / file trees</span>{{end}}</td></tr>
|
||||
<tr><td class="muted">Jobs</td><td class="num">{{.JobsActive}}</td><td>{{if .JobsActive}}active{{else}}<span class="muted">idle</span>{{end}}{{if .JobsFailed24h}} · <span class="badge warn">{{.JobsFailed24h}} failed in 24 h</span>{{end}}</td></tr>
|
||||
<tr><td class="muted">Proxmox exports</td><td class="num">{{.Exports}}</td><td class="muted">NFS / SMB shares — listed on <a href="/shares#external">Shares</a></td></tr>
|
||||
<tr><td class="muted">Worker</td><td></td><td class="small">{{if .WorkerHeartbeat}}last heartbeat {{.WorkerHeartbeat}}{{else}}no heartbeat yet{{end}}</td></tr>
|
||||
</tbody>
|
||||
</table></div>{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<section class="card">
|
||||
<h2>Datasets</h2>
|
||||
<div class="tablewrap"><table>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
<a href="/shares" {{if eq .Page "shares"}}class="on"{{end}}>Shares</a>
|
||||
<a href="/alerts" {{if eq .Page "alerts"}}class="on"{{end}}>Alerts{{if .Alerts}} <span class="badge crit">{{.Alerts}}</span>{{end}}</a>
|
||||
<a href="/settings" {{if eq .Page "settings"}}class="on"{{end}}>Settings</a>
|
||||
{{if .RepoISO}}<a href="{{.RepoISO}}" target="_blank" rel="noopener" title="ISO library, repositories, Proxmox exports">RepoISO ↗</a>{{end}}
|
||||
</nav>
|
||||
<span class="refresh" id="refresh-state" title="auto-refresh"></span>
|
||||
</header>
|
||||
|
||||
@@ -67,17 +67,6 @@
|
||||
<p class="muted small">Tests start only for disks that are awake. A long test on a large HDD takes many hours; disk stays usable.</p>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Integrations</h2>
|
||||
<div class="form-grid">
|
||||
<label class="wide">RepoISO URL <input name="repoiso_url" value="{{$c.Integrations.RepoISOURL}}" placeholder="http://127.0.0.1"></label>
|
||||
</div>
|
||||
<p class="muted small">RepoISO (ISO library, apt/dnf repositories, Proxmox ISO exports) on this host. nasmon polls
|
||||
<span class="mono"><url>/api/integration/status</span> every minute, shows a dashboard card, alerts when the portal
|
||||
or its worker is down, and adds a menu link. Use the local address (<span class="mono">http://127.0.0.1</span>) — the
|
||||
status endpoint only answers on loopback. Empty disables the integration.</p>
|
||||
</section>
|
||||
|
||||
<div class="actions">
|
||||
<button type="submit" name="action" value="save">Save</button>
|
||||
<button type="submit" name="action" value="test" class="secondary">Save & send test e-mail</button>
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
<tbody>
|
||||
{{range .External}}<tr>
|
||||
<td><span class="badge">{{if eq .Kind "smb"}}SMB{{else}}NFS{{end}}</span></td>
|
||||
<td>{{if eq .Source "repoiso"}}{{if $.RepoISO}}<a href="{{$.RepoISO}}/proxmox" target="_blank" rel="noopener">RepoISO</a>{{else}}RepoISO{{end}}{{else}}{{.Source}}{{end}}</td>
|
||||
<td class="mono">{{.Source}}</td>
|
||||
<td class="mono">{{if .Name}}\\{{$.Data.Host}}\{{.Name}}{{else}}—{{end}}</td>
|
||||
<td class="mono small">{{.Path}}</td>
|
||||
<td class="mono small">{{.Clients}}</td>
|
||||
|
||||
Reference in New Issue
Block a user