Github actions & clean up to be able to do linting (#27)

* Github actions & clean up to be able to do linting

* renamed user

* pushing images to github packages

* Fixed tags
This commit is contained in:
Fredrik Grönqvist
2019-12-19 13:03:03 +01:00
committed by GitHub
parent a127e67720
commit b471f3dff5
18 changed files with 15150 additions and 56 deletions
-24
View File
@@ -1,24 +0,0 @@
steps:
- name: Build container image
branches: "!master"
agents:
queue: default
os: linux
commands:
- buildah bud --format=docker --layers -f Dockerfile .
- name: Build & push container image
branches: "master"
agents:
queue: default
os: linux
commands:
- buildah bud --format=docker --layers -t embarkstudios/wireguard-ui:latest -f Dockerfile .
- buildah push --format=v2s2 --authfile /root/.dockerhub/config.json embarkstudios/wireguard-ui:latest
- name: Build and push debug container image
branches: "master"
agents:
queue: default
os: linux
commands:
- buildah bud --format=docker --layers -t embarkstudios/wireguard-ui:debug -f Dockerfile.debug .
- buildah push --format=v2s2 --authfile /root/.dockerhub/config.json embarkstudios/wireguard-ui:debug
+46
View File
@@ -0,0 +1,46 @@
name: Go
on:
push:
branches-ignore: master
jobs:
review:
name: Review code
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: bindfs
uses: "cedrickring/golang-action@1.4.1"
with:
args: go get -u github.com/go-bindata/go-bindata/... && go get -u github.com/elazarl/go-bindata-assetfs/... && go-bindata-assetfs -prefix ui/dist ui/dist
- name: Check styling error
uses: "cedrickring/golang-action@1.4.1"
with:
args: go get -u golang.org/x/lint/golint; golint -set_exit_status main.go server.go config.go # ./... update to use package when https://github.com/go-bindata/go-bindata/pull/37 is merged
- name: Check missing error check
uses: "cedrickring/golang-action@1.4.1"
with:
args: go get -u github.com/kisielk/errcheck; errcheck ./...
- name: Check suspicious constructs (1)
uses: "cedrickring/golang-action@1.4.1"
with:
args: go get honnef.co/go/tools/cmd/staticcheck; staticcheck -checks all,-ST1003,-U1000,-ST1005 ./... # have to disable ST1003,U1000,ST1005 due to the generated code
- name: Check suspicious constructs (2)
uses: "cedrickring/golang-action@1.4.1"
with:
args: go vet ./...
- name: Check missing error check
uses: "cedrickring/golang-action@1.4.1"
with:
args: go get github.com/securego/gosec/cmd/gosec; gosec ./... # https://github.com/securego/gosec
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install buildah
run: |
sudo apt-get install -qq -y software-properties-common
sudo add-apt-repository -y ppa:projectatomic/ppa
sudo apt-get update -qq
sudo apt-get -qq -y install buildah
- name: Build the Docker image
run: buildah bud --format=docker --layers -f Dockerfile .
+20
View File
@@ -0,0 +1,20 @@
name: Docker Image CI
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install buildah
run: |
sudo apt-get install -qq -y software-properties-common
sudo add-apt-repository -y ppa:projectatomic/ppa
sudo apt-get update -qq
sudo apt-get -qq -y install buildah
- name: Build & push the Docker image
run: |
buildah bud --format=docker --layers -t docker.pkg.github.com/EmbarkStudios/wireguard-ui/wireguard-ui:latest -t docker.pkg.github.com/EmbarkStudios/wireguard-ui/wireguard-ui:${GITHUB_SHA} -f Dockerfile .
buildah push --creds embarkbot:${{ secrets.GITHUB_TOKEN }} --format=v2s2 docker.pkg.github.com/EmbarkStudios/wireguard-ui/wireguard-ui
+2 -9
View File
@@ -1,11 +1,4 @@
FROM node:12-alpine AS ui FROM docker.io/golang:1.13 AS build
WORKDIR /ui
COPY ui/package.json ui/package-lock.json /ui/
RUN npm install
COPY ui .
RUN npm run build
FROM golang:1.13 AS build
WORKDIR /wg WORKDIR /wg
RUN go get github.com/go-bindata/go-bindata/... &&\ RUN go get github.com/go-bindata/go-bindata/... &&\
go get github.com/elazarl/go-bindata-assetfs/... go get github.com/elazarl/go-bindata-assetfs/...
@@ -13,7 +6,7 @@ COPY go.mod .
COPY go.sum . COPY go.sum .
RUN go mod download RUN go mod download
COPY . . COPY . .
COPY --from=ui /ui/dist ui/dist COPY /ui/dist ui/dist
RUN go-bindata-assetfs -prefix ui/dist ui/dist &&\ RUN go-bindata-assetfs -prefix ui/dist ui/dist &&\
go install . go install .
+3 -1
View File
@@ -20,7 +20,9 @@ A basic, self-contained management service for [WireGuard](https://wireguard.com
The easiest way to run wireguard-ui is using the container image. To test it, run: The easiest way to run wireguard-ui is using the container image. To test it, run:
```docker run --rm -it --privileged --entrypoint "/wireguard-ui" -v /tmp/wireguard-ui:/data -p 8080:8080 -p 5555:5555 embarkstudios/wireguard-ui --data-dir=/data --log-level=debug``` ```docker run --rm -it --privileged --entrypoint "/wireguard-ui" -v /tmp/wireguard-ui:/data -p 8080:8080 -p 5555:5555 docker.pkg.github.com/embarkstudios/wireguard-ui:latest --data-dir=/data --log-level=debug```
When running in production, we recommend using the latest release as opposed to `latest`.
## Developing ## Developing
+8 -1
View File
@@ -11,18 +11,21 @@ import (
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
// ServerConfig contains the reference to users, keys and where on disk the config is stored
type ServerConfig struct { type ServerConfig struct {
configPath string `json:"-"` configPath string
PrivateKey string PrivateKey string
PublicKey string PublicKey string
Users map[string]*UserConfig Users map[string]*UserConfig
} }
// UserConfig represents a user and it's clients
type UserConfig struct { type UserConfig struct {
Name string Name string
Clients map[string]*ClientConfig Clients map[string]*ClientConfig
} }
// ClientConfig represents a single client for a user
type ClientConfig struct { type ClientConfig struct {
Name string Name string
PrivateKey string PrivateKey string
@@ -31,6 +34,7 @@ type ClientConfig struct {
Notes string Notes string
} }
// NewServerConfig creates and returns a reference to a new ServerConfig
func NewServerConfig(cfgPath string) *ServerConfig { func NewServerConfig(cfgPath string) *ServerConfig {
key, err := wgtypes.GeneratePrivateKey() key, err := wgtypes.GeneratePrivateKey()
if err != nil { if err != nil {
@@ -62,6 +66,7 @@ func NewServerConfig(cfgPath string) *ServerConfig {
return cfg return cfg
} }
// Write writes the ServerConfig to the path specified in the config
func (cfg *ServerConfig) Write() error { func (cfg *ServerConfig) Write() error {
data, err := json.MarshalIndent(cfg, "", " ") data, err := json.MarshalIndent(cfg, "", " ")
if err != nil { if err != nil {
@@ -70,6 +75,7 @@ func (cfg *ServerConfig) Write() error {
return ioutil.WriteFile(cfg.configPath, data, 0600) return ioutil.WriteFile(cfg.configPath, data, 0600)
} }
// GetUserConfig returns a UserConfig for a specific user
func (cfg *ServerConfig) GetUserConfig(user string) *UserConfig { func (cfg *ServerConfig) GetUserConfig(user string) *UserConfig {
c, ok := cfg.Users[user] c, ok := cfg.Users[user]
if !ok { if !ok {
@@ -83,6 +89,7 @@ func (cfg *ServerConfig) GetUserConfig(user string) *UserConfig {
return c return c
} }
// NewClientConfig initiates a new client, returning a reference to the new config
func NewClientConfig(ip net.IP) *ClientConfig { func NewClientConfig(ip net.IP) *ClientConfig {
key, err := wgtypes.GeneratePrivateKey() key, err := wgtypes.GeneratePrivateKey()
if err != nil { if err != nil {
+34 -20
View File
@@ -16,7 +16,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/elazarl/go-bindata-assetfs" assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/google/nftables" "github.com/google/nftables"
"github.com/google/nftables/expr" "github.com/google/nftables/expr"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
@@ -48,6 +48,11 @@ var (
filenameRe = regexp.MustCompile("[^a-zA-Z0-9]+") filenameRe = regexp.MustCompile("[^a-zA-Z0-9]+")
) )
type contextKey string
const key = contextKey("user")
// Server is the running server
type Server struct { type Server struct {
serverConfigPath string serverConfigPath string
mutex sync.RWMutex mutex sync.RWMutex
@@ -57,18 +62,15 @@ type Server struct {
assets http.Handler assets http.Handler
} }
type WgLink struct { type wgLink struct {
attrs *netlink.LinkAttrs attrs *netlink.LinkAttrs
} }
type jwtClaims struct { func (w *wgLink) Attrs() *netlink.LinkAttrs {
}
func (w *WgLink) Attrs() *netlink.LinkAttrs {
return w.attrs return w.attrs
} }
func (w *WgLink) Type() string { func (w *wgLink) Type() string {
return "wireguard" return "wireguard"
} }
@@ -78,6 +80,7 @@ func ifname(n string) []byte {
return b return b
} }
// NewServer returns an instance of Server which contains both the webserver and the reference to Wireguard
func NewServer() *Server { func NewServer() *Server {
ipAddr, ipNet, err := net.ParseCIDR(*clientIPRange) ipAddr, ipNet, err := net.ParseCIDR(*clientIPRange)
if err != nil { if err != nil {
@@ -108,7 +111,7 @@ func NewServer() *Server {
return &s return &s
} }
func (s *Server) enableIpForward() error { func (s *Server) enableIPForward() error {
log.Info("Enabling sys.net.ipv4.ip_forward") log.Info("Enabling sys.net.ipv4.ip_forward")
p := "/proc/sys/net/ipv4/ip_forward" p := "/proc/sys/net/ipv4/ip_forward"
return ioutil.WriteFile(p, []byte("1"), 0640) return ioutil.WriteFile(p, []byte("1"), 0640)
@@ -118,7 +121,7 @@ func (s *Server) initInterface() error {
attrs := netlink.NewLinkAttrs() attrs := netlink.NewLinkAttrs()
attrs.Name = *wgLinkName attrs.Name = *wgLinkName
link := WgLink{ link := wgLink{
attrs: &attrs, attrs: &attrs,
} }
@@ -214,7 +217,7 @@ func (s *Server) allocateIP() net.IP {
} }
} }
if allocated[ip.String()] == false { if !allocated[ip.String()] {
log.Debug("Allocated IP: ", ip) log.Debug("Allocated IP: ", ip)
return ip return ip
} }
@@ -287,8 +290,9 @@ func (s *Server) configureWireGuard() error {
return nil return nil
} }
// Start configures wiregard and initiates the interfaces as well as starts the webserver to accept clients
func (s *Server) Start() error { func (s *Server) Start() error {
err := s.enableIpForward() err := s.enableIPForward()
if err != nil { if err != nil {
return err return err
} }
@@ -357,7 +361,7 @@ func (s *Server) userFromHeader(handler http.Handler) http.Handler {
} }
http.SetCookie(w, &cookie) http.SetCookie(w, &cookie)
ctx := context.WithValue(r.Context(), "user", user) ctx := context.WithValue(r.Context(), key, user)
handler.ServeHTTP(w, r.WithContext(ctx)) handler.ServeHTTP(w, r.WithContext(ctx))
}) })
} }
@@ -366,7 +370,7 @@ func (s *Server) withAuth(handler httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
log.Debug("Auth required") log.Debug("Auth required")
user := r.Context().Value("user") user := r.Context().Value(key)
if user == nil { if user == nil {
log.Error("Error getting username from request context") log.Error("Error getting username from request context")
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@@ -383,8 +387,9 @@ func (s *Server) withAuth(handler httprouter.Handle) httprouter.Handle {
} }
} }
// WhoAmI returns the identity of the current user
func (s *Server) WhoAmI(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { func (s *Server) WhoAmI(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := r.Context().Value("user").(string) user := r.Context().Value(key).(string)
log.Debug(user) log.Debug(user)
err := json.NewEncoder(w).Encode(struct{ User string }{user}) err := json.NewEncoder(w).Encode(struct{ User string }{user})
if err != nil { if err != nil {
@@ -393,8 +398,9 @@ func (s *Server) WhoAmI(w http.ResponseWriter, r *http.Request, ps httprouter.Pa
} }
} }
// GetClients returns a list of all clients for the current user
func (s *Server) GetClients(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { func (s *Server) GetClients(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := r.Context().Value("user").(string) user := r.Context().Value(key).(string)
log.Debug(user) log.Debug(user)
clients := map[string]*ClientConfig{} clients := map[string]*ClientConfig{}
userConfig := s.Config.Users[user] userConfig := s.Config.Users[user]
@@ -409,14 +415,16 @@ func (s *Server) GetClients(w http.ResponseWriter, r *http.Request, ps httproute
} }
} }
// Index returns the single-page app
func (s *Server) Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func (s *Server) Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
log.Debug("Serving single-page app from URL: ", r.URL) log.Debug("Serving single-page app from URL: ", r.URL)
r.URL.Path = "/" r.URL.Path = "/"
s.assets.ServeHTTP(w, r) s.assets.ServeHTTP(w, r)
} }
// GetClient returns a specific client for the current user
func (s *Server) GetClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { func (s *Server) GetClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := r.Context().Value("user").(string) user := r.Context().Value(key).(string)
usercfg := s.Config.Users[user] usercfg := s.Config.Users[user]
if usercfg == nil { if usercfg == nil {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
@@ -472,7 +480,10 @@ Endpoint = %s
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
w.Header().Set("Content-Type", "application/config") w.Header().Set("Content-Type", "application/config")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, configData) _, err := fmt.Fprint(w, configData)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
return return
} }
@@ -484,8 +495,9 @@ Endpoint = %s
} }
} }
// EditClient edits the specific client passed by the current user
func (s *Server) EditClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { func (s *Server) EditClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := r.Context().Value("user").(string) user := r.Context().Value(key).(string)
usercfg := s.Config.Users[user] usercfg := s.Config.Users[user]
if usercfg == nil { if usercfg == nil {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
@@ -526,8 +538,9 @@ func (s *Server) EditClient(w http.ResponseWriter, r *http.Request, ps httproute
} }
} }
// DeleteClient deletes the specified client for the current user
func (s *Server) DeleteClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { func (s *Server) DeleteClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := r.Context().Value("user").(string) user := r.Context().Value(key).(string)
usercfg := s.Config.Users[user] usercfg := s.Config.Users[user]
if usercfg == nil { if usercfg == nil {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
@@ -548,11 +561,12 @@ func (s *Server) DeleteClient(w http.ResponseWriter, r *http.Request, ps httprou
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
// CreateClient creates a new client for the current user
func (s *Server) CreateClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { func (s *Server) CreateClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
user := r.Context().Value("user").(string) user := r.Context().Value(key).(string)
log.WithField("user", user).Debug("CreateClient") log.WithField("user", user).Debug("CreateClient")
c := s.Config.GetUserConfig(user) c := s.Config.GetUserConfig(user)
-1
View File
@@ -1,3 +1,2 @@
node_modules node_modules
.idea .idea
dist
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+15
View File
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>wireguard-ui</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,600,700">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Mono">
<link rel="shortcut icon" href="/favicon.png"><link href="/vendors~main.css?cea63361befb692ab573" rel="stylesheet"><link href="/main.css?cea63361befb692ab573" rel="stylesheet"></head>
<body>
<script type="text/javascript" src="/vendors~main.1.js?cea63361befb692ab573"></script><script type="text/javascript" src="/main.js?cea63361befb692ab573"></script></body>
</html>
+236
View File
@@ -0,0 +1,236 @@
.mdc-typography {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
.mdc-typography--headline1 {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 6rem;
line-height: 6rem;
font-weight: 300;
letter-spacing: -0.01562em;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-typography--headline2 {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 3.75rem;
line-height: 3.75rem;
font-weight: 300;
letter-spacing: -0.00833em;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-typography--headline3 {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 3rem;
line-height: 3.125rem;
font-weight: 400;
letter-spacing: normal;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-typography--headline4 {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 2.125rem;
line-height: 2.5rem;
font-weight: 400;
letter-spacing: 0.00735em;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-typography--headline5 {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 1.5rem;
line-height: 2rem;
font-weight: 400;
letter-spacing: normal;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-typography--headline6 {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 1.25rem;
line-height: 2rem;
font-weight: 500;
letter-spacing: 0.0125em;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-typography--subtitle1 {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 1rem;
line-height: 1.75rem;
font-weight: 400;
letter-spacing: 0.00937em;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-typography--subtitle2 {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 0.875rem;
line-height: 1.375rem;
font-weight: 500;
letter-spacing: 0.00714em;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-typography--body1 {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 1rem;
line-height: 1.5rem;
font-weight: 400;
letter-spacing: 0.03125em;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-typography--body2 {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 400;
letter-spacing: 0.01786em;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-typography--caption {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 0.75rem;
line-height: 1.25rem;
font-weight: 400;
letter-spacing: 0.03333em;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-typography--button {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 0.875rem;
line-height: 2.25rem;
font-weight: 500;
letter-spacing: 0.08929em;
text-decoration: none;
text-transform: uppercase;
}
.mdc-typography--overline {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-size: 0.75rem;
line-height: 2rem;
font-weight: 500;
letter-spacing: 0.16667em;
text-decoration: none;
text-transform: uppercase;
}
.container {
padding: 30px;
}
body {
margin: 0;
padding: 0;
}
.float-right {
float: right;
}
dl {
display: flex;
flex-flow: row wrap;
}
dt {
flex-basis: 20%;
font-weight: bold;
text-align: right;
}
dd {
flex-basis: 70%;
flex-grow: 1;
margin-left: 0.5em;
overflow: hidden;
}
@media screen and (max-width: 800px) {
img.svelte-9q81nd {
display: none;
}
}
img.svelte-9q81nd {
margin-right: 40px;
border: 1px solid #ccc;
}
.download.svelte-9q81nd {
margin-top: 2em;
}
.newClient.svelte-o5v00 {
float: right;
}
h2.svelte-o5v00 small.svelte-o5v00 {
display: block;
clear: left;
color: #ccc;
}
.row.svelte-o5v00 {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.col.svelte-o5v00 {
display: flex;
flex-direction: column;
flex-basis: 100%;
flex: 1;
margin-left: 2em;
}
.help.svelte-o5v00 {
flex-basis: 10%;
}
h2.svelte-o5v00 {
margin: 0;
padding: 0;
}
.back.svelte-101labk {
position: fixed;
left: 10px;
top: 70px;
}
@media screen and (max-width: 800px) {
.user.svelte-1bvmsl0 {
display: none;
}
}
footer.svelte-vt2udd {
margin-top: 3em;
border-top: 1px solid #ddd;
text-align: center;
background: #f7f7f7;
}
/*# sourceMappingURL=main.css.map*/
+1
View File
File diff suppressed because one or more lines are too long
+1561
View File
File diff suppressed because it is too large Load Diff
+1
View File
File diff suppressed because one or more lines are too long
+9303
View File
File diff suppressed because it is too large Load Diff
+1
View File
File diff suppressed because one or more lines are too long
+3918
View File
File diff suppressed because it is too large Load Diff
+1
View File
File diff suppressed because one or more lines are too long