Initial commit
This commit is contained in:
commit
684b057b27
33 changed files with 1821 additions and 0 deletions
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# air
|
||||||
|
tmp
|
||||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["svelte.svelte-vscode"]
|
||||||
|
}
|
||||||
52
backend/.air.toml
Normal file
52
backend/.air.toml
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
root = "."
|
||||||
|
testdata_dir = "testdata"
|
||||||
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
args_bin = []
|
||||||
|
bin = "./tmp/main"
|
||||||
|
cmd = "go build -o tmp/main"
|
||||||
|
delay = 1000
|
||||||
|
exclude_dir = ["assets", "tmp", "vendor", "testdata", "ui"]
|
||||||
|
exclude_file = []
|
||||||
|
exclude_regex = ["_test.go"]
|
||||||
|
exclude_unchanged = false
|
||||||
|
follow_symlink = false
|
||||||
|
full_bin = ""
|
||||||
|
include_dir = []
|
||||||
|
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||||
|
include_file = []
|
||||||
|
kill_delay = "0s"
|
||||||
|
log = "build-errors.log"
|
||||||
|
poll = false
|
||||||
|
poll_interval = 0
|
||||||
|
post_cmd = []
|
||||||
|
pre_cmd = []
|
||||||
|
rerun = false
|
||||||
|
rerun_delay = 500
|
||||||
|
send_interrupt = false
|
||||||
|
stop_on_error = false
|
||||||
|
|
||||||
|
[color]
|
||||||
|
app = ""
|
||||||
|
build = "yellow"
|
||||||
|
main = "magenta"
|
||||||
|
runner = "green"
|
||||||
|
watcher = "cyan"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
main_only = false
|
||||||
|
silent = false
|
||||||
|
time = false
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
clean_on_exit = true
|
||||||
|
|
||||||
|
[proxy]
|
||||||
|
app_port = 0
|
||||||
|
enabled = false
|
||||||
|
proxy_port = 0
|
||||||
|
|
||||||
|
[screen]
|
||||||
|
clear_on_rebuild = false
|
||||||
|
keep_scroll = true
|
||||||
30
backend/Dockerfile
Normal file
30
backend/Dockerfile
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
FROM golang:alpine
|
||||||
|
|
||||||
|
# Set necessary environmet variables needed for our image
|
||||||
|
ENV GO111MODULE=on \
|
||||||
|
CGO_ENABLED=0 \
|
||||||
|
GOOS=linux \
|
||||||
|
GOARCH=amd64
|
||||||
|
|
||||||
|
# Move to working directory /build
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Copy the code from /app to the build folder into the container
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Configure the build (go.mod and go.sum are already copied with prior step)
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN go build -o main .
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy binary from build to main folder
|
||||||
|
RUN cp /build/main .
|
||||||
|
|
||||||
|
# Export necessary port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Command to run when starting the container
|
||||||
|
CMD ["/app/main"]
|
||||||
15
backend/go.mod
Normal file
15
backend/go.mod
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
module xtablo-backend
|
||||||
|
|
||||||
|
go 1.23.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-chi/chi/v5 v5.2.0
|
||||||
|
github.com/olivere/vite v0.0.0-20241125063354-5c2fc1f1ddc2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/rs/zerolog v1.33.0
|
||||||
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
|
)
|
||||||
25
backend/go.sum
Normal file
25
backend/go.sum
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/olivere/vite v0.0.0-20241125063354-5c2fc1f1ddc2 h1:yrFRHF77HTyASeJG/11+Zflj7Z5OVT+oIkeUc/EIwpI=
|
||||||
|
github.com/olivere/vite v0.0.0-20241125063354-5c2fc1f1ddc2/go.mod h1:GcOsJRAsACfTzrwnVKPHHQb2IpqJo2o7OEGht882nuA=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
|
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||||
|
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
11
backend/internal/frontend/dist/.vite/manifest.json
vendored
Normal file
11
backend/internal/frontend/dist/.vite/manifest.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"src/main.ts": {
|
||||||
|
"file": "assets/main-B7j1Bbjq.js",
|
||||||
|
"name": "main",
|
||||||
|
"src": "src/main.ts",
|
||||||
|
"isEntry": true,
|
||||||
|
"css": [
|
||||||
|
"assets/main-D3T09nt8.css"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
5
backend/internal/frontend/dist/assets/main-B7j1Bbjq.js
vendored
Normal file
5
backend/internal/frontend/dist/assets/main-B7j1Bbjq.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
backend/internal/frontend/dist/assets/main-D3T09nt8.css
vendored
Normal file
1
backend/internal/frontend/dist/assets/main-D3T09nt8.css
vendored
Normal file
File diff suppressed because one or more lines are too long
8
backend/internal/frontend/file.go
Normal file
8
backend/internal/frontend/file.go
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package frontend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed all:dist
|
||||||
|
var DistFS embed.FS
|
||||||
129
backend/internal/spahandler/handler.go
Normal file
129
backend/internal/spahandler/handler.go
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
package spahandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/olivere/vite"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"xtablo-backend/internal/frontend"
|
||||||
|
)
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
distFS fs.FS
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler() *handler {
|
||||||
|
distFS, err := fs.Sub(frontend.DistFS, "dist")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("creating sub-filesystem for 'dist' directory: %w", err))
|
||||||
|
}
|
||||||
|
return &handler{
|
||||||
|
distFS: distFS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetAssets() http.HandlerFunc {
|
||||||
|
return http.FileServerFS(h.distFS).ServeHTTP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetSpa() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg(err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||||
|
viteFragment, err := vite.HTMLFragment(vite.Config{
|
||||||
|
FS: h.distFS,
|
||||||
|
IsDev: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg(err.Error())
|
||||||
|
http.Error(w, "Error instantiating vite fragment", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("index").Parse(indexTmpl)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error parsing template", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tmpl.Execute(w, map[string]interface{}{
|
||||||
|
"Vite": viteFragment,
|
||||||
|
}); err != nil {
|
||||||
|
http.Error(w, "Error executing template", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve the public files generated by Vite. By default, these files are
|
||||||
|
// referenced in the DOM with a root-relative URL format (e.g. '/file.ext').
|
||||||
|
http.ServeFileFS(w, r, h.distFS, filepath.Base(r.URL.Path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) GetDevSpa() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg(err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||||
|
viteFragment, err := vite.HTMLFragment(vite.Config{
|
||||||
|
FS: os.DirFS("ui"),
|
||||||
|
IsDev: true,
|
||||||
|
ViteURL: "http://localhost:5173",
|
||||||
|
ViteTemplate: vite.SvelteTs,
|
||||||
|
ViteEntry: "/src/main.ts",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error instantiating vite fragment", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("index").Parse(indexTmpl)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error parsing template", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tmpl.Execute(w, map[string]interface{}{
|
||||||
|
"Vite": viteFragment,
|
||||||
|
}); err != nil {
|
||||||
|
http.Error(w, "Error executing template", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve files in the public directory. By default, these files are
|
||||||
|
// referenced in the DOM with a root-relative URL format (e.g. '/file.ext').
|
||||||
|
http.ServeFileFS(w, r, os.DirFS("./public"), filepath.Base(r.URL.Path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexTmpl = `<!doctype html>
|
||||||
|
<html lang="en" class="h-full scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
{{ .Vite.Tags }}
|
||||||
|
</head>
|
||||||
|
<body class="w-100">
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
57
backend/main.go
Normal file
57
backend/main.go
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
chi "github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Here we are implementing the NotImplemented handler. Whenever an API endpoint is hit
|
||||||
|
// we will simply return the message "Not Implemented"
|
||||||
|
var NotImplemented = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("Not Implemented"))
|
||||||
|
})
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Prepare logger
|
||||||
|
zerolog.TimeFieldFormat = time.DateTime
|
||||||
|
|
||||||
|
var (
|
||||||
|
isDev = flag.Bool("dev", false, "run in development mode")
|
||||||
|
)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
mux := chi.NewRouter()
|
||||||
|
|
||||||
|
if *isDev {
|
||||||
|
registerDevRoutes(mux)
|
||||||
|
} else {
|
||||||
|
registerProdRoutes(mux)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our API is going to consist of three routes
|
||||||
|
// /status - which we will call to make sure that our API is up and running
|
||||||
|
// /products - which will retrieve a list of products that the user can leave feedback on
|
||||||
|
// /products/{slug}/feedback - which will capture user feedback on products
|
||||||
|
mux.Get("/status", NotImplemented)
|
||||||
|
mux.Get("/products", NotImplemented)
|
||||||
|
mux.Post("/products/{slug}/feedback", NotImplemented)
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: "0.0.0.0:8443",
|
||||||
|
Handler: mux,
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
WriteTimeout: time.Minute,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msg("Listening on port 8443...")
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
log.Error().Msg(err.Error())
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
36
backend/router.go
Normal file
36
backend/router.go
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
chi "github.com/go-chi/chi/v5"
|
||||||
|
|
||||||
|
"xtablo-backend/internal/spahandler"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerProdRoutes(mux *chi.Mux) {
|
||||||
|
spaHandler := spahandler.NewHandler()
|
||||||
|
|
||||||
|
// Handle requests for Vite-managed assets.
|
||||||
|
mux.Get("/assets/*", spaHandler.GetAssets())
|
||||||
|
|
||||||
|
// Handle index.html request
|
||||||
|
mux.Get("/*", spaHandler.GetSpa())
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerDevRoutes(mux *chi.Mux) {
|
||||||
|
spaHandler := spahandler.NewHandler()
|
||||||
|
|
||||||
|
// Handle index.html request
|
||||||
|
mux.Get("/*", spaHandler.GetDevSpa())
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexTmpl = `<!doctype html>
|
||||||
|
<html lang="en" class="h-full scroll-smooth">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
{{ .Vite.Tags }}
|
||||||
|
</head>
|
||||||
|
<body class="w-100">
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
8
justfile
Normal file
8
justfile
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
_frontend-dev:
|
||||||
|
cd ui && pnpm run dev
|
||||||
|
|
||||||
|
_backend-dev:
|
||||||
|
air -- -dev
|
||||||
|
|
||||||
|
dev:
|
||||||
|
just _backend-dev & (just _frontend-dev)
|
||||||
13
ui/index.html
Normal file
13
ui/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>X-Tablo</title>
|
||||||
|
</head>
|
||||||
|
<body class="w-100">
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
28
ui/package.json
Normal file
28
ui/package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "xtablo-ui",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
|
"svelte": "^5.15.0",
|
||||||
|
"svelte-check": "^4.1.1",
|
||||||
|
"typescript": "~5.6.2",
|
||||||
|
"vite": "^6.0.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@auth0/auth0-spa-js": "^2.1.3",
|
||||||
|
"carbon-components-svelte": "^0.87.0",
|
||||||
|
"carbon-icons-svelte": "^12.13.0",
|
||||||
|
"less": "^4.2.1",
|
||||||
|
"svelte-google-login": "^0.0.3",
|
||||||
|
"tachyons": "4.12.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1062
ui/pnpm-lock.yaml
Normal file
1062
ui/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
42
ui/src/App.svelte
Normal file
42
ui/src/App.svelte
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Login from "./Login.svelte";
|
||||||
|
import { Theme } from "carbon-components-svelte";
|
||||||
|
import type { Auth0Client } from "@auth0/auth0-spa-js";
|
||||||
|
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import auth from "./authService";
|
||||||
|
import { isAuthenticated, user, user_tasks, tasks } from "./store";
|
||||||
|
import config from "./auth_config";
|
||||||
|
|
||||||
|
let auth0Client: Auth0Client;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
auth0Client = await auth.createClient();
|
||||||
|
|
||||||
|
isAuthenticated.set(await auth0Client.isAuthenticated());
|
||||||
|
user.set(await auth0Client.getUser());
|
||||||
|
});
|
||||||
|
|
||||||
|
function login() {
|
||||||
|
auth.loginWithPopup(auth0Client, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
auth.logout(auth0Client);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="w-100 pa3">
|
||||||
|
<Theme
|
||||||
|
render="toggle"
|
||||||
|
toggle={{
|
||||||
|
themes: ["g10", "g100"],
|
||||||
|
labelA: "Light Mode",
|
||||||
|
labelB: "Dark Mode",
|
||||||
|
size: "sm",
|
||||||
|
}}
|
||||||
|
persist
|
||||||
|
persistKey="__carbon-theme"
|
||||||
|
/>
|
||||||
|
<Login handleLogin={login} />
|
||||||
|
</main>
|
||||||
85
ui/src/Login.svelte
Normal file
85
ui/src/Login.svelte
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Content,
|
||||||
|
Form,
|
||||||
|
Link,
|
||||||
|
Modal,
|
||||||
|
PasswordInput,
|
||||||
|
RadioButton,
|
||||||
|
RadioButtonGroup,
|
||||||
|
TextInput,
|
||||||
|
} from "carbon-components-svelte";
|
||||||
|
import RightArrow from "carbon-icons-svelte/lib/ArrowRight.svelte";
|
||||||
|
|
||||||
|
let { handleLogin }: { handleLogin: () => void } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-row items-center justify-center ma4">
|
||||||
|
<div class="login-block ma6">
|
||||||
|
<form class="measure center">
|
||||||
|
<fieldset id="sign_up" class="ba b--transparent ph0 mh0">
|
||||||
|
<legend class="f2 fw5 mh0 mb1">Log In to X-Tablo</legend>
|
||||||
|
<span
|
||||||
|
>Don't have an account, create one for free <Link
|
||||||
|
href="#0"
|
||||||
|
class="f6 link dim">here</Link
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="mt3">
|
||||||
|
<label class="db fw6 lh-copy f6" for="email-address">Email</label>
|
||||||
|
<TextInput
|
||||||
|
class="pa2 bg-transparent ba w-100"
|
||||||
|
type="email"
|
||||||
|
id="email-address"
|
||||||
|
name="email-address"
|
||||||
|
></TextInput>
|
||||||
|
</div>
|
||||||
|
<div class="mv3">
|
||||||
|
<label class="db fw6 lh-copy f6" for="password">Password</label>
|
||||||
|
<TextInput
|
||||||
|
class="pa2 bg-transparent ba w-100"
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
></TextInput>
|
||||||
|
</div>
|
||||||
|
<Checkbox labelText="Remember ID" checked />
|
||||||
|
</fieldset>
|
||||||
|
<div class="">
|
||||||
|
<Button
|
||||||
|
on:click={handleLogin}
|
||||||
|
class="b ph3 pv2 input-reset ba db w-50 grow"
|
||||||
|
type="submit"
|
||||||
|
icon={RightArrow}>Log In</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="lh-copy mt3">
|
||||||
|
<Link href="#0" class="f6 link dim db">Sign up</Link>
|
||||||
|
<Link href="#0" class="f6 link dim db">Forgot your password?</Link>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<script
|
||||||
|
src="https://unpkg.com/@lottiefiles/lottie-player@2.0.8/dist/lottie-player.js"
|
||||||
|
></script><lottie-player
|
||||||
|
src="https://lottie.host/36551324-1e54-499f-8410-4695a457a128/rrkpkFcJVP.json"
|
||||||
|
background="##FFFFFF"
|
||||||
|
speed="1"
|
||||||
|
style="width: 400px; height: 400px"
|
||||||
|
loop
|
||||||
|
autoplay
|
||||||
|
direction="1"
|
||||||
|
mode="normal"
|
||||||
|
></lottie-player>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.login-block {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
10
ui/src/app.css
Normal file
10
ui/src/app.css
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
:app {
|
||||||
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
1
ui/src/assets/svelte.svg
Normal file
1
ui/src/assets/svelte.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
46
ui/src/authService.ts
Normal file
46
ui/src/authService.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import {
|
||||||
|
createAuth0Client,
|
||||||
|
Auth0Client,
|
||||||
|
type Auth0ClientOptions,
|
||||||
|
} from "@auth0/auth0-spa-js";
|
||||||
|
import { user, isAuthenticated, popupOpen } from "./store";
|
||||||
|
import config from "./auth_config";
|
||||||
|
|
||||||
|
async function createClient() {
|
||||||
|
let auth0Client = await createAuth0Client({
|
||||||
|
domain: config.domain,
|
||||||
|
clientId: config.clientId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return auth0Client;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loginWithPopup(
|
||||||
|
client: Auth0Client,
|
||||||
|
options: Auth0ClientOptions
|
||||||
|
) {
|
||||||
|
popupOpen.set(true);
|
||||||
|
try {
|
||||||
|
await client.loginWithPopup(options);
|
||||||
|
|
||||||
|
user.set(await client.getUser());
|
||||||
|
isAuthenticated.set(true);
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.error(e);
|
||||||
|
} finally {
|
||||||
|
popupOpen.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout(client: Auth0Client) {
|
||||||
|
return client.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auth = {
|
||||||
|
createClient,
|
||||||
|
loginWithPopup,
|
||||||
|
logout,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default auth;
|
||||||
6
ui/src/auth_config.ts
Normal file
6
ui/src/auth_config.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
const config = {
|
||||||
|
domain: "dev-iw7iwywee7bt0yer.eu.auth0.com",
|
||||||
|
clientId: "hxPBFRIRqQKqXoIdEcrODEhLhZPmSwm3",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
10
ui/src/lib/Counter.svelte
Normal file
10
ui/src/lib/Counter.svelte
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<script lang="ts">
|
||||||
|
let count: number = $state(0)
|
||||||
|
const increment = () => {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button onclick={increment}>
|
||||||
|
count is {count}
|
||||||
|
</button>
|
||||||
13
ui/src/main.ts
Normal file
13
ui/src/main.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import "vite/modulepreload-polyfill";
|
||||||
|
import "carbon-components-svelte/css/all.css";
|
||||||
|
|
||||||
|
import { mount } from "svelte";
|
||||||
|
import "tachyons";
|
||||||
|
import "./app.css";
|
||||||
|
import App from "./App.svelte";
|
||||||
|
|
||||||
|
const app = mount(App, {
|
||||||
|
target: document.getElementById("app")!,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
||||||
1
ui/src/public/vite.svg
Normal file
1
ui/src/public/vite.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
19
ui/src/store.ts
Normal file
19
ui/src/store.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { writable, derived, type Writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const isAuthenticated = writable(false);
|
||||||
|
export const user: Writable<{ email?: string | undefined } | undefined> =
|
||||||
|
writable();
|
||||||
|
export const popupOpen = writable(false);
|
||||||
|
export const error = writable();
|
||||||
|
|
||||||
|
export const tasks: Writable<{ user: string }[]> = writable([]);
|
||||||
|
|
||||||
|
export const user_tasks = derived([tasks, user], ([$tasks, $user]) => {
|
||||||
|
let logged_in_user_tasks: any[] = [];
|
||||||
|
|
||||||
|
if ($user && $user.email) {
|
||||||
|
logged_in_user_tasks = $tasks.filter((task) => task.user === $user.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
return logged_in_user_tasks;
|
||||||
|
});
|
||||||
3
ui/src/vite-env.d.ts
vendored
Normal file
3
ui/src/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
/// <reference types="svelte" />
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
/// <reference types="vite-plugin-svgr/client" />
|
||||||
7
ui/svelte.config.js
Normal file
7
ui/svelte.config.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: vitePreprocess(),
|
||||||
|
}
|
||||||
20
ui/tsconfig.app.json
Normal file
20
ui/tsconfig.app.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
/**
|
||||||
|
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||||
|
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||||
|
* Note that setting allowJs false does not prevent the use
|
||||||
|
* of JS in `.svelte` files.
|
||||||
|
*/
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.svelte"]
|
||||||
|
}
|
||||||
8
ui/tsconfig.json
Normal file
8
ui/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
24
ui/tsconfig.node.json
Normal file
24
ui/tsconfig.node.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
17
ui/vite.config.ts
Normal file
17
ui/vite.config.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [svelte()],
|
||||||
|
build: {
|
||||||
|
// generates .vite/manifest.json in outDir
|
||||||
|
manifest: true,
|
||||||
|
emptyOutDir: true,
|
||||||
|
outDir: "../backend/internal/frontend/dist",
|
||||||
|
rollupOptions: {
|
||||||
|
// overwrite default .html entry
|
||||||
|
input: "/src/main.ts",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue