update http
This commit is contained in:
parent
b07a74543b
commit
1a1713eb14
68
admin.go
68
admin.go
@ -2,6 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"embed"
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -14,8 +17,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AdminConfig struct {
|
type AdminConfig struct {
|
||||||
Users []User `json:"users"`
|
Users []*User `json:"users"`
|
||||||
Secrets SecretsConfig `json:"secrets"`
|
Secrets *SecretsConfig `json:"secrets"`
|
||||||
Addr string `json:"addr"`
|
Addr string `json:"addr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,24 +31,52 @@ type SecretsConfig struct {
|
|||||||
ScryptP int `json:"scrypt_p"`
|
ScryptP int `json:"scrypt_p"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:embed assets
|
||||||
|
var assets embed.FS
|
||||||
|
|
||||||
func NewAdmin() *AdminConfig {
|
func NewAdmin() *AdminConfig {
|
||||||
pepper, _ := password.Generate(20, 5, 0, false, false)
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
ctx, _ := password.Generate(20, 5, 0, false, false)
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
a := &AdminConfig{
|
a := &AdminConfig{
|
||||||
Addr: "0.0.0.0:8080",
|
Addr: "0.0.0.0:8080",
|
||||||
Secrets: SecretsConfig{
|
Secrets: NewSecrets(),
|
||||||
|
Users: make([]*User, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSecrets() *SecretsConfig {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
pepper, _ := password.Generate(20, 5, 0, false, false)
|
||||||
|
ctx, _ := password.Generate(20, 5, 0, false, false)
|
||||||
|
|
||||||
|
return &SecretsConfig{
|
||||||
PasswordPepper: pepper,
|
PasswordPepper: pepper,
|
||||||
ContextKey: ctx,
|
ContextKey: ctx,
|
||||||
ContextExpiration: 3600,
|
ContextExpiration: 3600,
|
||||||
ScryptN: 32768,
|
ScryptN: 32768,
|
||||||
ScryptR: 8,
|
ScryptR: 8,
|
||||||
ScryptP: 1,
|
ScryptP: 1,
|
||||||
},
|
|
||||||
Users: make([]User, 0),
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AdminConfig) NewAdminUser() {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
p, _ := password.Generate(20, 5, 0, false, false)
|
||||||
|
u, _ := NewUser("admin", p)
|
||||||
|
|
||||||
|
a.Users = append(a.Users, u)
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{}).Warnf("Admin user password : %s", p)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
return a
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AdminConfig) Run() {
|
func (a *AdminConfig) Run() {
|
||||||
@ -61,6 +92,17 @@ func (a *AdminConfig) Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
|
if t, err := template.ParseFS(assets, "assets/templates/*.html"); err != nil {
|
||||||
|
log.WithFields(log.Fields{"call": "template.ParseFS", "error": err}).Errorf("")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
r.SetHTMLTemplate(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.GET("/", HttpAnyIndex)
|
||||||
|
r.POST("/", HttpAnyIndex)
|
||||||
|
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "pong",
|
"message": "pong",
|
||||||
@ -74,6 +116,16 @@ func (a *AdminConfig) Run() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
fsys, _ := fs.Sub(assets, "assets/static")
|
||||||
|
r.StaticFS("/assets", http.FS(fsys))
|
||||||
|
|
||||||
|
protected := r.Group("p", HttpAuth())
|
||||||
|
protected.GET("test", HttpAnyHome)
|
||||||
|
|
||||||
|
unprotected := r.Group("u", HttpNoAuth())
|
||||||
|
unprotected.GET("signin", HttpAnySignIn)
|
||||||
|
unprotected.POST("signin", HttpAnySignIn)
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: a.Addr,
|
Addr: a.Addr,
|
||||||
Handler: r,
|
Handler: r,
|
||||||
|
18
assets/backup.sample.json
Normal file
18
assets/backup.sample.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"schedule":{
|
||||||
|
"hourly":"25h",
|
||||||
|
"daily":"1m",
|
||||||
|
"weekly":"3m",
|
||||||
|
"monthly":"13m"
|
||||||
|
},
|
||||||
|
"box":{},
|
||||||
|
"email":{
|
||||||
|
"active":false
|
||||||
|
},
|
||||||
|
"apps":[],
|
||||||
|
"timezone":"Etc/UTC",
|
||||||
|
"admin":{
|
||||||
|
"addr":":8080"
|
||||||
|
},
|
||||||
|
"debug":true
|
||||||
|
}
|
5002
assets/static/css/bootstrap-grid.css
vendored
Normal file
5002
assets/static/css/bootstrap-grid.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
assets/static/css/bootstrap-grid.css.map
Normal file
1
assets/static/css/bootstrap-grid.css.map
Normal file
File diff suppressed because one or more lines are too long
7
assets/static/css/bootstrap-grid.min.css
vendored
Normal file
7
assets/static/css/bootstrap-grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/static/css/bootstrap-grid.min.css.map
Normal file
1
assets/static/css/bootstrap-grid.min.css.map
Normal file
File diff suppressed because one or more lines are too long
5001
assets/static/css/bootstrap-grid.rtl.css
vendored
Normal file
5001
assets/static/css/bootstrap-grid.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
assets/static/css/bootstrap-grid.rtl.css.map
Normal file
1
assets/static/css/bootstrap-grid.rtl.css.map
Normal file
File diff suppressed because one or more lines are too long
7
assets/static/css/bootstrap-grid.rtl.min.css
vendored
Normal file
7
assets/static/css/bootstrap-grid.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/static/css/bootstrap-grid.rtl.min.css.map
Normal file
1
assets/static/css/bootstrap-grid.rtl.min.css.map
Normal file
File diff suppressed because one or more lines are too long
426
assets/static/css/bootstrap-reboot.css
vendored
Normal file
426
assets/static/css/bootstrap-reboot.css
vendored
Normal file
@ -0,0 +1,426 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v5.0.2 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2021 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2021 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
:root {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #212529;
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: inherit;
|
||||||
|
background-color: currentColor;
|
||||||
|
border: 0;
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr:not([size]) {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6, h5, h4, h3, h2, h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h4 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title],
|
||||||
|
abbr[data-bs-original-title] {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
-webkit-text-decoration-skip-ink: none;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ul,
|
||||||
|
ol ul,
|
||||||
|
ul ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
padding: 0.2em;
|
||||||
|
background-color: #fcf8e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0d6efd;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #0a58ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
direction: ltr /* rtl:ignore */;
|
||||||
|
unicode-bidi: bidi-override;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: #d63384;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
a > code {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #212529;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
}
|
||||||
|
kbd kbd {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
caption-side: bottom;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
color: #6c757d;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
text-align: -webkit-match-parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
tr,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border-color: inherit;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus:not(:focus-visible) {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
optgroup,
|
||||||
|
textarea {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role=button] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
select:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[list]::-webkit-calendar-picker-indicator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type=button],
|
||||||
|
[type=reset],
|
||||||
|
[type=submit] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
button:not(:disabled),
|
||||||
|
[type=button]:not(:disabled),
|
||||||
|
[type=reset]:not(:disabled),
|
||||||
|
[type=submit]:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
legend {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
legend + * {
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-datetime-edit-fields-wrapper,
|
||||||
|
::-webkit-datetime-edit-text,
|
||||||
|
::-webkit-datetime-edit-minute,
|
||||||
|
::-webkit-datetime-edit-hour-field,
|
||||||
|
::-webkit-datetime-edit-day-field,
|
||||||
|
::-webkit-datetime-edit-month-field,
|
||||||
|
::-webkit-datetime-edit-year-field {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type=search] {
|
||||||
|
outline-offset: -2px;
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rtl:raw:
|
||||||
|
[type="tel"],
|
||||||
|
[type="url"],
|
||||||
|
[type="email"],
|
||||||
|
[type="number"] {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-color-swatch-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::file-selector-button {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
1
assets/static/css/bootstrap-reboot.css.map
Normal file
1
assets/static/css/bootstrap-reboot.css.map
Normal file
File diff suppressed because one or more lines are too long
8
assets/static/css/bootstrap-reboot.min.css
vendored
Normal file
8
assets/static/css/bootstrap-reboot.min.css
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v5.0.2 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2021 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2021 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
1
assets/static/css/bootstrap-reboot.min.css.map
Normal file
1
assets/static/css/bootstrap-reboot.min.css.map
Normal file
File diff suppressed because one or more lines are too long
423
assets/static/css/bootstrap-reboot.rtl.css
vendored
Normal file
423
assets/static/css/bootstrap-reboot.rtl.css
vendored
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v5.0.2 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2021 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2021 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
:root {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #212529;
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1rem 0;
|
||||||
|
color: inherit;
|
||||||
|
background-color: currentColor;
|
||||||
|
border: 0;
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr:not([size]) {
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6, h5, h4, h3, h2, h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: calc(1.375rem + 1.5vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: calc(1.325rem + 0.9vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: calc(1.3rem + 0.6vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
h4 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title],
|
||||||
|
abbr[data-bs-original-title] {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
-webkit-text-decoration-skip-ink: none;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ul,
|
||||||
|
ol ul,
|
||||||
|
ul ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
padding: 0.2em;
|
||||||
|
background-color: #fcf8e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0d6efd;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #0a58ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
direction: ltr ;
|
||||||
|
unicode-bidi: bidi-override;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 0.875em;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: #d63384;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
a > code {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #212529;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
}
|
||||||
|
kbd kbd {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
caption-side: bottom;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
color: #6c757d;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
text-align: -webkit-match-parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
tr,
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border-color: inherit;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus:not(:focus-visible) {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
optgroup,
|
||||||
|
textarea {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role=button] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
select:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[list]::-webkit-calendar-picker-indicator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type=button],
|
||||||
|
[type=reset],
|
||||||
|
[type=submit] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
button:not(:disabled),
|
||||||
|
[type=button]:not(:disabled),
|
||||||
|
[type=reset]:not(:disabled),
|
||||||
|
[type=submit]:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
float: right;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: calc(1.275rem + 0.3vw);
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
legend {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
legend + * {
|
||||||
|
clear: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-datetime-edit-fields-wrapper,
|
||||||
|
::-webkit-datetime-edit-text,
|
||||||
|
::-webkit-datetime-edit-minute,
|
||||||
|
::-webkit-datetime-edit-hour-field,
|
||||||
|
::-webkit-datetime-edit-day-field,
|
||||||
|
::-webkit-datetime-edit-month-field,
|
||||||
|
::-webkit-datetime-edit-year-field {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type=search] {
|
||||||
|
outline-offset: -2px;
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="tel"],
|
||||||
|
[type="url"],
|
||||||
|
[type="email"],
|
||||||
|
[type="number"] {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-color-swatch-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::file-selector-button {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
1
assets/static/css/bootstrap-reboot.rtl.css.map
Normal file
1
assets/static/css/bootstrap-reboot.rtl.css.map
Normal file
File diff suppressed because one or more lines are too long
8
assets/static/css/bootstrap-reboot.rtl.min.css
vendored
Normal file
8
assets/static/css/bootstrap-reboot.rtl.min.css
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v5.0.2 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2021 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2021 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */
|
1
assets/static/css/bootstrap-reboot.rtl.min.css.map
Normal file
1
assets/static/css/bootstrap-reboot.rtl.min.css.map
Normal file
File diff suppressed because one or more lines are too long
4752
assets/static/css/bootstrap-utilities.css
vendored
Normal file
4752
assets/static/css/bootstrap-utilities.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
assets/static/css/bootstrap-utilities.css.map
Normal file
1
assets/static/css/bootstrap-utilities.css.map
Normal file
File diff suppressed because one or more lines are too long
7
assets/static/css/bootstrap-utilities.min.css
vendored
Normal file
7
assets/static/css/bootstrap-utilities.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/static/css/bootstrap-utilities.min.css.map
Normal file
1
assets/static/css/bootstrap-utilities.min.css.map
Normal file
File diff suppressed because one or more lines are too long
4743
assets/static/css/bootstrap-utilities.rtl.css
vendored
Normal file
4743
assets/static/css/bootstrap-utilities.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
assets/static/css/bootstrap-utilities.rtl.css.map
Normal file
1
assets/static/css/bootstrap-utilities.rtl.css.map
Normal file
File diff suppressed because one or more lines are too long
7
assets/static/css/bootstrap-utilities.rtl.min.css
vendored
Normal file
7
assets/static/css/bootstrap-utilities.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/static/css/bootstrap-utilities.rtl.min.css.map
Normal file
1
assets/static/css/bootstrap-utilities.rtl.min.css.map
Normal file
File diff suppressed because one or more lines are too long
10837
assets/static/css/bootstrap.css
vendored
Normal file
10837
assets/static/css/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
assets/static/css/bootstrap.css.map
Normal file
1
assets/static/css/bootstrap.css.map
Normal file
File diff suppressed because one or more lines are too long
7
assets/static/css/bootstrap.min.css
vendored
Normal file
7
assets/static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/static/css/bootstrap.min.css.map
Normal file
1
assets/static/css/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
10813
assets/static/css/bootstrap.rtl.css
vendored
Normal file
10813
assets/static/css/bootstrap.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
assets/static/css/bootstrap.rtl.css.map
Normal file
1
assets/static/css/bootstrap.rtl.css.map
Normal file
File diff suppressed because one or more lines are too long
7
assets/static/css/bootstrap.rtl.min.css
vendored
Normal file
7
assets/static/css/bootstrap.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/static/css/bootstrap.rtl.min.css.map
Normal file
1
assets/static/css/bootstrap.rtl.min.css.map
Normal file
File diff suppressed because one or more lines are too long
6748
assets/static/js/bootstrap.bundle.js
vendored
Normal file
6748
assets/static/js/bootstrap.bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
assets/static/js/bootstrap.bundle.js.map
Normal file
1
assets/static/js/bootstrap.bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
7
assets/static/js/bootstrap.bundle.min.js
vendored
Normal file
7
assets/static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/static/js/bootstrap.bundle.min.js.map
Normal file
1
assets/static/js/bootstrap.bundle.min.js.map
Normal file
File diff suppressed because one or more lines are too long
4967
assets/static/js/bootstrap.esm.js
vendored
Normal file
4967
assets/static/js/bootstrap.esm.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
assets/static/js/bootstrap.esm.js.map
Normal file
1
assets/static/js/bootstrap.esm.js.map
Normal file
File diff suppressed because one or more lines are too long
7
assets/static/js/bootstrap.esm.min.js
vendored
Normal file
7
assets/static/js/bootstrap.esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/static/js/bootstrap.esm.min.js.map
Normal file
1
assets/static/js/bootstrap.esm.min.js.map
Normal file
File diff suppressed because one or more lines are too long
5016
assets/static/js/bootstrap.js
vendored
Normal file
5016
assets/static/js/bootstrap.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
assets/static/js/bootstrap.js.map
Normal file
1
assets/static/js/bootstrap.js.map
Normal file
File diff suppressed because one or more lines are too long
7
assets/static/js/bootstrap.min.js
vendored
Normal file
7
assets/static/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/static/js/bootstrap.min.js.map
Normal file
1
assets/static/js/bootstrap.min.js.map
Normal file
File diff suppressed because one or more lines are too long
29
assets/templates/page-signin.html
Normal file
29
assets/templates/page-signin.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<title>Login :: zBackup</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap core CSS -->
|
||||||
|
<link href="/assets/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="text-center">
|
||||||
|
<div class="container">
|
||||||
|
<form class="form-signin" method="POST" action="/u/submit">
|
||||||
|
<h1 class="h3 mb-3 font-weight-normal">Sign in</h1>
|
||||||
|
{{if (ne .Error "")}}<div class="alert alert-danger">{{.Error}}</div>{{end}}
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="username" class="form-control" id="username" placeholder="Username" name="username" required autofocus>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="password" class="form-control" id="pwd" placeholder="Password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-lg btn-primary btn-block" type="submit" >Sign in</button>
|
||||||
|
<p class="mt-5 mb-3 text-muted">© 2023</p>
|
||||||
|
</form>
|
||||||
|
<a href="/u/recover">Lost password</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
20
backup.go
20
backup.go
@ -9,12 +9,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfgFile = flag.String("config", "backup.json", "config file")
|
cfgFile = flag.String("config", "", "config file")
|
||||||
isDaemon = flag.Bool("daemon", false, "run as daemon")
|
isDaemon = flag.Bool("daemon", false, "run as daemon")
|
||||||
debug = flag.Bool("debug", false, "log debug messages")
|
debug = flag.Bool("debug", false, "log debug messages")
|
||||||
quiet = flag.Bool("quiet", false, "remove most log messages")
|
quiet = flag.Bool("quiet", false, "remove most log messages")
|
||||||
logFile = flag.String("logfile", "", "log file")
|
logFile = flag.String("logfile", "", "log file")
|
||||||
cfg Config
|
cfg *Config
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -39,15 +39,29 @@ func main() {
|
|||||||
}
|
}
|
||||||
log.SetReportCaller(true)
|
log.SetReportCaller(true)
|
||||||
|
|
||||||
if err := cfg.LoadFile(*cfgFile); err != nil {
|
if *cfgFile != "" {
|
||||||
|
if c, err := LoadConfigFile(*cfgFile); err != nil {
|
||||||
log.Printf("Cannot load config (%s)", err)
|
log.Printf("Cannot load config (%s)", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
cfg = c
|
||||||
|
}
|
||||||
|
} else if c, err := LoadConfigFile("backup.json"); err == nil {
|
||||||
|
cfg = c
|
||||||
|
} else {
|
||||||
|
cfg, _ = LoadConfigByte(sampleCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *isDaemon {
|
if *isDaemon {
|
||||||
if cfg.Admin == nil {
|
if cfg.Admin == nil {
|
||||||
cfg.Admin = NewAdmin()
|
cfg.Admin = NewAdmin()
|
||||||
}
|
}
|
||||||
|
if cfg.Admin.Secrets == nil {
|
||||||
|
cfg.Admin.Secrets = NewSecrets()
|
||||||
|
}
|
||||||
|
if len(cfg.Admin.Users) == 0 {
|
||||||
|
cfg.Admin.NewAdminUser()
|
||||||
|
}
|
||||||
cfg.Admin.Run()
|
cfg.Admin.Run()
|
||||||
} else {
|
} else {
|
||||||
cfg.Run()
|
cfg.Run()
|
||||||
|
121
config.go
121
config.go
@ -1,10 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -26,6 +28,14 @@ type Config struct {
|
|||||||
timezone *time.Location `json:"-"`
|
timezone *time.Location `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfgMx sync.Mutex
|
||||||
|
cfgRun bool
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed assets/backup.sample.json
|
||||||
|
var sampleCfg []byte
|
||||||
|
|
||||||
type BoxConfig struct {
|
type BoxConfig struct {
|
||||||
Addr string `json:"addr"`
|
Addr string `json:"addr"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
@ -43,49 +53,66 @@ type AppConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load config from file
|
// Load config from file
|
||||||
func (c *Config) LoadFile(path string) error {
|
func LoadConfigFile(path string) (*Config, error) {
|
||||||
log.WithFields(log.Fields{"path": path}).Debugf("starting")
|
log.WithFields(log.Fields{"path": path}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{"path": path}).Debugf("done")
|
defer log.WithFields(log.Fields{"path": path}).Debugf("done")
|
||||||
|
|
||||||
b, err := ioutil.ReadFile(path)
|
b, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{"path": path, "error": err, "call": "ioutil.ReadFile"}).Errorf("")
|
log.WithFields(log.Fields{"path": path, "error": err, "call": "os.ReadFile"}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err = hujson.Standardize(b)
|
return LoadConfigByte(b)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load config from string
|
||||||
|
func LoadConfigByte(conf []byte) (*Config, error) {
|
||||||
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
c := &Config{}
|
||||||
|
if err := json.Unmarshal(sampleCfg, c); err != nil {
|
||||||
|
log.WithFields(log.Fields{"error": err, "call": "json.Unmarshal", "attr": "sampleCfg"}).Errorf("")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := hujson.Standardize(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{"path": path, "error": err, "call": "hujson.Standardize"}).Errorf("")
|
log.WithFields(log.Fields{"error": err, "call": "hujson.Standardize"}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(b, &c); err != nil {
|
if err := json.Unmarshal(b, c); err != nil {
|
||||||
log.WithFields(log.Fields{"path": path, "error": err, "call": "json.Unmarshal"}).Errorf("")
|
log.WithFields(log.Fields{"error": err, "call": "json.Unmarshal"}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.timezone, err = time.LoadLocation(cfg.Timezone)
|
c.timezone, err = time.LoadLocation(c.Timezone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{"path": path, "error": err, "call": "time.LoadLocation", "attr": cfg.Timezone}).Errorf("")
|
log.WithFields(log.Fields{"error": err, "call": "time.LoadLocation", "attr": cfg.Timezone}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Email.SmtpHost) == 0 {
|
if c.Email.Active {
|
||||||
|
if len(c.Email.SmtpHost) == 0 {
|
||||||
err := fmt.Errorf("no smtp")
|
err := fmt.Errorf("no smtp")
|
||||||
log.WithFields(log.Fields{"path": path, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"error": err}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Email.FromEmail) == 0 {
|
if len(c.Email.FromEmail) == 0 {
|
||||||
err := fmt.Errorf("no email from")
|
err := fmt.Errorf("no email from")
|
||||||
log.WithFields(log.Fields{"path": path, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"error": err}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Email.ToEmail) == 0 {
|
if len(c.Email.ToEmail) == 0 {
|
||||||
err := fmt.Errorf("no email to")
|
err := fmt.Errorf("no email to")
|
||||||
log.WithFields(log.Fields{"path": path, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"error": err}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range c.ScheduleDuration {
|
for k, v := range c.ScheduleDuration {
|
||||||
@ -96,26 +123,26 @@ func (c *Config) LoadFile(path string) error {
|
|||||||
case "monthly":
|
case "monthly":
|
||||||
case "yearly":
|
case "yearly":
|
||||||
if _, err := Expiration(time.Now(), v); err != nil {
|
if _, err := Expiration(time.Now(), v); err != nil {
|
||||||
log.WithFields(log.Fields{"path": path, "schedule": k, "deadline": v, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"schedule": k, "deadline": v, "error": err}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err := errors.New("invalid schedule")
|
err := errors.New("invalid schedule")
|
||||||
log.WithFields(log.Fields{"path": path, "schedule": k, "deadline": v, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"schedule": k, "deadline": v, "error": err}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.box = make(map[string]*Box)
|
c.box = make(map[string]*Box)
|
||||||
for k, v := range c.Box {
|
for k, v := range c.Box {
|
||||||
if b, err := c.NewBox(k, v.Addr, v.User, v.Key); err != nil {
|
if b, err := c.NewBox(k, v.Addr, v.User, v.Key); err != nil {
|
||||||
log.WithFields(log.Fields{"path": path, "call": "NewBox", "attr": k, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"call": "NewBox", "attr": k, "error": err}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
if _, ok := c.box[k]; ok {
|
if _, ok := c.box[k]; ok {
|
||||||
err := errors.New("already exists")
|
err := errors.New("already exists")
|
||||||
log.WithFields(log.Fields{"path": path, "attr": k, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"attr": k, "error": err}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.box[k] = b
|
c.box[k] = b
|
||||||
}
|
}
|
||||||
@ -124,33 +151,33 @@ func (c *Config) LoadFile(path string) error {
|
|||||||
c.apps = make(map[string]*App)
|
c.apps = make(map[string]*App)
|
||||||
for _, v := range c.Apps {
|
for _, v := range c.Apps {
|
||||||
if a, err := c.NewApp(v.Name, v.Sources, v.Destinations, v.Schedule, v.Before, v.After); err != nil {
|
if a, err := c.NewApp(v.Name, v.Sources, v.Destinations, v.Schedule, v.Before, v.After); err != nil {
|
||||||
log.WithFields(log.Fields{"path": path, "call": "NewApp", "attr": v.Name, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"call": "NewApp", "attr": v.Name, "error": err}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
if _, ok := c.apps[v.Name]; ok {
|
if _, ok := c.apps[v.Name]; ok {
|
||||||
err := errors.New("app already exists")
|
err := errors.New("app already exists")
|
||||||
log.WithFields(log.Fields{"path": path, "app": v.Name, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"app": v.Name, "error": err}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.apps[v.Name] = a
|
c.apps[v.Name] = a
|
||||||
for k, _ := range a.schedule {
|
for k := range a.schedule {
|
||||||
if dur, ok := c.ScheduleDuration[k]; ok {
|
if dur, ok := c.ScheduleDuration[k]; ok {
|
||||||
re := regexp.MustCompile(`^forever|([0-9]+(h|d|m|y))+$`)
|
re := regexp.MustCompile(`^forever|([0-9]+(h|d|m|y))+$`)
|
||||||
if !re.MatchString(dur) {
|
if !re.MatchString(dur) {
|
||||||
err := errors.New("incorrect schedule duration")
|
err := errors.New("incorrect schedule duration")
|
||||||
log.WithFields(log.Fields{"path": path, "app": v.Name, "schedule": k, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"app": v.Name, "schedule": k, "error": err}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := errors.New("undefined schedule duration")
|
err := errors.New("undefined schedule duration")
|
||||||
log.WithFields(log.Fields{"path": path, "app": v.Name, "schedule": k, "error": err}).Errorf("")
|
log.WithFields(log.Fields{"app": v.Name, "schedule": k, "error": err}).Errorf("")
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run config
|
// Run config
|
||||||
@ -158,6 +185,16 @@ func (c *Config) Run() {
|
|||||||
log.WithFields(log.Fields{}).Debugf("starting")
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{}).Debugf("done")
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
|
if cfgRun {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgMx.Lock()
|
||||||
|
defer cfgMx.Unlock()
|
||||||
|
|
||||||
|
cfgRun = true
|
||||||
|
defer func() { cfgRun = false }()
|
||||||
|
|
||||||
e := NewEmail(time.Now())
|
e := NewEmail(time.Now())
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@ -240,7 +277,7 @@ func (c *Config) Run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(e.items) > 0 {
|
if len(e.items) > 0 {
|
||||||
if err := e.Send(); err != nil {
|
if err := e.Send(cfg.Email.SmtpHost, cfg.Email.FromEmail, cfg.Email.ToEmail); err != nil {
|
||||||
log.WithFields(log.Fields{"call": "email.Send", "error": err}).Errorf("")
|
log.WithFields(log.Fields{"call": "email.Send", "error": err}).Errorf("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
email.go
7
email.go
@ -19,6 +19,7 @@ type Email struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EmailConfig struct {
|
type EmailConfig struct {
|
||||||
|
Active bool `json:"active"`
|
||||||
SmtpHost string `json:"smtp"`
|
SmtpHost string `json:"smtp"`
|
||||||
FromEmail string `json:"email_from"`
|
FromEmail string `json:"email_from"`
|
||||||
ToEmail []string `json:"email_to"`
|
ToEmail []string `json:"email_to"`
|
||||||
@ -38,7 +39,7 @@ func (e *Email) AddItem(item string) {
|
|||||||
e.items = append(e.items, item)
|
e.items = append(e.items, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Email) Send() error {
|
func (e *Email) Send(addr, from string, to []string) error {
|
||||||
log.WithFields(log.Fields{}).Debugf("starting")
|
log.WithFields(log.Fields{}).Debugf("starting")
|
||||||
defer log.WithFields(log.Fields{}).Debugf("done")
|
defer log.WithFields(log.Fields{}).Debugf("done")
|
||||||
|
|
||||||
@ -57,8 +58,8 @@ func (e *Email) Send() error {
|
|||||||
|
|
||||||
subject := fmt.Sprintf("Autobackup report (%s)", e.startTime)
|
subject := fmt.Sprintf("Autobackup report (%s)", e.startTime)
|
||||||
|
|
||||||
if err := SendMail(cfg.Email.SmtpHost, cfg.Email.FromEmail, subject, body, cfg.Email.ToEmail); err != nil {
|
if err := SendMail(addr, from, subject, body, to); err != nil {
|
||||||
log.WithFields(log.Fields{"addr": cfg.Email.SmtpHost, "from": cfg.Email.FromEmail, "subject": subject, "call": "SendMail", "error": err}).Errorf("")
|
log.WithFields(log.Fields{"addr": addr, "from": from, "subject": subject, "call": "SendMail", "error": err}).Errorf("")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
67
http.go
Normal file
67
http.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HttpAuth() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Search signed-in userID
|
||||||
|
userID := 0
|
||||||
|
if userID == 0 {
|
||||||
|
// Return 404 and abort handlers chain.
|
||||||
|
c.String(http.StatusNotFound, "404 page not found")
|
||||||
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HttpNoAuth() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HttpAnySignIn(c *gin.Context) {
|
||||||
|
if GetWebSessionUserID(c) > 0 {
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, "/p/home")
|
||||||
|
} else {
|
||||||
|
SetCSRFToken(c)
|
||||||
|
warning, _ := c.Cookie("warning")
|
||||||
|
c.SetCookie("warning", "", -1, "/", cfg.Admin.Addr, false, true)
|
||||||
|
c.HTML(http.StatusOK, "page-signin.html", gin.H{
|
||||||
|
"Error": warning,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func HttpAnyIndex(c *gin.Context) {
|
||||||
|
if GetWebSessionUserID(c) > 0 {
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, "/p/home")
|
||||||
|
} else {
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, "/u/signin")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func HttpAnyHome(c *gin.Context) {
|
||||||
|
if GetWebSessionUserID(c) == 0 {
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, "/u/signin")
|
||||||
|
} else {
|
||||||
|
SetCSRFToken(c)
|
||||||
|
c.HTML(http.StatusOK, "page-home.html", gin.H{})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWebSessionUserID(c *gin.Context) int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetCSRFToken(c *gin.Context) {
|
||||||
|
return
|
||||||
|
}
|
7
ssh.go
7
ssh.go
@ -108,11 +108,16 @@ func (s *Ssh) Exec(cmd string) (string, error) {
|
|||||||
}
|
}
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
|
|
||||||
|
if session.Setenv("TZ", cfg.Timezone); err != nil {
|
||||||
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Setenv", "error": err}).Errorf("")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
var bufout, buferr bytes.Buffer
|
var bufout, buferr bytes.Buffer
|
||||||
session.Stdout = &bufout
|
session.Stdout = &bufout
|
||||||
session.Stderr = &buferr
|
session.Stderr = &buferr
|
||||||
|
|
||||||
err = session.Run("TZ=\"" + cfg.Timezone + "\" " + cmd)
|
err = session.Run(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Run", "error": err, "stderr": buferr.String()}).Errorf("")
|
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Run", "error": err, "stderr": buferr.String()}).Errorf("")
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Code generated by version.sh (@generated) DO NOT EDIT.
|
// Code generated by version.sh (@generated) DO NOT EDIT.
|
||||||
package main
|
package main
|
||||||
var githash = "ab4bfd0"
|
var githash = "b07a745"
|
||||||
var branch = "v2"
|
var branch = "v2"
|
||||||
var buildstamp = "2023-08-01_09:17:25"
|
var buildstamp = "2023-08-17_17:25:15"
|
||||||
var commits = "81"
|
var commits = "82"
|
||||||
var version = "ab4bfd0-b81 - 2023-08-01_09:17:25"
|
var version = "b07a745-b82 - 2023-08-17_17:25:15"
|
||||||
|
Loading…
Reference in New Issue
Block a user