[2024-07-11](INIT): first commit

This commit is contained in:
juneflbjpz 2024-07-11 21:25:58 +08:00
commit 2a87eab550
26 changed files with 3436 additions and 0 deletions

33
corelib/response.go Normal file
View File

@ -0,0 +1,33 @@
package corelib
import (
"encoding/json"
"net/http"
)
// JsonResponse 统一的JSON响应结构体
type JsonResponse struct {
Status int `json:"status"`
Message string `json:"message"`
Data any `json:"data,omitempty"` // 使用 any 类型来存储任意类型的数据
}
// WriteJsonResponse 用于写入JSON响应
func WriteJsonResponse(w http.ResponseWriter, status int, message string, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
response := JsonResponse{
Status: status,
Message: message,
Data: data,
}
err := json.NewEncoder(w).Encode(response)
if err != nil {
panic(err)
return
}
}
func Success(w http.ResponseWriter) {
WriteJsonResponse(w, 200, "OK", nil)
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module picgo
go 1.22
require github.com/gorilla/mux v1.8.1

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=

1
handler/index.go Normal file
View File

@ -0,0 +1 @@
package handler

1
handler/login.go Normal file
View File

@ -0,0 +1 @@
package handler

34
handler/static.go Normal file
View File

@ -0,0 +1,34 @@
package handler
import (
"net/http"
"os"
"path/filepath"
)
func StaticHandler(w http.ResponseWriter, r *http.Request) {
// 文件服务器的根目录
root := "./static"
// 自定义文件处理器
fileHandler := http.StripPrefix("/static/", http.FileServer(http.Dir(root)))
// 获取请求的路径
path := filepath.Join(root, r.URL.Path[len("/static/"):])
// 检查路径是否是一个目录
info, err := os.Stat(path)
if err != nil {
// 如果文件不存在返回404
http.NotFound(w, r)
return
}
if info.IsDir() {
// 如果是目录返回404
http.NotFound(w, r)
return
}
// 处理文件
fileHandler.ServeHTTP(w, r)
}

53
handler/upload.go Normal file
View File

@ -0,0 +1,53 @@
package handler
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"picgo/corelib"
)
func UploadFileHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
// 解析表单数据限制最大内存为32MB
err := r.ParseMultipartForm(32 << 20)
if err != nil {
corelib.WriteJsonResponse(w, 1020, "文件大于32MB", nil)
return
}
// 获取文件句柄
file, handler, err := r.FormFile("file")
if err != nil {
fmt.Println("Error retrieving the file")
fmt.Println(err)
corelib.WriteJsonResponse(w, 1021, "获取文件失败", nil)
return
}
defer file.Close()
// 保存文件到本地
f, err := os.OpenFile(filepath.Join("./static/pic", handler.Filename), os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println("Error saving file")
fmt.Println(err)
corelib.WriteJsonResponse(w, 1023, "创建文件失败", nil)
return
}
defer f.Close()
// 将文件内容拷贝到本地文件
_, err = io.Copy(f, file)
if err != nil {
fmt.Println("Error copying file")
fmt.Println(err)
corelib.WriteJsonResponse(w, 1024, "保存文件失败", nil)
return
}
//fmt.Fprintf(w, "File %s uploaded successfully!", handler.Filename)
corelib.Success(w)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}

18
main.go Normal file
View File

@ -0,0 +1,18 @@
package main
import (
"log"
"net/http"
"picgo/router"
)
func main() {
// 创建路由
r := router.InitRouter()
// 启动HTTP服务器
log.Println("Serving on http://localhost:8082")
err := http.ListenAndServe(":8082", r)
if err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}

15
router/router.go Normal file
View File

@ -0,0 +1,15 @@
package router
import (
"net/http"
"picgo/handler"
)
func InitRouter() *http.ServeMux {
var mux *http.ServeMux
// 创建新的路由器
mux = http.NewServeMux()
mux.HandleFunc("/static/", handler.StaticHandler)
mux.HandleFunc("/api/v1/upload", handler.UploadFileHandler)
return mux
}

1704
static/css/bootstrap-icons.css vendored Normal file

File diff suppressed because it is too large Load Diff

7
static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

933
static/css/sweetalert.css Normal file
View File

