add web gui
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,7 @@
|
|||||||
/cli
|
/cli
|
||||||
/server
|
/server
|
||||||
|
/web
|
||||||
/env/
|
/env/
|
||||||
/build/
|
/build/
|
||||||
|
*.exe
|
||||||
|
/config.json
|
||||||
22
build.sh
22
build.sh
@@ -55,6 +55,28 @@ for platform in "${platforms[@]}"; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# WEB
|
||||||
|
|
||||||
|
platforms=("linux/amd64" "linux/arm64" "linux/riscv64" "linux/ppc64le")
|
||||||
|
|
||||||
|
for platform in "${platforms[@]}"; do
|
||||||
|
echo "* Compiling web server for $platform..."
|
||||||
|
platform_split=(${platform//\// })
|
||||||
|
|
||||||
|
EXT=""
|
||||||
|
if [ "${platform_split[0]}" == "windows" ]; then
|
||||||
|
EXT=.exe
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$MAKE_PACKAGE" == "true" ]; then
|
||||||
|
CGO_ENABLED=0 GOOS=${platform_split[0]} GOARCH=${platform_split[1]} go build -o build/cloudsave_web$EXT -a ./cmd/web
|
||||||
|
tar -czf build/server_${platform_split[0]}_${platform_split[1]}.tar.gz build/cloudsave_web$EXT
|
||||||
|
rm build/cloudsave_web$EXT
|
||||||
|
else
|
||||||
|
CGO_ENABLED=0 GOOS=${platform_split[0]} GOARCH=${platform_split[1]} go build -o build/cloudsave_web_${platform_split[0]}_${platform_split[1]}$EXT -a ./cmd/web
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
## CLIENT
|
## CLIENT
|
||||||
|
|
||||||
platforms=("windows/amd64" "windows/arm64" "darwin/amd64" "darwin/arm64" "linux/amd64" "linux/arm64")
|
platforms=("windows/amd64" "windows/arm64" "darwin/amd64" "darwin/arm64" "linux/amd64" "linux/arm64")
|
||||||
|
|||||||
8
cmd/web/config.template.json
Normal file
8
cmd/web/config.template.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"port": 8181
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"url": "http://localhost:8080"
|
||||||
|
}
|
||||||
|
}
|
||||||
40
cmd/web/server/config/config.go
Normal file
40
cmd/web/server/config/config.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Configuration struct {
|
||||||
|
Server ServerConfiguration `json:"server"`
|
||||||
|
Remote RemoteConfiguration `json:"remote"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerConfiguration struct {
|
||||||
|
Port int `json:"port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteConfiguration struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func Load(path string) (Configuration, error) {
|
||||||
|
f, err := os.OpenFile(path, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return Configuration{}, fmt.Errorf("failed to open configuration file: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
d := json.NewDecoder(f)
|
||||||
|
|
||||||
|
var c Configuration
|
||||||
|
err = d.Decode(&c)
|
||||||
|
if err != nil {
|
||||||
|
return Configuration{}, fmt.Errorf("failed to parse configuration file (%s): %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
63
cmd/web/server/middlewares.go
Normal file
63
cmd/web/server/middlewares.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func recoverMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
err := recover()
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicAuth implements a simple middleware handler for adding basic http auth to a route.
|
||||||
|
func BasicAuth(realm string, creds map[string]string) func(next http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user, pass, ok := r.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
basicAuthFailed(w, r, realm)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
credPass := creds[user]
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(credPass), []byte(pass)); err != nil {
|
||||||
|
basicAuthFailed(w, r, realm)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicAuthFailed(w http.ResponseWriter, r *http.Request, realm string) {
|
||||||
|
unauthorized(realm, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unauthorized(realm string, w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
_, err := w.Write([]byte(UnauthorizedErrorHTMLPage))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func internalServerError(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, err := w.Write([]byte(InternalServerErrorHTMLPage))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
248
cmd/web/server/server.go
Normal file
248
cmd/web/server/server.go
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cloudsave/cmd/web/server/config"
|
||||||
|
"cloudsave/pkg/constants"
|
||||||
|
"cloudsave/pkg/remote/client"
|
||||||
|
"cloudsave/pkg/repository"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
HTTPServer struct {
|
||||||
|
Server *http.Server
|
||||||
|
Config config.Configuration
|
||||||
|
Templates Templates
|
||||||
|
}
|
||||||
|
|
||||||
|
Templates struct {
|
||||||
|
Dashboard *template.Template
|
||||||
|
Detailled *template.Template
|
||||||
|
System *template.Template
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
DetaillePayload struct {
|
||||||
|
Version string
|
||||||
|
Save repository.Metadata
|
||||||
|
BackupMetadata []repository.Backup
|
||||||
|
Hash string
|
||||||
|
}
|
||||||
|
|
||||||
|
DashboardPayload struct {
|
||||||
|
Version string
|
||||||
|
Saves []repository.Metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemPayload struct {
|
||||||
|
Version string
|
||||||
|
Client client.Information
|
||||||
|
Server client.Information
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed templates/500.html
|
||||||
|
InternalServerErrorHTMLPage string
|
||||||
|
|
||||||
|
//go:embed templates/401.html
|
||||||
|
UnauthorizedErrorHTMLPage string
|
||||||
|
|
||||||
|
//go:embed templates/dashboard.html
|
||||||
|
DashboardHTMLPage string
|
||||||
|
|
||||||
|
//go:embed templates/detailled.html
|
||||||
|
DetailledHTMLPage string
|
||||||
|
|
||||||
|
//go:embed templates/information.html
|
||||||
|
SystemHTMLPage string
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewServer start the http server
|
||||||
|
func NewServer(c config.Configuration) *HTTPServer {
|
||||||
|
dashboardTemplate := template.New("dashboard")
|
||||||
|
dashboardTemplate.Parse(DashboardHTMLPage)
|
||||||
|
|
||||||
|
detailledTemplate := template.New("detailled")
|
||||||
|
detailledTemplate.Parse(DetailledHTMLPage)
|
||||||
|
|
||||||
|
systemTemplate := template.New("system")
|
||||||
|
systemTemplate.Parse(SystemHTMLPage)
|
||||||
|
|
||||||
|
s := &HTTPServer{
|
||||||
|
Config: c,
|
||||||
|
Templates: Templates{
|
||||||
|
Dashboard: dashboardTemplate,
|
||||||
|
Detailled: detailledTemplate,
|
||||||
|
System: systemTemplate,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
router := chi.NewRouter()
|
||||||
|
router.Use(middleware.Logger)
|
||||||
|
router.Use(recoverMiddleware)
|
||||||
|
router.Route("/web", func(routerAPI chi.Router) {
|
||||||
|
routerAPI.Get("/", s.dashboard)
|
||||||
|
routerAPI.Get("/{id}", s.detailled)
|
||||||
|
routerAPI.Get("/system", s.system)
|
||||||
|
})
|
||||||
|
s.Server = &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%d", c.Server.Port),
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPServer) dashboard(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user, pass, ok := r.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
basicAuthFailed(w, r, "realm")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cli := client.New(s.Config.Remote.URL, user, pass)
|
||||||
|
|
||||||
|
if err := cli.Ping(); err != nil {
|
||||||
|
slog.Error("unable to connect to the remote", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
saves, err := cli.All()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, client.ErrUnauthorized) {
|
||||||
|
unauthorized("Unable to access resources", w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Error("unable to connect to the remote", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(saves, func(a, b repository.Metadata) int {
|
||||||
|
return a.Date.Compare(b.Date)
|
||||||
|
})
|
||||||
|
|
||||||
|
slices.Reverse(saves)
|
||||||
|
|
||||||
|
payload := DashboardPayload{
|
||||||
|
Version: constants.Version,
|
||||||
|
Saves: saves,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Templates.Dashboard.Execute(w, payload); err != nil {
|
||||||
|
slog.Error("failed to render the html pages", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPServer) detailled(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user, pass, ok := r.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
basicAuthFailed(w, r, "realm")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := chi.URLParam(r, "id")
|
||||||
|
cli := client.New(s.Config.Remote.URL, user, pass)
|
||||||
|
|
||||||
|
if err := cli.Ping(); err != nil {
|
||||||
|
slog.Error("unable to connect to the remote", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
save, err := cli.Metadata(id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, client.ErrUnauthorized) {
|
||||||
|
unauthorized("Unable to access resources", w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Error("unable to connect to the remote", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := cli.Hash(id)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("unable to connect to the remote", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, err := cli.ListArchives(id)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("unable to connect to the remote", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var bm []repository.Backup
|
||||||
|
for _, i := range ids {
|
||||||
|
b, err := cli.ArchiveInfo(id, i)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("unable to connect to the remote", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bm = append(bm, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := DetaillePayload{
|
||||||
|
Save: save,
|
||||||
|
Hash: h,
|
||||||
|
BackupMetadata: bm,
|
||||||
|
Version: constants.Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Templates.Detailled.Execute(w, payload); err != nil {
|
||||||
|
slog.Error("failed to render the html pages", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPServer) system(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user, pass, ok := r.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
basicAuthFailed(w, r, "realm")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cli := client.New(s.Config.Remote.URL, user, pass)
|
||||||
|
|
||||||
|
if err := cli.Ping(); err != nil {
|
||||||
|
slog.Error("unable to connect to the remote", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clientInfo := client.Information{
|
||||||
|
Version: constants.Version,
|
||||||
|
APIVersion: constants.ApiVersion,
|
||||||
|
GoVersion: runtime.Version(),
|
||||||
|
OSName: runtime.GOOS,
|
||||||
|
OSArchitecture: runtime.GOARCH,
|
||||||
|
}
|
||||||
|
serverInfo, err := cli.Version()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, client.ErrUnauthorized) {
|
||||||
|
unauthorized("Unable to access resources", w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Error("unable to connect to the remote", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := SystemPayload{
|
||||||
|
Version: constants.Version,
|
||||||
|
Client: clientInfo,
|
||||||
|
Server: serverInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Templates.System.Execute(w, payload); err != nil {
|
||||||
|
slog.Error("failed to render the html pages", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
9
cmd/web/server/templates/401.html
Normal file
9
cmd/web/server/templates/401.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>You are not allowed</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>401 Unauthorized</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9
cmd/web/server/templates/500.html
Normal file
9
cmd/web/server/templates/500.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>An error occured</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>500 Internal Server Error</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
38
cmd/web/server/templates/dashboard.html
Normal file
38
cmd/web/server/templates/dashboard.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Dashboard</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<nav class="navbar bg-body-tertiary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<span class="navbar-brand mb-0 h1">CloudSave</span>
|
||||||
|
<a class="muted" href="/web/system">v{{.Version}}</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container" style="margin-top: 1rem;">
|
||||||
|
<div class="list-group">
|
||||||
|
{{range .Saves}}
|
||||||
|
<a href="/web/{{.ID}}" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<h5 class="mb-1">{{.Name}}</h5>
|
||||||
|
<small>{{.Date}}</small>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1">Version: {{.Version}}</p>
|
||||||
|
<small>{{.ID}}</small>
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
</html>
|
||||||
54
cmd/web/server/templates/detailled.html
Normal file
54
cmd/web/server/templates/detailled.html
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{{.Save.Name}}</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<nav class="navbar bg-body-tertiary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<span class="navbar-brand mb-0 h1">CloudSave</span>
|
||||||
|
<a class="muted" href="/web/system">v{{.Version}}</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container" style="margin-top: 1rem;">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/web">Home</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">{{.Save.ID}}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<div class="list-group">
|
||||||
|
<h1>{{.Save.Name}} <span class="badge text-bg-success">Version {{.Save.Version}}</span></h1>
|
||||||
|
<hr />
|
||||||
|
<h3>Details</h3>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item">UUID: {{.Save.ID}}</li>
|
||||||
|
<li class="list-group-item">Last Upload: {{.Save.Date}}</li>
|
||||||
|
<li class="list-group-item">Hash (MD5): {{.Hash}}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<h3>Backup</h3>
|
||||||
|
{{ range .BackupMetadata}}
|
||||||
|
<div class="card" style="margin-top: 1rem;">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{.CreatedAt}}</h5>
|
||||||
|
<h6 class="card-subtitle mb-2 text-body-secondary">{{.UUID}}</h6>
|
||||||
|
<p class="card-text">MD5: {{.MD5}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
</html>
|
||||||
52
cmd/web/server/templates/information.html
Normal file
52
cmd/web/server/templates/information.html
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>System Information</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<nav class="navbar bg-body-tertiary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<span class="navbar-brand mb-0 h1">CloudSave</span>
|
||||||
|
<span class="muted">v{{.Version}}</span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container" style="margin-top: 1rem;">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/web">Home</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">System</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<div class="list-group">
|
||||||
|
<h3>Client</h3>
|
||||||
|
<hr />
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item">Version: {{.Client.Version}}</li>
|
||||||
|
<li class="list-group-item">API Version: {{.Client.APIVersion}}</li>
|
||||||
|
<li class="list-group-item">Go Version: {{.Client.GoVersion}}</li>
|
||||||
|
<li class="list-group-item">OS: {{.Client.OSName}}/{{.Client.OSArchitecture}}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<h3>Server</h3>
|
||||||
|
<hr />
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item">Version: {{.Server.Version}}</li>
|
||||||
|
<li class="list-group-item">API Version: {{.Server.APIVersion}}</li>
|
||||||
|
<li class="list-group-item">Go Version: {{.Server.GoVersion}}</li>
|
||||||
|
<li class="list-group-item">OS: {{.Server.OSName}}/{{.Server.OSArchitecture}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
</html>
|
||||||
34
cmd/web/web.go
Normal file
34
cmd/web/web.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cloudsave/cmd/web/server"
|
||||||
|
"cloudsave/cmd/web/server/config"
|
||||||
|
"cloudsave/pkg/constants"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Printf("CloudSave web -- v%s.%s.%s\n\n", constants.Version, runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
|
var configPath string
|
||||||
|
flag.StringVar(&configPath, "config", "/var/lib/cloudsave/config.json", "Define the path to the configuration file")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
c, err := config.Load(configPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "failed to load configuration:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := server.NewServer(c)
|
||||||
|
|
||||||
|
fmt.Println("starting server at :" + strconv.Itoa(c.Server.Port))
|
||||||
|
if err := s.Server.ListenAndServe(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "failed to start web server:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
package constants
|
package constants
|
||||||
|
|
||||||
const Version = "0.0.2"
|
const Version = "0.0.3"
|
||||||
|
|
||||||
const ApiVersion = 1
|
const ApiVersion = 1
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNotFound error = errors.New("not found")
|
ErrNotFound error = errors.New("not found")
|
||||||
|
ErrUnauthorized error = errors.New("unauthorized (HTTP Error 401)")
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(baseURL, username, password string) *Client {
|
func New(baseURL, username, password string) *Client {
|
||||||
@@ -382,6 +383,10 @@ func (c *Client) get(url string) (obj.HTTPObject, error) {
|
|||||||
return obj.HTTPObject{}, ErrNotFound
|
return obj.HTTPObject{}, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if res.StatusCode == 401 {
|
||||||
|
return obj.HTTPObject{}, ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
return obj.HTTPObject{}, fmt.Errorf("server returns an unexpected status code: %d %s (expected 200)", res.StatusCode, res.Status)
|
return obj.HTTPObject{}, fmt.Errorf("server returns an unexpected status code: %d %s (expected 200)", res.StatusCode, res.Status)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user