aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--_www/index.tmpl15
-rw-r--r--cmd/lilin/main.go46
-rw-r--r--lilin.go8
-rw-r--r--lilin_test.go14
-rw-r--r--reports.go10
-rw-r--r--server.go67
-rw-r--r--server_options.go4
-rw-r--r--service_report.go11
-rw-r--r--worker.go13
9 files changed, 138 insertions, 50 deletions
diff --git a/_www/index.tmpl b/_www/index.tmpl
new file mode 100644
index 0000000..250843d
--- /dev/null
+++ b/_www/index.tmpl
@@ -0,0 +1,15 @@
+<!doctype html>
+<html lang="en-GB">
+ <head>
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width" />
+ <title>{{.Title}}</title>
+ </head>
+ <body>
+ {{range .Services}}
+ <div>
+ <div>{{.Name}}</div>
+ </div>
+ {{end}}
+ </body>
+</html>
diff --git a/cmd/lilin/main.go b/cmd/lilin/main.go
new file mode 100644
index 0000000..21c40ec
--- /dev/null
+++ b/cmd/lilin/main.go
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: 2025 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: GPL-3.0-only
+
+package main
+
+import (
+ "context"
+ "log"
+ "os"
+ "os/signal"
+ "time"
+
+ "git.sr.ht/~shulhan/lilin"
+)
+
+func main() {
+ var serverOpts = lilin.ServerOptions{
+ BaseDir: `testdata`,
+ IsDevelopment: true,
+ }
+
+ var server *lilin.Server
+ var err error
+ server, err = lilin.NewServer(serverOpts)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ go func() {
+ err = server.ListenAndServe()
+ if err != nil {
+ log.Fatal(err)
+ }
+ }()
+
+ var sigq = make(chan os.Signal, 1)
+ signal.Notify(sigq, os.Interrupt)
+ <-sigq
+
+ var ctx = context.Background()
+ var timeout = 5 * time.Second
+ var cancelf context.CancelFunc
+ ctx, cancelf = context.WithTimeout(ctx, timeout)
+ defer cancelf()
+ _ = server.Shutdown(ctx)
+}
diff --git a/lilin.go b/lilin.go
index 1fa04c1..1febf86 100644
--- a/lilin.go
+++ b/lilin.go
@@ -3,7 +3,13 @@
package lilin
-import "time"
+import (
+ "embed"
+ "time"
+)
// defTimeout define default timeout for service and client connection.
const defTimeout = 5 * time.Second
+
+//go:embed _www
+var wwwFS embed.FS
diff --git a/lilin_test.go b/lilin_test.go
index fe951a0..3e1899d 100644
--- a/lilin_test.go
+++ b/lilin_test.go
@@ -14,7 +14,6 @@ import (
"git.sr.ht/~shulhan/lilin"
"git.sr.ht/~shulhan/lilin/internal"
"git.sr.ht/~shulhan/pakakeh.go/lib/net"
- "git.sr.ht/~shulhan/pakakeh.go/lib/test"
)
var client *lilin.Client
@@ -104,16 +103,3 @@ func dummyHTTPService() {
log.Fatal(err)
}
}
-
-func TestServer_handleServicesSummary(t *testing.T) {
- var gotSummary []lilin.Service
- var err error
-
- gotSummary, err = client.ServicesSummary()
- if err != nil {
- t.Fatal(err)
- }
-
- var expSummary = []lilin.Service{{}, {}, {}}
- test.Assert(t, `ServicesSummary`, expSummary, gotSummary)
-}
diff --git a/reports.go b/reports.go
new file mode 100644
index 0000000..1ffbd43
--- /dev/null
+++ b/reports.go
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2025 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: GPL-3.0-only
+
+package lilin
+
+// Reports contains the report for all services.
+type Reports struct {
+ Services map[string]ServiceReport
+ Title string
+}
diff --git a/server.go b/server.go
index bd272ec..620c36f 100644
--- a/server.go
+++ b/server.go
@@ -4,13 +4,13 @@
package lilin
import (
+ "bytes"
"context"
- "encoding/json"
"fmt"
+ "html/template"
+ "log"
"net/http"
"time"
-
- libhttp "git.sr.ht/~shulhan/pakakeh.go/lib/http"
)
const (
@@ -25,13 +25,21 @@ type Server struct {
worker *worker
Options ServerOptions
+ Reports Reports
+
+ pageIndexTmpl *template.Template
+ pageIndexBody bytes.Buffer
}
+// NewServer create new server to serve the content of _www and HTTP APIs.
func NewServer(opts ServerOptions) (srv *Server, err error) {
var logp = `NewServer`
srv = &Server{
Options: opts,
+ Reports: Reports{
+ Services: map[string]ServiceReport{},
+ },
}
err = srv.Options.init()
@@ -44,8 +52,23 @@ func NewServer(opts ServerOptions) (srv *Server, err error) {
return nil, fmt.Errorf(`%s: %w`, logp, err)
}
+ for name := range srv.worker.Services {
+ srv.Reports.Services[name] = ServiceReport{
+ Name: name,
+ }
+ }
+
+ srv.pageIndexTmpl, err = template.ParseFS(wwwFS, `_www/index.tmpl`)
+ if err != nil {
+ return nil, err
+ }
+ err = srv.pageIndexTmpl.Execute(&srv.pageIndexBody, &srv.Reports)
+ if err != nil {
+ return nil, err
+ }
+
var mux = http.NewServeMux()
- mux.HandleFunc(`GET `+pathAPIServicesSummary, srv.handleServicesSummary)
+ mux.Handle(`GET /`, srv)
srv.httpd = &http.Server{
Addr: srv.Options.Address,
@@ -58,6 +81,8 @@ func NewServer(opts ServerOptions) (srv *Server, err error) {
// ListenAndServe start handling request on incoming connections.
func (srv *Server) ListenAndServe() (err error) {
+ log.Printf(`lilin: starting HTTP server at %s`, srv.Options.Address)
+
err = srv.httpd.ListenAndServe()
if err != nil {
return fmt.Errorf(`ListenAndServe: %w`, err)
@@ -75,27 +100,25 @@ func (srv *Server) Shutdown(ctx context.Context) (err error) {
return nil
}
-func (srv *Server) handleServicesSummary(resw http.ResponseWriter, req *http.Request) {
- var summary = srv.worker.summary()
- var resp = libhttp.EndpointResponse{
- Data: summary,
- }
- var respBody []byte
+func (srv *Server) ServeHTTP(resw http.ResponseWriter, req *http.Request) {
var err error
- respBody, err = json.Marshal(resp)
- if err != nil {
- goto fail
+ switch req.URL.Path {
+ case `/`, `/index.html`:
+ _, err = resw.Write(srv.pageIndexBody.Bytes())
+ if err != nil {
+ srv.internalError(resw, err)
+ }
+ return
}
- resw.WriteHeader(http.StatusOK)
- resw.Write(respBody)
- return
-fail:
+ http.Redirect(resw, req, `/`, http.StatusSeeOther)
+}
+
+func (srv *Server) internalError(resw http.ResponseWriter, err error) {
resw.WriteHeader(http.StatusInternalServerError)
- respBody = []byte(fmt.Sprintf(responseTemplateJSON,
- http.StatusInternalServerError,
- `ERR_INTERNAL`,
- err.Error()))
- resw.Write(respBody)
+ _, err = resw.Write([]byte(err.Error()))
+ if err != nil {
+ log.Println(`internalError:`, err.Error())
+ }
}
diff --git a/server_options.go b/server_options.go
index 5197ffd..4a033fc 100644
--- a/server_options.go
+++ b/server_options.go
@@ -24,6 +24,10 @@ type ServerOptions struct {
// The address to listen for HTTP server and APIs.
Address string `ini:"server::address"`
+
+ // IsDevelopment run the server in development mode with direct access
+ // to file system in _www instead of using [embed.FS].
+ IsDevelopment bool
}
// init initialize the server by reading the configuration from
diff --git a/service_report.go b/service_report.go
new file mode 100644
index 0000000..2627bc2
--- /dev/null
+++ b/service_report.go
@@ -0,0 +1,11 @@
+// SPDX-FileCopyrightText: 2025 M. Shulhan <ms@kilabit.info>
+// SPDX-License-Identifier: GPL-3.0-only
+
+package lilin
+
+// ServiceReport contains the scan report for single service.
+type ServiceReport struct {
+ Name string
+ Last ScanReport
+ History []ScanReport
+}
diff --git a/worker.go b/worker.go
index d86852d..f58363c 100644
--- a/worker.go
+++ b/worker.go
@@ -4,10 +4,8 @@
package lilin
import (
- "maps"
"os"
"path/filepath"
- "slices"
"strings"
"sync"
@@ -85,14 +83,3 @@ func (wrk *worker) loadServiceDir(configDir string) (err error) {
}
return nil
}
-
-// summary return all services status ordered by name.
-func (wrk *worker) summary() (list []Service) {
- wrk.Lock()
- var keys = slices.Sorted(maps.Keys(wrk.Services))
- for _, name := range keys {
- list = append(list, *wrk.Services[name])
- }
- wrk.Unlock()
- return list
-}