aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2025-07-31 00:27:20 +0700
committerShulhan <ms@kilabit.info>2025-07-31 00:27:20 +0700
commitb6fec02b55a4227b6ecb47c2bc40a5a025af2b8a (patch)
tree36956ce1c9c51027f6af3982c3c6add0cc596f80
parent0537c5e094c7b6e5ff376ccdf0dba80adf5c4342 (diff)
downloadlilin-b6fec02b55a4227b6ecb47c2bc40a5a025af2b8a.tar.xz
all: refactoring web page to render using template
Instead of using HTML and JavaScript, generate the web page using template. This minimize number of works we do in the future (creating client in JavaScript and HTTP APIs).
-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
-}