@ -0,0 +1,933 @@
body.stop-scrolling {
height: 100%;
overflow: hidden; }
.sweet-overlay {
background-color: black;
/* IE8 */
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";
/* IE8 */
background-color: rgba(0, 0, 0, 0.4);
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: none;
z-index: 10000; }
.sweet-alert {
background-color: white;
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
width: 478px;
padding: 17px;
border-radius: 5px;
text-align: center;
position: fixed;
left: 50%;
top: 50%;
margin-left: -256px;
margin-top: -200px;
overflow: hidden;
display: none;
z-index: 99999; }
@media all and (max-width: 540px) {
.sweet-alert {
width: auto;
margin-left: 0;
margin-right: 0;
left: 15px;
right: 15px; } }
.sweet-alert h2 {
color: #575757;
font-size: 30px;
text-align: center;
font-weight: 600;
text-transform: none;
position: relative;
margin: 25px 0;
padding: 0;
line-height: 40px;
display: block; }
.sweet-alert p {
color: #797979;
font-size: 16px;
text-align: center;
font-weight: 300;
position: relative;
text-align: inherit;
float: none;
margin: 0;
padding: 0;
line-height: normal; }
.sweet-alert fieldset {
border: none;
position: relative; }
.sweet-alert .sa-error-container {
background-color: #f1f1f1;
margin-left: -17px;
margin-right: -17px;
overflow: hidden;
padding: 0 10px;
max-height: 0;
webkit-transition: padding 0.15s, max-height 0.15s;
transition: padding 0.15s, max-height 0.15s; }
.sweet-alert .sa-error-container.show {
padding: 10px 0;
max-height: 100px;
webkit-transition: padding 0.2s, max-height 0.2s;
transition: padding 0.25s, max-height 0.25s; }
.sweet-alert .sa-error-container .icon {
display: inline-block;
width: 24px;
height: 24px;
border-radius: 50%;
background-color: #ea7d7d;
color: white;
line-height: 24px;
text-align: center;
margin-right: 3px; }
.sweet-alert .sa-error-container p {
display: inline-block; }
.sweet-alert .sa-input-error {
position: absolute;
top: 29px;
right: 26px;
width: 20px;
height: 20px;
opacity: 0;
-webkit-transform: scale(0.5);
transform: scale(0.5);
-webkit-transform-origin: 50% 50%;
transform-origin: 50% 50%;
-webkit-transition: all 0.1s;
transition: all 0.1s; }
.sweet-alert .sa-input-error::before, .sweet-alert .sa-input-error::after {
content: "";
width: 20px;
height: 6px;
background-color: #f06e57;
border-radius: 3px;
position: absolute;
top: 50%;
margin-top: -4px;
left: 50%;
margin-left: -9px; }
.sweet-alert .sa-input-error::before {
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg); }
.sweet-alert .sa-input-error::after {
-webkit-transform: rotate(45deg);
transform: rotate(45deg); }
.sweet-alert .sa-input-error.show {
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1); }
.sweet-alert input {
width: 100%;
box-sizing: border-box;
border-radius: 3px;
border: 1px solid #d7d7d7;
height: 43px;
margin-top: 10px;
margin-bottom: 17px;
font-size: 18px;
box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.06);
padding: 0 12px;
display: none;
-webkit-transition: all 0.3s;
transition: all 0.3s; }
.sweet-alert input:focus {
outline: none;
box-shadow: 0px 0px 3px #c4e6f5;
border: 1px solid #b4dbed; }
.sweet-alert input:focus::-moz-placeholder {
transition: opacity 0.3s 0.03s ease;
opacity: 0.5; }
.sweet-alert input:focus:-ms-input-placeholder {
transition: opacity 0.3s 0.03s ease;
opacity: 0.5; }
.sweet-alert input:focus::-webkit-input-placeholder {
transition: opacity 0.3s 0.03s ease;
opacity: 0.5; }
.sweet-alert input::-moz-placeholder {
color: #bdbdbd; }
.sweet-alert input:-ms-input-placeholder {
color: #bdbdbd; }
.sweet-alert input::-webkit-input-placeholder {
color: #bdbdbd; }
.sweet-alert.show-input input {
display: block; }
.sweet-alert .sa-confirm-button-container {
display: inline-block;
position: relative; }
.sweet-alert .la-ball-fall {
position: absolute;
left: 50%;
top: 50%;
margin-left: -27px;
margin-top: 4px;
opacity: 0;
visibility: hidden; }
.sweet-alert button {
background-color: #8CD4F5;
color: white;
border: none;
box-shadow: none;
font-size: 17px;
font-weight: 500;
-webkit-border-radius: 4px;
border-radius: 5px;
padding: 10px 32px;
margin: 26px 5px 0 5px;
cursor: pointer; }
.sweet-alert button:focus {
outline: none;
box-shadow: 0 0 2px rgba(128, 179, 235, 0.5), inset 0 0 0 1px rgba(0, 0, 0, 0.05); }
.sweet-alert button:hover {
background-color: #7ecff4; }
.sweet-alert button:active {
background-color: #5dc2f1; }
.sweet-alert button.cancel {
background-color: #C1C1C1; }
.sweet-alert button.cancel:hover {
background-color: #b9b9b9; }
.sweet-alert button.cancel:active {
background-color: #a8a8a8; }
.sweet-alert button.cancel:focus {
box-shadow: rgba(197, 205, 211, 0.8) 0px 0px 2px, rgba(0, 0, 0, 0.0470588) 0px 0px 0px 1px inset !important; }
.sweet-alert button[disabled] {
opacity: .6;
cursor: default; }
.sweet-alert button.confirm[disabled] {
color: transparent; }
.sweet-alert button.confirm[disabled] ~ .la-ball-fall {
opacity: 1;
visibility: visible;
transition-delay: 0s; }
.sweet-alert button::-moz-focus-inner {
border: 0; }
.sweet-alert[data-has-cancel-button=false] button {
box-shadow: none !important; }
.sweet-alert[data-has-confirm-button=false][data-has-cancel-button=false] {
padding-bottom: 40px; }
.sweet-alert .sa-icon {
width: 80px;
height: 80px;
border: 4px solid gray;
-webkit-border-radius: 40px;
border-radius: 40px;
border-radius: 50%;
margin: 20px auto;
padding: 0;
position: relative;
box-sizing: content-box; }
.sweet-alert .sa-icon.sa-error {
border-color: #F27474; }
.sweet-alert .sa-icon.sa-error .sa-x-mark {
position: relative;
display: block; }
.sweet-alert .sa-icon.sa-error .sa-line {
position: absolute;
height: 5px;
width: 47px;
background-color: #F27474;
display: block;
top: 37px;
border-radius: 2px; }
.sweet-alert .sa-icon.sa-error .sa-line.sa-left {
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
left: 17px; }
.sweet-alert .sa-icon.sa-error .sa-line.sa-right {
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
right: 16px; }
.sweet-alert .sa-icon.sa-warning {
border-color: #F8BB86; }
.sweet-alert .sa-icon.sa-warning .sa-body {
position: absolute;
width: 5px;
height: 47px;
left: 50%;
top: 10px;
-webkit-border-radius: 2px;
border-radius: 2px;
margin-left: -2px;
background-color: #F8BB86; }
.sweet-alert .sa-icon.sa-warning .sa-dot {
position: absolute;
width: 7px;
height: 7px;
-webkit-border-radius: 50%;
border-radius: 50%;
margin-left: -3px;
left: 50%;
bottom: 10px;
background-color: #F8BB86; }
.sweet-alert .sa-icon.sa-info {
border-color: #C9DAE1; }
.sweet-alert .sa-icon.sa-info::before {
content: "";
position: absolute;
width: 5px;
height: 29px;
left: 50%;
bottom: 17px;
border-radius: 2px;
margin-left: -2px;
background-color: #C9DAE1; }
.sweet-alert .sa-icon.sa-info::after {
content: "";
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
margin-left: -3px;
top: 19px;
background-color: #C9DAE1;
left: 50%; }
.sweet-alert .sa-icon.sa-success {
border-color: #A5DC86; }
.sweet-alert .sa-icon.sa-success::before, .sweet-alert .sa-icon.sa-success::after {
content: '';
-webkit-border-radius: 40px;
border-radius: 40px;
border-radius: 50%;
position: absolute;
width: 60px;
height: 120px;
background: white;
-webkit-transform: rotate(45deg);
transform: rotate(45deg); }
.sweet-alert .sa-icon.sa-success::before {
-webkit-border-radius: 120px 0 0 120px;
border-radius: 120px 0 0 120px;
top: -7px;
left: -33px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-transform-origin: 60px 60px;
transform-origin: 60px 60px; }
.sweet-alert .sa-icon.sa-success::after {
-webkit-border-radius: 0 120px 120px 0;
border-radius: 0 120px 120px 0;
top: -11px;
left: 30px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-transform-origin: 0px 60px;
transform-origin: 0px 60px; }
.sweet-alert .sa-icon.sa-success .sa-placeholder {
width: 80px;
height: 80px;
border: 4px solid rgba(165, 220, 134, 0.2);
-webkit-border-radius: 40px;
border-radius: 40px;
border-radius: 50%;
box-sizing: content-box;
position: absolute;
left: -4px;
top: -4px;
z-index: 2; }
.sweet-alert .sa-icon.sa-success .sa-fix {
width: 5px;
height: 90px;
background-color: white;
position: absolute;
left: 28px;
top: 8px;
z-index: 1;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg); }
.sweet-alert .sa-icon.sa-success .sa-line {
height: 5px;
background-color: #A5DC86;
display: block;
border-radius: 2px;
position: absolute;
z-index: 2; }
.sweet-alert .sa-icon.sa-success .sa-line.sa-tip {
width: 25px;
left: 14px;
top: 46px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg); }
.sweet-alert .sa-icon.sa-success .sa-line.sa-long {
width: 47px;
right: 8px;
top: 38px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg); }
.sweet-alert .sa-icon.sa-custom {
background-size: contain;
border-radius: 0;
border: none;
background-position: center center;
background-repeat: no-repeat; }
/*
* Animations
*/
@-webkit-keyframes showSweetAlert {
0% {
transform: scale(0.7);
-webkit-transform: scale(0.7); }
45% {
transform: scale(1.05);
-webkit-transform: scale(1.05); }
80% {
transform: scale(0.95);
-webkit-transform: scale(0.95); }
100% {
transform: scale(1);
-webkit-transform: scale(1); } }
@keyframes showSweetAlert {
0% {
transform: scale(0.7);
-webkit-transform: scale(0.7); }
45% {
transform: scale(1.05);
-webkit-transform: scale(1.05); }
80% {
transform: scale(0.95);
-webkit-transform: scale(0.95); }
100% {
transform: scale(1);
-webkit-transform: scale(1); } }
@-webkit-keyframes hideSweetAlert {
0% {
transform: scale(1);
-webkit-transform: scale(1); }
100% {
transform: scale(0.5);
-webkit-transform: scale(0.5); } }
@keyframes hideSweetAlert {
0% {
transform: scale(1);
-webkit-transform: scale(1); }
100% {
transform: scale(0.5);
-webkit-transform: scale(0.5); } }
@-webkit-keyframes slideFromTop {
0% {
top: 0%; }
100% {
top: 50%; } }
@keyframes slideFromTop {
0% {
top: 0%; }
100% {
top: 50%; } }
@-webkit-keyframes slideToTop {
0% {
top: 50%; }
100% {
top: 0%; } }
@keyframes slideToTop {
0% {
top: 50%; }
100% {
top: 0%; } }
@-webkit-keyframes slideFromBottom {
0% {
top: 70%; }
100% {
top: 50%; } }
@keyframes slideFromBottom {
0% {
top: 70%; }
100% {
top: 50%; } }
@-webkit-keyframes slideToBottom {
0% {
top: 50%; }
100% {
top: 70%; } }
@keyframes slideToBottom {
0% {
top: 50%; }
100% {
top: 70%; } }
.showSweetAlert[data-animation=pop] {
-webkit-animation: showSweetAlert 0.3s;
animation: showSweetAlert 0.3s; }
.showSweetAlert[data-animation=none] {
-webkit-animation: none;
animation: none; }
.showSweetAlert[data-animation=slide-from-top] {
-webkit-animation: slideFromTop 0.3s;
animation: slideFromTop 0.3s; }
.showSweetAlert[data-animation=slide-from-bottom] {
-webkit-animation: slideFromBottom 0.3s;
animation: slideFromBottom 0.3s; }
.hideSweetAlert[data-animation=pop] {
-webkit-animation: hideSweetAlert 0.2s;
animation: hideSweetAlert 0.2s; }
.hideSweetAlert[data-animation=none] {
-webkit-animation: none;
animation: none; }
.hideSweetAlert[data-animation=slide-from-top] {
-webkit-animation: slideToTop 0.4s;
animation: slideToTop 0.4s; }
.hideSweetAlert[data-animation=slide-from-bottom] {
-webkit-animation: slideToBottom 0.3s;
animation: slideToBottom 0.3s; }
@-webkit-keyframes animateSuccessTip {
0% {
width: 0;
left: 1px;
top: 19px; }
54% {
width: 0;
left: 1px;
top: 19px; }
70% {
width: 50px;
left: -8px;
top: 37px; }
84% {
width: 17px;
left: 21px;
top: 48px; }
100% {
width: 25px;
left: 14px;
top: 45px; } }
@keyframes animateSuccessTip {
0% {
width: 0;
left: 1px;
top: 19px; }
54% {
width: 0;
left: 1px;
top: 19px; }
70% {
width: 50px;
left: -8px;
top: 37px; }
84% {
width: 17px;
left: 21px;
top: 48px; }
100% {
width: 25px;
left: 14px;
top: 45px; } }
@-webkit-keyframes animateSuccessLong {
0% {
width: 0;
right: 46px;
top: 54px; }
65% {
width: 0;
right: 46px;
top: 54px; }
84% {
width: 55px;
right: 0px;
top: 35px; }
100% {
width: 47px;
right: 8px;
top: 38px; } }
@keyframes animateSuccessLong {
0% {
width: 0;
right: 46px;
top: 54px; }
65% {
width: 0;
right: 46px;
top: 54px; }
84% {
width: 55px;
right: 0px;
top: 35px; }
100% {
width: 47px;
right: 8px;
top: 38px; } }
@-webkit-keyframes rotatePlaceholder {
0% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg); }
5% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg); }
12% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg); }
100% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg); } }
@keyframes rotatePlaceholder {
0% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg); }
5% {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg); }
12% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg); }
100% {
transform: rotate(-405deg);
-webkit-transform: rotate(-405deg); } }
.animateSuccessTip {
-webkit-animation: animateSuccessTip 0.75s;
animation: animateSuccessTip 0.75s; }
.animateSuccessLong {
-webkit-animation: animateSuccessLong 0.75s;
animation: animateSuccessLong 0.75s; }
.sa-icon.sa-success.animate::after {
-webkit-animation: rotatePlaceholder 4.25s ease-in;
animation: rotatePlaceholder 4.25s ease-in; }
@-webkit-keyframes animateErrorIcon {
0% {
transform: rotateX(100deg);
-webkit-transform: rotateX(100deg);
opacity: 0; }
100% {
transform: rotateX(0deg);
-webkit-transform: rotateX(0deg);
opacity: 1; } }
@keyframes animateErrorIcon {
0% {
transform: rotateX(100deg);
-webkit-transform: rotateX(100deg);
opacity: 0; }
100% {
transform: rotateX(0deg);
-webkit-transform: rotateX(0deg);
opacity: 1; } }
.animateErrorIcon {
-webkit-animation: animateErrorIcon 0.5s;
animation: animateErrorIcon 0.5s; }
@-webkit-keyframes animateXMark {
0% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0; }
50% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0; }
80% {
transform: scale(1.15);
-webkit-transform: scale(1.15);
margin-top: -6px; }
100% {
transform: scale(1);
-webkit-transform: scale(1);
margin-top: 0;
opacity: 1; } }
@keyframes animateXMark {
0% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0; }
50% {
transform: scale(0.4);
-webkit-transform: scale(0.4);
margin-top: 26px;
opacity: 0; }
80% {
transform: scale(1.15);
-webkit-transform: scale(1.15);
margin-top: -6px; }
100% {
transform: scale(1);
-webkit-transform: scale(1);
margin-top: 0;
opacity: 1; } }
.animateXMark {
-webkit-animation: animateXMark 0.5s;
animation: animateXMark 0.5s; }
@-webkit-keyframes pulseWarning {
0% {
border-color: #F8D486; }
100% {
border-color: #F8BB86; } }
@keyframes pulseWarning {
0% {
border-color: #F8D486; }
100% {
border-color: #F8BB86; } }
.pulseWarning {
-webkit-animation: pulseWarning 0.75s infinite alternate;
animation: pulseWarning 0.75s infinite alternate; }
@-webkit-keyframes pulseWarningIns {
0% {
background-color: #F8D486; }
100% {
background-color: #F8BB86; } }
@keyframes pulseWarningIns {
0% {
background-color: #F8D486; }
100% {
background-color: #F8BB86; } }
.pulseWarningIns {
-webkit-animation: pulseWarningIns 0.75s infinite alternate;
animation: pulseWarningIns 0.75s infinite alternate; }
@-webkit-keyframes rotate-loading {
0% {
transform: rotate(0deg); }
100% {
transform: rotate(360deg); } }
@keyframes rotate-loading {
0% {
transform: rotate(0deg); }
100% {
transform: rotate(360deg); } }
/* Internet Explorer 9 has some special quirks that are fixed here */
/* The icons are not animated. */
/* This file is automatically merged into sweet-alert.min.js through Gulp */
/* Error icon */
.sweet-alert .sa-icon.sa-error .sa-line.sa-left {
-ms-transform: rotate(45deg) \9; }
.sweet-alert .sa-icon.sa-error .sa-line.sa-right {
-ms-transform: rotate(-45deg) \9; }
/* Success icon */
.sweet-alert .sa-icon.sa-success {
border-color: transparent\9; }
.sweet-alert .sa-icon.sa-success .sa-line.sa-tip {
-ms-transform: rotate(45deg) \9; }
.sweet-alert .sa-icon.sa-success .sa-line.sa-long {
-ms-transform: rotate(-45deg) \9; }
/*!
* Load Awesome v1.1.0 (http://github.danielcardoso.net/load-awesome/)
* Copyright 2015 Daniel Cardoso <@DanielCardoso>
* Licensed under MIT
*/
.la-ball-fall,
.la-ball-fall > div {
position: relative;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box; }
.la-ball-fall {
display: block;
font-size: 0;
color: #fff; }
.la-ball-fall.la-dark {
color: #333; }
.la-ball-fall > div {
display: inline-block;
float: none;
background-color: currentColor;
border: 0 solid currentColor; }
.la-ball-fall {
width: 54px;
height: 18px; }
.la-ball-fall > div {
width: 10px;
height: 10px;
margin: 4px;
border-radius: 100%;
opacity: 0;
-webkit-animation: ball-fall 1s ease-in-out infinite;
-moz-animation: ball-fall 1s ease-in-out infinite;
-o-animation: ball-fall 1s ease-in-out infinite;
animation: ball-fall 1s ease-in-out infinite; }
.la-ball-fall > div:nth-child(1) {
-webkit-animation-delay: -200ms;
-moz-animation-delay: -200ms;
-o-animation-delay: -200ms;
animation-delay: -200ms; }
.la-ball-fall > div:nth-child(2) {
-webkit-animation-delay: -100ms;
-moz-animation-delay: -100ms;
-o-animation-delay: -100ms;
animation-delay: -100ms; }
.la-ball-fall > div:nth-child(3) {
-webkit-animation-delay: 0ms;
-moz-animation-delay: 0ms;
-o-animation-delay: 0ms;
animation-delay: 0ms; }
.la-ball-fall.la-sm {
width: 26px;
height: 8px; }
.la-ball-fall.la-sm > div {
width: 4px;
height: 4px;
margin: 2px; }
.la-ball-fall.la-2x {
width: 108px;
height: 36px; }
.la-ball-fall.la-2x > div {
width: 20px;
height: 20px;
margin: 8px; }
.la-ball-fall.la-3x {
width: 162px;
height: 54px; }
.la-ball-fall.la-3x > div {
width: 30px;
height: 30px;
margin: 12px; }
/*
* Animation
*/
@-webkit-keyframes ball-fall {
0% {
opacity: 0;
-webkit-transform: translateY(-145%);
transform: translateY(-145%); }
10% {
opacity: .5; }
20% {
opacity: 1;
-webkit-transform: translateY(0);
transform: translateY(0); }
80% {
opacity: 1;
-webkit-transform: translateY(0);
transform: translateY(0); }
90% {
opacity: .5; }
100% {
opacity: 0;
-webkit-transform: translateY(145%);
transform: translateY(145%); } }
@-moz-keyframes ball-fall {
0% {
opacity: 0;
-moz-transform: translateY(-145%);
transform: translateY(-145%); }
10% {
opacity: .5; }
20% {
opacity: 1;
-moz-transform: translateY(0);
transform: translateY(0); }
80% {
opacity: 1;
-moz-transform: translateY(0);
transform: translateY(0); }
90% {
opacity: .5; }
100% {
opacity: 0;
-moz-transform: translateY(145%);
transform: translateY(145%); } }
@-o-keyframes ball-fall {
0% {
opacity: 0;
-o-transform: translateY(-145%);
transform: translateY(-145%); }
10% {
opacity: .5; }
20% {
opacity: 1;
-o-transform: translateY(0);
transform: translateY(0); }
80% {
opacity: 1;
-o-transform: translateY(0);
transform: translateY(0); }
90% {
opacity: .5; }
100% {
opacity: 0;
-o-transform: translateY(145%);
transform: translateY(145%); } }
@keyframes ball-fall {
0% {
opacity: 0;
-webkit-transform: translateY(-145%);
-moz-transform: translateY(-145%);
-o-transform: translateY(-145%);
transform: translateY(-145%); }
10% {
opacity: .5; }
20% {
opacity: 1;
-webkit-transform: translateY(0);
-moz-transform: translateY(0);
-o-transform: translateY(0);
transform: translateY(0); }
80% {
opacity: 1;
-webkit-transform: translateY(0);
-moz-transform: translateY(0);
-o-transform: translateY(0);
transform: translateY(0); }
90% {
opacity: .5; }
100% {
opacity: 0;
-webkit-transform: translateY(145%);
-moz-transform: translateY(145%);
-o-transform: translateY(145%);
transform: translateY(145%); } }

BIN
static/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

7
static/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

338
static/js/index.js Normal file
View File

@ -0,0 +1,338 @@
function Domain() {
this.tableData = null
this.model = false
this.editIndex = -1
// this.url = "http://127.0.0.1:8300/domain"
this.url = "https://monitor.api.ggbba.top/domain"
this.currentPage = 1
this.pageData = null
this.searchText = ""
this.numPages = 0
}
// 搜索域名
Domain.prototype.searchDomain = function () {
let self = this
$('#search-btn').click(function () {
let search = $('#search-input').val()
self.searchText = search
if (self.searchText) {
self.currentPage = 1
self.initTableData(self.currentPage, self.searchText)
} else {
self.initTableData(1, "")
}
})
}
// 点击分页按钮
Domain.prototype.domainPageBtn = function () {
let self = this
$('.page-btn').click(function () {
self.currentPage = parseInt($(this).attr('data-p'))
self.initTableData(self.currentPage, self.searchText)
})
}
// 分页初始化
Domain.prototype.domainPageInit = function () {
let self = this
// 是否是最后一页
let is_finally = false
// 是否是第一页
let is_first = false
// 总页数
let num_pages = self.pageData['num_pages']
if (self.currentPage === 1) {
is_first = true
}
if (self.currentPage === num_pages) {
is_finally = true
}
self.pageData['is_first'] = is_first
self.pageData['is_finally'] = is_finally
}
// 表单验证
Domain.prototype.verifyDomain = function (domain) {
let patt = /(https?|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/i
return patt.test(domain)
}
// 错误弹窗
Domain.prototype.alertError = function (msg) {
swal('提示', msg, 'error');
}
// 弹窗
Domain.prototype.alertToast = function (msg, type) {
swal({
'title': msg,
'text': '',
'type': type,
'showCancelButton': false,
'showConfirmButton': false,
'timer': 1000
})
}
// 带确认按钮的弹窗
Domain.prototype.alertConfirm = function (params) {
swal({
'title': params['title'] ? params['title'] : '提示',
'showCancelButton': true,
'showConfirmButton': true,
'type': params['type'] ? params['type'] : '',
'confirmButtonText': params['confirmText'] ? params['confirmText'] : '确定',
'cancelButtonText': params['cancelText'] ? params['cancelText'] : '取消',
'text': params['text'] ? params['text'] : ''
}, function (isConfirm) {
if (isConfirm) {
if (params['confirmCallback']) {
params['confirmCallback']()
}
} else {
if (params['cancelCallback']) {
params['cancelCallback']()
}
}
})
}
// 成功弹窗
Domain.prototype.alertSuccessToast = function (msg) {
if (!msg) {
msg = '成功!'
}
this.alertToast(msg, 'success')
}
// 渲染模板
Domain.prototype.renderPage = function () {
let self = this;
let html = template('tpl-table-tr', {domainList: self.tableData})
$('#tbody-domin').empty().append(html)
html = template('tpl-page-li', {page_data: self.pageData})
$('#page-li').empty().append(html)
self.editDomain()
self.deleteDomain()
self.domainPageBtn()
}
// 初始化数据
Domain.prototype.initTableData = function (num, query) {
let self = this;
let requestUrl = self.url + '/page/' + num
if (query) {
requestUrl += "?search=" + query
}
$.get(requestUrl, function (data, status) {
if (data['code'] === 200) {
self.currentPage = num
self.tableData = data['data']['domain_data']
self.pageData = data['data']['pagination_data']
self.numPages = self.pageData['num_pages']
self.domainPageInit()
self.renderPage()
}
})
}
// 添加域名
Domain.prototype.addDomain = function () {
let self = this;
$("#add-domain").click(function () {
self.initAddForm()
})
}
// 初始化添加域名表单弹窗
Domain.prototype.initAddForm = function () {
this.model = false
$('#DomainModalLabel').text("添加域名")
$('#cdn-f').prop('checked', true)
$('#boce-f').prop('checked', true)
$('#form-key').val("")
$('#form-domain').val("")
$('#form-remark').val("")
}
// 提交添加域名表单
Domain.prototype.addSubmit = function () {
let self = this
let boceId = $("input[name='options-boce']:checked").attr("id")
let boce = false
if (boceId === "boce-t") {
boce = true
}
let cdnId = $("input[name='options-cdn']:checked").attr("id")
let cdn = false
if (cdnId === "cdn-t") {
cdn = true
}
let domain = $('#form-domain').val()
let autoKey = $('#form-key').val()
let remark = $('#form-remark').val()
let requestData = {'name': domain,'auth_key': auto_Key, 'remark': remark, 'boce': boce, 'category': cdn}
$("#DomainModal").modal("hide")
if (!self.verifyDomain(domain)) {
self.alertError("域名格式不正确")
return
}
$.ajax({
type: "post",
url: self.url,
data: JSON.stringify(requestData),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (result) {
self.alertSuccessToast("添加成功!")
self.initTableData(self.numPages, '')
}
})
}
// 编辑域名
Domain.prototype.editDomain = function () {
let self = this;
$(".edit-domain").click(function () {
let index = $(this).attr('data-index')
self.initEditForm(index)
})
}
// 初始化编辑域名表单弹窗
Domain.prototype.initEditForm = function (index) {
this.editIndex = index
this.model = true
$('#DomainModalLabel').text("编辑域名")
let data = this.tableData[index]
if (data['category']) {
$('#cdn-t').prop('checked', true)
} else {
$('#cdn-f').prop('checked', true)
}
if (data['boce']) {
$('#boce-t').prop('checked', true)
} else {
$('#boce-f').prop('checked', true)
}
$('#form-key').val(data['auth_key'])
$('#form-domain').val(data['name'])
$('#form-remark').val(data['remark'])
}
// 编辑域名表单提交
Domain.prototype.editSubmit = function () {
let self = this
if (self.model) {
let data = this.tableData[self.editIndex]
let id = data['id']
let boceId = $("input[name='options-boce']:checked").attr("id")
let boce = false
if (boceId === "boce-t") {
boce = true
}
let cdnId = $("input[name='options-cdn']:checked").attr("id")
let cdn = false
if (cdnId === "cdn-t") {
cdn = true
}
let domain = $('#form-domain').val()
let autoKey = $('#form-key').val()
let remark = $('#form-remark').val()
let requestData = {'id': id, 'name': domain, 'auth_key': autoKey, 'remark': remark, 'boce': boce, 'category': cdn}
$("#DomainModal").modal("hide")
if (!self.verifyDomain(domain)) {
self.alertError("域名格式不正确")
return
}
$.ajax({
type: "put",
url: self.url,
data: JSON.stringify(requestData),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (result) {
self.alertSuccessToast("编辑成功!")
self.tableData[self.editIndex] = requestData
self.renderPage()
}
})
}
}
// 表单提交策略
Domain.prototype.submitData = function () {
let self = this;
$('#submit-data').click(function () {
if (self.model) {
self.editSubmit()
} else {
self.addSubmit()
}
})
}
// 删除域名
Domain.prototype.deleteDomain = function () {
let self = this;
$('.delete-domain').click(function () {
let index = $(this).attr('data-index')
let requestData = {'id': self.tableData[index]['id']}
self.alertConfirm({
'text': '您确定要删除' + self.tableData[index]['name'] + '吗?',
'confirmCallback': function () {
$.ajax({
type: "delete",
url: self.url,
data: JSON.stringify(requestData),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (result) {
self.alertSuccessToast("删除成功!")
self.initTableData(self.currentPage, self.searchText)
}
})
}
})
})
}
// 入口方法
Domain.prototype.run = function () {
let self = this
self.initTableData(self.currentPage, '')
self.addDomain()
self.editDomain()
self.submitData()
self.deleteDomain()
self.domainPageBtn()
self.searchDomain()
}
// 构造执行入口
$(function () {
// 模板过滤方法
if (window.template) {
template.defaults.imports.domainSubstring = function (dateValue) {
if (dateValue.length > 40 ) {
return dateValue.substring(0, 37) + "..."
} else {
return dateValue
}
}
}
let domain = new Domain()
domain.run()
})

2
static/js/jquery-3.3.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

5
static/js/popper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
static/js/sweetalert.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

217
view/index.html Normal file
View File

@ -0,0 +1,217 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" href="../static/img/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="../static/css/bootstrap.min.css">
<link rel="stylesheet" href="../static/css/bootstrap-icons.css">
<link rel="stylesheet" href="../static/css/sweetalert.css">
<title>域名监控</title>
</head>
<body>
<div class="container-fluid" style="padding-left: 0; padding-right: 0">
<nav class="navbar justify-content-center navbar-dark bg-dark">
<span class="navbar-brand mb-0 h1">域名监控</span>
</nav>
</div>
<div class="container">
<div class="shadow p-3 bg-white rounded mt-3">
<div class="alert alert-primary form-inline" role="alert">
<button type="button" id="add-domain" class="btn btn-primary" data-toggle="modal"
data-target="#DomainModal"><i
class="bi bi-plus-lg"></i> 添加域名
</button>
<div class="input-group mr-3" style="position: absolute; right: 0">
<input type="text" id="search-input" class="form-control" placeholder="域名" aria-label="域名" aria-describedby="searc-btn">
<div class="input-group-append">
<button class="btn btn-primary" id="search-btn" type="button"><i class="bi bi-search"></i> 搜索
</button>
</div>
</div>
</div>
<div class="bg-light">
<table class="table table-bordered text-nowrap">
<thead>
<tr class="table-primary">
<th scope="col">编号</th>
<th scope="col">域名</th>
<th scope="col">Boce</th>
<th scope="col">备注</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody id="tbody-domin">
<script id="tpl-table-tr" type="text/html">
{{each domainList value index }}
<tr>
<td>{{index}}</td>
<td>
<span class="d-inline-block" tabindex="0" data-toggle="tooltip" data-placement="top"
title="{{value.name}}">
{{value.name|domainSubstring}}
</span>
</td>
{{if value.boce}}
<td><span class="badge rounded-pill bg-success">True</span></td>
{{else}}
<td><span class="badge rounded-pill bg-secondary">False</span></td>
{{/if}}
<td>{{value.remark}}</td>
<td>
<button type="button" class="btn btn-sm btn-primary"
onclick="window.open('{{value.name}}')"><i
class="bi bi-link-45deg"></i> 打开
</button>
<button type="button" class="btn btn-sm btn-warning edit-domain" data-index="{{index}}"
data-toggle="modal"
data-target="#DomainModal"><i
class="bi bi-pencil-square"></i> 编辑
</button>
<button type="button" class="btn btn-sm btn-danger delete-domain"
data-index="{{index}}"><i
class="bi bi-trash3"></i> 删除
</button>
</td>
</tr>
{{/each}}
</script>
</tbody>
</table>
<!-- 分页 -->
<nav>
<ul class="pagination justify-content-end" id="page-li">
<script id="tpl-page-li" type="text/html">
<!-- 上一页 -->
<li class="page-item{{if page_data.is_first}} disabled{{/if}}">
<a class="page-link page-btn" data-p="{{page_data.current_page-1}}"
href="javascript:void(0)">上一页</a>
</li>
<!-- 当前页左边是否显示 ... -->
{{if page_data.left_has_more }}
<li class="page-item"><a class="page-link page-btn" data-p="1" href="javascript:void(0)">1</a>
</li>
<li class="page-item"><span class="page-link">...</span></li>
{{/if}}
<!-- 当前页左边显示按钮 -->
{{each page_data.left_pages lp}}
<li class="page-item"><a class="page-link page-btn" data-p="{{lp}}" href="javascript:void(0)">{{lp}}</a>
</li>
{{/each}}
<!-- 当前页 -->
<li class="page-item active"><span class="page-link">{{page_data.current_page}}</span></li>
<!-- 当前页右边是否显示 ... -->
{{each page_data.right_pages rp}}
<li class="page-item"><a class="page-link page-btn" data-p="{{rp}}" href="javascript:void(0)">{{rp}}</a>
</li>
{{/each}}
<!-- 当前页右边显示按钮 -->
{{if page_data.right_has_more }}
<li class="page-item"><span class="page-link">...</span></li>
<li class="page-item"><a class="page-link page-btn" data-p="{{page_data.num_pages}}"
href="javascript:void(0)">{{page_data.num_pages}}</a></li>
{{/if}}
<!-- 下一页 -->
<li class="page-item{{if page_data.is_finally}} disabled{{/if}}">
<a class="page-link page-btn" data-p="{{page_data.current_page+1}}"
href="javascript:void(0)">下一页</a>
</li>
</script>
</ul>
</nav>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="DomainModal" tabindex="-1" role="dialog" aria-labelledby="DomainModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="DomainModalLabel" modal-data="0">添加域名</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="col-auto">
<div class="form-group row">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text alert-primary">域名:</div>
</div>
<input type="text" id="form-domain" class="form-control" placeholder="域名">
</div>
</div>
<div class="form-group row">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text alert-primary">备注:</div>
</div>
<input type="text" id="form-remark" class="form-control" placeholder="备注">
</div>
</div>
<div class="form-group row">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text alert-primary">秘钥:</div>
</div>
<input type="text" id="form-key" class="form-control" placeholder="cdn鉴权秘钥">
</div>
</div>
<div class="form-group row">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text alert-primary">CDN: </div>
</div>
<div class="btn-group" data-toggle="buttons-cdn">
<label class="btn btn-light" style="margin-bottom: 0">
<input type="radio" name="options-cdn" id="cdn-t" autocomplete="off">True
</label>
<label class="btn btn-light" style="margin-bottom: 0">
<input type="radio" name="options-cdn" id="cdn-f" autocomplete="off" checked>False
</label>
<label style="margin-left:10px;color: red;height: 30px;line-height: 38px;">是否需要鉴权</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text alert-primary">Boce: </div>
</div>
<div class="btn-group" data-toggle="buttons-boce">
<label class="btn btn-light" style="margin-bottom: 0">
<input type="radio" name="options-boce" id="boce-t" autocomplete="off">True
</label>
<label class="btn btn-light" style="margin-bottom: 0">
<input type="radio" name="options-boce" id="boce-f" autocomplete="off" checked>False
</label>
<label style="margin-left:10px;color: red;height: 30px;line-height: 38px;">是否加到boce、17ce测试</label>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" id="submit-data">提交</button>
</div>
</div>
</div>
</div>
<script src="../static/js/jquery-3.3.1.min.js"></script>
<script src="../static/js/popper.min.js"></script>
<script src="../static/js/bootstrap.min.js"></script>
<script src="../static/js/template-web.js"></script>
<script src="../static/js/sweetalert.min.js"></script>
<script src="../static/js/index.js"></script>
</body>
</html>

54
view/login.html Normal file
View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Login Page</title>
<!-- Bootstrap CSS -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<style>
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-form {
width: 100%;
max-width: 420px;
padding: 15px;
margin: auto;
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-form card shadow-sm">
<div class="card-body">
<h3 class="card-title text-center">Login</h3>
<form action="/login" method="POST">
<div class="form-group">
<label for="email">Email address</label>
<input type="email" class="form-control" id="email" name="email" placeholder="Enter email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Password" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Login</button>
<p class="text-center mt-3">
<a href="#">Forgot your password?</a>
</p>
<p class="text-center">
<a href="#">Register</a>
</p>
</form>
</div>
</div>
</div>
<!-- Bootstrap JS and dependencies -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.3/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>