further refactoring, added statistics fetcher module
@ -3,7 +3,7 @@
|
|||||||
<module name="wg-portal" />
|
<module name="wg-portal" />
|
||||||
<working_directory value="$PROJECT_DIR$" />
|
<working_directory value="$PROJECT_DIR$" />
|
||||||
<kind value="PACKAGE" />
|
<kind value="PACKAGE" />
|
||||||
<package value="github.com/h44z/wg-portal/internal/ports/api/build_tool" />
|
<package value="github.com/h44z/wg-portal/cmd/api_build_tool" />
|
||||||
<directory value="$PROJECT_DIR$" />
|
<directory value="$PROJECT_DIR$" />
|
||||||
<filePath value="$PROJECT_DIR$/internal/ports/api/build_tool/main.go" />
|
<filePath value="$PROJECT_DIR$/internal/ports/api/build_tool/main.go" />
|
||||||
<method v="2" />
|
<method v="2" />
|
||||||
|
@ -19,7 +19,7 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiBasePath := filepath.Join(wd, "/internal/ports/api")
|
apiBasePath := filepath.Join(wd, "/internal/app/api")
|
||||||
apis := []string{"v0"}
|
apis := []string{"v0"}
|
||||||
|
|
||||||
hasError := false
|
hasError := false
|
@ -2,16 +2,20 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"github.com/h44z/wg-portal/internal/app/api/core"
|
||||||
|
handlersV0 "github.com/h44z/wg-portal/internal/app/api/v0/handlers"
|
||||||
|
"github.com/h44z/wg-portal/internal/app/auth"
|
||||||
|
"github.com/h44z/wg-portal/internal/app/users"
|
||||||
|
"github.com/h44z/wg-portal/internal/app/wireguard"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/h44z/wg-portal/internal"
|
"github.com/h44z/wg-portal/internal"
|
||||||
"github.com/h44z/wg-portal/internal/adapters"
|
"github.com/h44z/wg-portal/internal/adapters"
|
||||||
"github.com/h44z/wg-portal/internal/app"
|
"github.com/h44z/wg-portal/internal/app"
|
||||||
"github.com/h44z/wg-portal/internal/config"
|
"github.com/h44z/wg-portal/internal/config"
|
||||||
"github.com/h44z/wg-portal/internal/ports/api/core"
|
|
||||||
handlersV0 "github.com/h44z/wg-portal/internal/ports/api/v0/handlers"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
evbus "github.com/vardius/message-bus"
|
evbus "github.com/vardius/message-bus"
|
||||||
)
|
)
|
||||||
@ -19,10 +23,11 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
ctx := internal.SignalAwareContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
|
ctx := internal.SignalAwareContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
logrus.Infof("Starting web portal...")
|
logrus.Infof("Starting WireGuard Portal...")
|
||||||
|
|
||||||
cfg, err := config.GetConfig()
|
cfg, err := config.GetConfig()
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
|
setupLogging(cfg)
|
||||||
|
|
||||||
rawDb, err := adapters.NewDatabase(cfg.Database)
|
rawDb, err := adapters.NewDatabase(cfg.Database)
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
@ -32,12 +37,12 @@ func main() {
|
|||||||
|
|
||||||
wireGuard := adapters.NewWireGuardRepository()
|
wireGuard := adapters.NewWireGuardRepository()
|
||||||
|
|
||||||
shouldExit, err := app.HandleProgramArgs(cfg, rawDb, wireGuard)
|
shouldExit, err := app.HandleProgramArgs(cfg, rawDb)
|
||||||
switch {
|
switch {
|
||||||
case shouldExit && err == nil:
|
case shouldExit && err == nil:
|
||||||
return
|
return
|
||||||
case shouldExit && err != nil:
|
case shouldExit && err != nil:
|
||||||
logrus.Errorf("failed to process program args: %v", err)
|
logrus.Errorf("Failed to process program args: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
case !shouldExit:
|
case !shouldExit:
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
@ -46,9 +51,22 @@ func main() {
|
|||||||
queueSize := 100
|
queueSize := 100
|
||||||
eventBus := evbus.New(queueSize)
|
eventBus := evbus.New(queueSize)
|
||||||
|
|
||||||
backend, err := app.New(cfg, eventBus, database, wireGuard)
|
userManager, err := users.NewUserManager(cfg, eventBus, database, database)
|
||||||
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
|
authenticator, err := auth.NewAuthenticator(&cfg.Auth, eventBus, userManager)
|
||||||
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
|
wireGuardManager, err := wireguard.NewWireGuardManager(cfg, eventBus, wireGuard, database)
|
||||||
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
|
statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, database, wireGuard)
|
||||||
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
|
backend, err := app.New(cfg, eventBus, authenticator, userManager, wireGuardManager, statisticsCollector)
|
||||||
|
internal.AssertNoError(err)
|
||||||
|
err = backend.Startup(ctx)
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
backend.Users.StartBackgroundJobs(ctx)
|
|
||||||
|
|
||||||
apiFrontend := handlersV0.NewRestApi(cfg, backend)
|
apiFrontend := handlersV0.NewRestApi(cfg, backend)
|
||||||
|
|
||||||
@ -56,10 +74,30 @@ func main() {
|
|||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
go webSrv.Run(ctx, cfg.Web.ListeningAddress)
|
go webSrv.Run(ctx, cfg.Web.ListeningAddress)
|
||||||
fmt.Println(backend) // TODO: Remove
|
|
||||||
|
|
||||||
// wait until context gets cancelled
|
// wait until context gets cancelled
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
|
|
||||||
logrus.Infof("Stopped web portal")
|
logrus.Infof("Stopping WireGuard Portal")
|
||||||
|
|
||||||
|
time.Sleep(5 * time.Second) // wait for (most) goroutines to finish gracefully
|
||||||
|
|
||||||
|
logrus.Infof("Stopped WireGuard Portal")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupLogging(cfg *config.Config) {
|
||||||
|
switch strings.ToLower(cfg.Advanced.LogLevel) {
|
||||||
|
case "trace":
|
||||||
|
logrus.SetLevel(logrus.TraceLevel)
|
||||||
|
case "debug":
|
||||||
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
case "info", "information":
|
||||||
|
logrus.SetLevel(logrus.InfoLevel)
|
||||||
|
case "warn", "warning":
|
||||||
|
logrus.SetLevel(logrus.WarnLevel)
|
||||||
|
case "error":
|
||||||
|
logrus.SetLevel(logrus.ErrorLevel)
|
||||||
|
default:
|
||||||
|
logrus.SetLevel(logrus.WarnLevel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ coverage
|
|||||||
/cypress/screenshots/
|
/cypress/screenshots/
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/extensions.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
.idea
|
.idea
|
||||||
*.suo
|
*.suo
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 308 B |
@ -1,7 +1,7 @@
|
|||||||
// src/lang/index.js
|
// src/lang/index.js
|
||||||
import de from './translations/de';
|
import de from './translations/de.json';
|
||||||
import en from './translations/en';
|
import en from './translations/en.json';
|
||||||
import es from './translations/es';
|
import es from './translations/es.json';
|
||||||
import {createI18n} from "vue-i18n";
|
import {createI18n} from "vue-i18n";
|
||||||
|
|
||||||
function getStoredLanguage() {
|
function getStoredLanguage() {
|
@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
|||||||
|
|
||||||
import { notify } from "@kyvg/vue3-notification";
|
import { notify } from "@kyvg/vue3-notification";
|
||||||
import { apiWrapper } from '../helpers/fetch-wrapper.js'
|
import { apiWrapper } from '../helpers/fetch-wrapper.js'
|
||||||
import router from '../router/index.js'
|
import router from '../router'
|
||||||
|
|
||||||
export const authStore = defineStore({
|
export const authStore = defineStore({
|
||||||
id: 'auth',
|
id: 'auth',
|
@ -12,7 +12,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: '../frontend-dist',
|
outDir: '../internal/app/api/core/frontend-dist',
|
||||||
emptyOutDir: true
|
emptyOutDir: true
|
||||||
},
|
},
|
||||||
// local dev api (proxy to avoid cors problems)
|
// local dev api (proxy to avoid cors problems)
|
12
go.mod
@ -11,6 +11,7 @@ require (
|
|||||||
github.com/go-ldap/ldap/v3 v3.4.4
|
github.com/go-ldap/ldap/v3 v3.4.4
|
||||||
github.com/h44z/lightmigrate v1.0.0
|
github.com/h44z/lightmigrate v1.0.0
|
||||||
github.com/h44z/lightmigrate-mysql v0.0.0-20220114152421-d1fec9d056f1
|
github.com/h44z/lightmigrate-mysql v0.0.0-20220114152421-d1fec9d056f1
|
||||||
|
github.com/prometheus-community/pro-bing v0.2.0
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/swaggo/swag v1.8.9
|
github.com/swaggo/swag v1.8.9
|
||||||
@ -53,6 +54,7 @@ require (
|
|||||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
github.com/gorilla/sessions v1.2.1 // indirect
|
github.com/gorilla/sessions v1.2.1 // indirect
|
||||||
@ -82,11 +84,11 @@ require (
|
|||||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
|
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.8 // indirect
|
github.com/ugorji/go/codec v1.2.8 // indirect
|
||||||
github.com/vishvananda/netns v0.0.2 // indirect
|
github.com/vishvananda/netns v0.0.2 // indirect
|
||||||
golang.org/x/net v0.5.0 // indirect
|
golang.org/x/net v0.10.0 // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
golang.org/x/sync v0.2.0 // indirect
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
golang.org/x/text v0.6.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
golang.org/x/tools v0.5.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
|
25
go.sum
@ -98,6 +98,7 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
|||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
@ -191,6 +192,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
|
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
|
||||||
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
|
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
|
||||||
|
github.com/prometheus-community/pro-bing v0.2.0 h1:hyK7yPFndU3LCDwEQJwPQUCjNkp1DGP/VxyzrWfXZUU=
|
||||||
|
github.com/prometheus-community/pro-bing v0.2.0/go.mod h1:20arNb2S8rNG3EtmjHyZZU92cfbhQx7oCHZ9sulAV+I=
|
||||||
github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
|
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
|
||||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||||
@ -253,7 +256,7 @@ golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80
|
|||||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
@ -266,8 +269,8 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
|||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
|
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
|
||||||
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
|
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
|
||||||
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
||||||
@ -275,8 +278,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -297,8 +300,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
@ -309,13 +312,13 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4=
|
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c h1:Okh6a1xpnJslG9Mn84pId1Mn+Q8cvpo4HCeeFWHo0cA=
|
golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c h1:Okh6a1xpnJslG9Mn84pId1Mn+Q8cvpo4HCeeFWHo0cA=
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"gorm.io/gorm/utils"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -26,13 +28,74 @@ import (
|
|||||||
var sqlMigrationFs embed.FS
|
var sqlMigrationFs embed.FS
|
||||||
var SchemaVersion uint64 = 1
|
var SchemaVersion uint64 = 1
|
||||||
|
|
||||||
|
// GormLogger is a custom logger for Gorm, making it use logrus.
|
||||||
|
type GormLogger struct {
|
||||||
|
SlowThreshold time.Duration
|
||||||
|
SourceField string
|
||||||
|
IgnoreErrRecordNotFound bool
|
||||||
|
Debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogger(slowThreshold time.Duration, debug bool) *GormLogger {
|
||||||
|
return &GormLogger{
|
||||||
|
SlowThreshold: slowThreshold,
|
||||||
|
Debug: debug,
|
||||||
|
IgnoreErrRecordNotFound: true,
|
||||||
|
SourceField: "src",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GormLogger) LogMode(logger.LogLevel) logger.Interface {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GormLogger) Info(ctx context.Context, s string, args ...interface{}) {
|
||||||
|
logrus.WithContext(ctx).Infof(s, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GormLogger) Warn(ctx context.Context, s string, args ...interface{}) {
|
||||||
|
logrus.WithContext(ctx).Warnf(s, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GormLogger) Error(ctx context.Context, s string, args ...interface{}) {
|
||||||
|
logrus.WithContext(ctx).Errorf(s, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
|
||||||
|
elapsed := time.Since(begin)
|
||||||
|
sql, rows := fc()
|
||||||
|
fields := logrus.Fields{
|
||||||
|
"rows": rows,
|
||||||
|
"duration": elapsed,
|
||||||
|
}
|
||||||
|
if l.SourceField != "" {
|
||||||
|
fields[l.SourceField] = utils.FileWithLineNum()
|
||||||
|
}
|
||||||
|
if err != nil && !(errors.Is(err, gorm.ErrRecordNotFound) && l.IgnoreErrRecordNotFound) {
|
||||||
|
fields[logrus.ErrorKey] = err
|
||||||
|
logrus.WithContext(ctx).WithFields(fields).Errorf("%s", sql)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.SlowThreshold != 0 && elapsed > l.SlowThreshold {
|
||||||
|
logrus.WithContext(ctx).WithFields(fields).Warnf("%s", sql)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.Debug {
|
||||||
|
logrus.WithContext(ctx).WithFields(fields).Debugf("%s", sql)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewDatabase(cfg config.DatabaseConfig) (*gorm.DB, error) {
|
func NewDatabase(cfg config.DatabaseConfig) (*gorm.DB, error) {
|
||||||
var gormDb *gorm.DB
|
var gormDb *gorm.DB
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch cfg.Type {
|
switch cfg.Type {
|
||||||
case config.DatabaseMySQL:
|
case config.DatabaseMySQL:
|
||||||
gormDb, err = gorm.Open(gormMySQL.Open(cfg.DSN), &gorm.Config{})
|
gormDb, err = gorm.Open(gormMySQL.Open(cfg.DSN), &gorm.Config{
|
||||||
|
Logger: NewLogger(cfg.SlowQueryThreshold, cfg.Debug),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open MySQL database: %w", err)
|
return nil, fmt.Errorf("failed to open MySQL database: %w", err)
|
||||||
}
|
}
|
||||||
@ -46,12 +109,16 @@ func NewDatabase(cfg config.DatabaseConfig) (*gorm.DB, error) {
|
|||||||
return nil, fmt.Errorf("failed to ping MySQL database: %w", err)
|
return nil, fmt.Errorf("failed to ping MySQL database: %w", err)
|
||||||
}
|
}
|
||||||
case config.DatabaseMsSQL:
|
case config.DatabaseMsSQL:
|
||||||
gormDb, err = gorm.Open(sqlserver.Open(cfg.DSN), &gorm.Config{})
|
gormDb, err = gorm.Open(sqlserver.Open(cfg.DSN), &gorm.Config{
|
||||||
|
Logger: NewLogger(cfg.SlowQueryThreshold, cfg.Debug),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open sqlserver database: %w", err)
|
return nil, fmt.Errorf("failed to open sqlserver database: %w", err)
|
||||||
}
|
}
|
||||||
case config.DatabasePostgres:
|
case config.DatabasePostgres:
|
||||||
gormDb, err = gorm.Open(postgres.Open(cfg.DSN), &gorm.Config{})
|
gormDb, err = gorm.Open(postgres.Open(cfg.DSN), &gorm.Config{
|
||||||
|
Logger: NewLogger(cfg.SlowQueryThreshold, cfg.Debug),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open Postgres database: %w", err)
|
return nil, fmt.Errorf("failed to open Postgres database: %w", err)
|
||||||
}
|
}
|
||||||
@ -61,7 +128,10 @@ func NewDatabase(cfg config.DatabaseConfig) (*gorm.DB, error) {
|
|||||||
return nil, fmt.Errorf("failed to create database base directory: %w", err)
|
return nil, fmt.Errorf("failed to create database base directory: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gormDb, err = gorm.Open(sqlite.Open(cfg.DSN), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true})
|
gormDb, err = gorm.Open(sqlite.Open(cfg.DSN), &gorm.Config{
|
||||||
|
Logger: NewLogger(cfg.SlowQueryThreshold, cfg.Debug),
|
||||||
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open sqlite database: %w", err)
|
return nil, fmt.Errorf("failed to open sqlite database: %w", err)
|
||||||
}
|
}
|
||||||
@ -89,9 +159,11 @@ func NewSqlRepository(db *gorm.DB) (*SqlRepo, error) {
|
|||||||
|
|
||||||
func (r *SqlRepo) migrate() error {
|
func (r *SqlRepo) migrate() error {
|
||||||
// TODO: REMOVE
|
// TODO: REMOVE
|
||||||
logrus.Warnf("user migration: %v", r.db.AutoMigrate(&domain.User{}))
|
logrus.Debugf("user migration: %v", r.db.AutoMigrate(&domain.User{}))
|
||||||
logrus.Warnf("interface migration: %v", r.db.AutoMigrate(&domain.Interface{}))
|
logrus.Debugf("interface migration: %v", r.db.AutoMigrate(&domain.Interface{}))
|
||||||
logrus.Warnf("peer migration: %v", r.db.AutoMigrate(&domain.Peer{}))
|
logrus.Debugf("peer migration: %v", r.db.AutoMigrate(&domain.Peer{}))
|
||||||
|
logrus.Debugf("peer status migration: %v", r.db.AutoMigrate(&domain.PeerStatus{}))
|
||||||
|
logrus.Debugf("interface status migration: %v", r.db.AutoMigrate(&domain.InterfaceStatus{}))
|
||||||
// TODO: REMOVE THE ABOVE LINES
|
// TODO: REMOVE THE ABOVE LINES
|
||||||
|
|
||||||
rawDb, err := r.db.DB()
|
rawDb, err := r.db.DB()
|
||||||
@ -267,7 +339,7 @@ func (r *SqlRepo) GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIden
|
|||||||
|
|
||||||
err := r.db.WithContext(ctx).
|
err := r.db.WithContext(ctx).
|
||||||
Table("interface_addresses").
|
Table("interface_addresses").
|
||||||
Joins("LEFT JOIN cidrs ON interface_addresses.cidr_addr = cidrs.addr AND interface_addresses.cidr_net_length = cidrs.net_len").
|
Joins("LEFT JOIN cidrs ON interface_addresses.cidr_cidr = cidrs.cidr").
|
||||||
Scan(&ips).Error
|
Scan(&ips).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -525,3 +597,113 @@ func (r *SqlRepo) upsertUser(tx *gorm.DB, user *domain.User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// endregion users
|
// endregion users
|
||||||
|
|
||||||
|
// region statistics
|
||||||
|
|
||||||
|
func (r *SqlRepo) UpdateInterfaceStatus(ctx context.Context, id domain.InterfaceIdentifier, updateFunc func(in *domain.InterfaceStatus) (*domain.InterfaceStatus, error)) error {
|
||||||
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
in, err := r.getOrCreateInterfaceStatus(tx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err // return any error will roll back
|
||||||
|
}
|
||||||
|
|
||||||
|
in, err = updateFunc(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.upsertInterfaceStatus(tx, in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// return nil will commit the whole transaction
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SqlRepo) getOrCreateInterfaceStatus(tx *gorm.DB, id domain.InterfaceIdentifier) (*domain.InterfaceStatus, error) {
|
||||||
|
var in domain.InterfaceStatus
|
||||||
|
|
||||||
|
// defaults will be applied to newly created record
|
||||||
|
defaults := domain.InterfaceStatus{
|
||||||
|
InterfaceId: id,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tx.Attrs(defaults).FirstOrCreate(&in, id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SqlRepo) upsertInterfaceStatus(tx *gorm.DB, in *domain.InterfaceStatus) error {
|
||||||
|
err := tx.Save(in).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SqlRepo) UpdatePeerStatus(ctx context.Context, id domain.PeerIdentifier, updateFunc func(in *domain.PeerStatus) (*domain.PeerStatus, error)) error {
|
||||||
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
in, err := r.getOrCreatePeerStatus(tx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err // return any error will roll back
|
||||||
|
}
|
||||||
|
|
||||||
|
in, err = updateFunc(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.upsertPeerStatus(tx, in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// return nil will commit the whole transaction
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SqlRepo) getOrCreatePeerStatus(tx *gorm.DB, id domain.PeerIdentifier) (*domain.PeerStatus, error) {
|
||||||
|
var in domain.PeerStatus
|
||||||
|
|
||||||
|
// defaults will be applied to newly created record
|
||||||
|
defaults := domain.PeerStatus{
|
||||||
|
PeerId: id,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tx.Attrs(defaults).FirstOrCreate(&in, id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SqlRepo) upsertPeerStatus(tx *gorm.DB, in *domain.PeerStatus) error {
|
||||||
|
err := tx.Save(in).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion statistics
|
||||||
|
@ -283,6 +283,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/interface/new": {
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Interface"
|
||||||
|
],
|
||||||
|
"summary": "Create the new interface record.",
|
||||||
|
"operationId": "interfaces_handleCreatePost",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "The interface data",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Interface"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Interface"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/interface/peers/{id}": {
|
"/interface/peers/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -312,6 +355,82 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/interface/prepare": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Interface"
|
||||||
|
],
|
||||||
|
"summary": "Prepare a new interface.",
|
||||||
|
"operationId": "interfaces_handlePrepareGet",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Interface"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/interface/{id}": {
|
||||||
|
"put": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Interface"
|
||||||
|
],
|
||||||
|
"summary": "Update the interface record.",
|
||||||
|
"operationId": "interfaces_handleUpdatePut",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The interface identifier",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The interface data",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Interface"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Interface"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/now": {
|
"/now": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Nothing more to describe...",
|
"description": "Nothing more to describe...",
|
||||||
@ -396,6 +515,128 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/users/new": {
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Create the new user record.",
|
||||||
|
"operationId": "users_handleCreatePost",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "The user data",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.User"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.User"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/users/{id}": {
|
||||||
|
"put": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Update the user record.",
|
||||||
|
"operationId": "users_handleUpdatePut",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The user identifier",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "The user data",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.User"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.User"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/users/{id}/peers": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Get peers for the given user.",
|
||||||
|
"operationId": "users_handlePeersGet",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/model.Peer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/model.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
@ -434,11 +675,17 @@
|
|||||||
},
|
},
|
||||||
"Dns": {
|
"Dns": {
|
||||||
"description": "the dns server that should be set if the interface is up, comma separated",
|
"description": "the dns server that should be set if the interface is up, comma separated",
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"DnsSearch": {
|
"DnsSearch": {
|
||||||
"description": "the dns search option string that should be set if the interface is up, will be appended to DnsStr",
|
"description": "the dns search option string that should be set if the interface is up, will be appended to DnsStr",
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"EnabledPeers": {
|
"EnabledPeers": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@ -467,15 +714,24 @@
|
|||||||
},
|
},
|
||||||
"PeerDefAllowedIPs": {
|
"PeerDefAllowedIPs": {
|
||||||
"description": "the default allowed IP string for the peer",
|
"description": "the default allowed IP string for the peer",
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"PeerDefDns": {
|
"PeerDefDns": {
|
||||||
"description": "the default dns server for the peer",
|
"description": "the default dns server for the peer",
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"PeerDefDnsSearch": {
|
"PeerDefDnsSearch": {
|
||||||
"description": "the default dns search options for the peer",
|
"description": "the default dns search options for the peer",
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"PeerDefEndpoint": {
|
"PeerDefEndpoint": {
|
||||||
"description": "the default endpoint for the peer",
|
"description": "the default endpoint for the peer",
|
||||||
@ -491,7 +747,10 @@
|
|||||||
},
|
},
|
||||||
"PeerDefNetwork": {
|
"PeerDefNetwork": {
|
||||||
"description": "the default subnets from which peers will get their IP addresses, comma seperated",
|
"description": "the default subnets from which peers will get their IP addresses, comma seperated",
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"PeerDefPersistentKeepalive": {
|
"PeerDefPersistentKeepalive": {
|
||||||
"description": "the default persistent keep-alive Value",
|
"description": "the default persistent keep-alive Value",
|
@ -26,11 +26,15 @@ definitions:
|
|||||||
Dns:
|
Dns:
|
||||||
description: the dns server that should be set if the interface is up, comma
|
description: the dns server that should be set if the interface is up, comma
|
||||||
separated
|
separated
|
||||||
type: string
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
DnsSearch:
|
DnsSearch:
|
||||||
description: the dns search option string that should be set if the interface
|
description: the dns search option string that should be set if the interface
|
||||||
is up, will be appended to DnsStr
|
is up, will be appended to DnsStr
|
||||||
type: string
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
EnabledPeers:
|
EnabledPeers:
|
||||||
type: integer
|
type: integer
|
||||||
FirewallMark:
|
FirewallMark:
|
||||||
@ -52,13 +56,19 @@ definitions:
|
|||||||
type: integer
|
type: integer
|
||||||
PeerDefAllowedIPs:
|
PeerDefAllowedIPs:
|
||||||
description: the default allowed IP string for the peer
|
description: the default allowed IP string for the peer
|
||||||
type: string
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
PeerDefDns:
|
PeerDefDns:
|
||||||
description: the default dns server for the peer
|
description: the default dns server for the peer
|
||||||
type: string
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
PeerDefDnsSearch:
|
PeerDefDnsSearch:
|
||||||
description: the default dns search options for the peer
|
description: the default dns search options for the peer
|
||||||
type: string
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
PeerDefEndpoint:
|
PeerDefEndpoint:
|
||||||
description: the default endpoint for the peer
|
description: the default endpoint for the peer
|
||||||
type: string
|
type: string
|
||||||
@ -71,7 +81,9 @@ definitions:
|
|||||||
PeerDefNetwork:
|
PeerDefNetwork:
|
||||||
description: the default subnets from which peers will get their IP addresses,
|
description: the default subnets from which peers will get their IP addresses,
|
||||||
comma seperated
|
comma seperated
|
||||||
type: string
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
PeerDefPersistentKeepalive:
|
PeerDefPersistentKeepalive:
|
||||||
description: the default persistent keep-alive Value
|
description: the default persistent keep-alive Value
|
||||||
type: integer
|
type: integer
|
||||||
@ -413,6 +425,39 @@ paths:
|
|||||||
summary: Get the current host name.
|
summary: Get the current host name.
|
||||||
tags:
|
tags:
|
||||||
- Testing
|
- Testing
|
||||||
|
/interface/{id}:
|
||||||
|
put:
|
||||||
|
operationId: interfaces_handleUpdatePut
|
||||||
|
parameters:
|
||||||
|
- description: The interface identifier
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: The interface data
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Interface'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Interface'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Error'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Error'
|
||||||
|
summary: Update the interface record.
|
||||||
|
tags:
|
||||||
|
- Interface
|
||||||
/interface/all:
|
/interface/all:
|
||||||
get:
|
get:
|
||||||
operationId: interfaces_handleAllGet
|
operationId: interfaces_handleAllGet
|
||||||
@ -453,6 +498,34 @@ paths:
|
|||||||
summary: Get single interface.
|
summary: Get single interface.
|
||||||
tags:
|
tags:
|
||||||
- Interface
|
- Interface
|
||||||
|
/interface/new:
|
||||||
|
post:
|
||||||
|
operationId: interfaces_handleCreatePost
|
||||||
|
parameters:
|
||||||
|
- description: The interface data
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Interface'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Interface'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Error'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Error'
|
||||||
|
summary: Create the new interface record.
|
||||||
|
tags:
|
||||||
|
- Interface
|
||||||
/interface/peers/{id}:
|
/interface/peers/{id}:
|
||||||
get:
|
get:
|
||||||
operationId: interfaces_handlePeersGet
|
operationId: interfaces_handlePeersGet
|
||||||
@ -472,6 +545,23 @@ paths:
|
|||||||
summary: Get peers for the given interface.
|
summary: Get peers for the given interface.
|
||||||
tags:
|
tags:
|
||||||
- Interface
|
- Interface
|
||||||
|
/interface/prepare:
|
||||||
|
get:
|
||||||
|
operationId: interfaces_handlePrepareGet
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Interface'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Error'
|
||||||
|
summary: Prepare a new interface.
|
||||||
|
tags:
|
||||||
|
- Interface
|
||||||
/now:
|
/now:
|
||||||
get:
|
get:
|
||||||
description: Nothing more to describe...
|
description: Nothing more to describe...
|
||||||
@ -528,4 +618,84 @@ paths:
|
|||||||
summary: Get all user records.
|
summary: Get all user records.
|
||||||
tags:
|
tags:
|
||||||
- Users
|
- Users
|
||||||
|
/users/{id}:
|
||||||
|
put:
|
||||||
|
operationId: users_handleUpdatePut
|
||||||
|
parameters:
|
||||||
|
- description: The user identifier
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: The user data
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.User'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.User'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Error'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Error'
|
||||||
|
summary: Update the user record.
|
||||||
|
tags:
|
||||||
|
- Users
|
||||||
|
/users/{id}/peers:
|
||||||
|
get:
|
||||||
|
operationId: users_handlePeersGet
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/model.Peer'
|
||||||
|
type: array
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Error'
|
||||||
|
summary: Get peers for the given user.
|
||||||
|
tags:
|
||||||
|
- Users
|
||||||
|
/users/new:
|
||||||
|
post:
|
||||||
|
operationId: users_handleCreatePost
|
||||||
|
parameters:
|
||||||
|
- description: The user data
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.User'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.User'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Error'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/model.Error'
|
||||||
|
summary: Create the new user record.
|
||||||
|
tags:
|
||||||
|
- Users
|
||||||
swagger: "2.0"
|
swagger: "2.0"
|
Before Width: | Height: | Size: 692 KiB After Width: | Height: | Size: 692 KiB |
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
Before Width: | Height: | Size: 829 KiB After Width: | Height: | Size: 829 KiB |
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 434 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 259 B |
Before Width: | Height: | Size: 251 B After Width: | Height: | Size: 251 B |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 757 B After Width: | Height: | Size: 757 B |