mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: 实现容器启停、列表、日志等功能
This commit is contained in:
parent
20a57cc5ab
commit
a79ba71ef4
82
backend/app/api/v1/container.go
Normal file
82
backend/app/api/v1/container.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||||
|
"github.com/1Panel-dev/1Panel/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/global"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *BaseApi) SearchContainer(c *gin.Context) {
|
||||||
|
var req dto.PageContainer
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
total, list, err := containerService.Page(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, dto.PageResult{
|
||||||
|
Items: list,
|
||||||
|
Total: total,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) ContainerOperation(c *gin.Context) {
|
||||||
|
var req dto.ContainerOperation
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := containerService.ContainerOperation(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) ContainerDetail(c *gin.Context) {
|
||||||
|
id, ok := c.Params.Get("id")
|
||||||
|
if !ok {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, errors.New("error id in path"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, err := containerService.ContainerInspect(id)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseApi) ContainerLogs(c *gin.Context) {
|
||||||
|
var req dto.ContainerLog
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := global.VALID.Struct(req); err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logs, err := containerService.ContainerLogs(req)
|
||||||
|
if err != nil {
|
||||||
|
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helper.SuccessWithData(c, logs)
|
||||||
|
}
|
@ -13,6 +13,7 @@ var (
|
|||||||
hostService = service.ServiceGroupApp.HostService
|
hostService = service.ServiceGroupApp.HostService
|
||||||
backupService = service.ServiceGroupApp.BackupService
|
backupService = service.ServiceGroupApp.BackupService
|
||||||
groupService = service.ServiceGroupApp.GroupService
|
groupService = service.ServiceGroupApp.GroupService
|
||||||
|
containerService = service.ServiceGroupApp.ContainerService
|
||||||
commandService = service.ServiceGroupApp.CommandService
|
commandService = service.ServiceGroupApp.CommandService
|
||||||
operationService = service.ServiceGroupApp.OperationService
|
operationService = service.ServiceGroupApp.OperationService
|
||||||
fileService = service.ServiceGroupApp.FileService
|
fileService = service.ServiceGroupApp.FileService
|
||||||
|
27
backend/app/dto/container.go
Normal file
27
backend/app/dto/container.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
type PageContainer struct {
|
||||||
|
PageInfo
|
||||||
|
Status string `json:"status" validate:"required,oneof=all running"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerInfo struct {
|
||||||
|
ContainerID string `json:"containerID"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
ImageId string `json:"imageID"`
|
||||||
|
ImageName string `json:"imageName"`
|
||||||
|
CreateTime string `json:"createTime"`
|
||||||
|
State string `json:"state"`
|
||||||
|
RunTime string `json:"runTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerLog struct {
|
||||||
|
ContainerID string `json:"containerID" validate:"required"`
|
||||||
|
Mode string `json:"mode" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerOperation struct {
|
||||||
|
ContainerID string `json:"containerID" validate:"required"`
|
||||||
|
Operation string `json:"operation" validate:"required,oneof=start stop reStart kill pause unPause reName remove"`
|
||||||
|
NewName string `json:"newName"`
|
||||||
|
}
|
140
backend/app/service/container.go
Normal file
140
backend/app/service/container.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/1Panel-dev/1Panel/app/dto"
|
||||||
|
"github.com/1Panel-dev/1Panel/constant"
|
||||||
|
"github.com/1Panel-dev/1Panel/utils/docker"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContainerService struct{}
|
||||||
|
|
||||||
|
type IContainerService interface {
|
||||||
|
Page(req dto.PageContainer) (int64, interface{}, error)
|
||||||
|
ContainerOperation(req dto.ContainerOperation) error
|
||||||
|
ContainerLogs(param dto.ContainerLog) (string, error)
|
||||||
|
ContainerInspect(id string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIContainerService() IContainerService {
|
||||||
|
return &ContainerService{}
|
||||||
|
}
|
||||||
|
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
|
||||||
|
var (
|
||||||
|
records []types.Container
|
||||||
|
list []types.Container
|
||||||
|
backDatas []dto.ContainerInfo
|
||||||
|
)
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
list, err = client.ContainerList(context.Background(), types.ContainerListOptions{All: req.Status == "all"})
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||||
|
if start > total {
|
||||||
|
records = make([]types.Container, 0)
|
||||||
|
} else {
|
||||||
|
if end >= total {
|
||||||
|
end = total
|
||||||
|
}
|
||||||
|
records = list[start:end]
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, container := range records {
|
||||||
|
backDatas = append(backDatas, dto.ContainerInfo{
|
||||||
|
ContainerID: container.ID,
|
||||||
|
CreateTime: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"),
|
||||||
|
Name: container.Names[0][1:],
|
||||||
|
ImageId: strings.Split(container.ImageID, ":")[1],
|
||||||
|
ImageName: container.Image,
|
||||||
|
State: container.State,
|
||||||
|
RunTime: container.Status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(total), backDatas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error {
|
||||||
|
var err error
|
||||||
|
ctx := context.Background()
|
||||||
|
dc, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch req.Operation {
|
||||||
|
case constant.ContainerOpStart:
|
||||||
|
err = dc.ContainerStart(ctx, req.ContainerID, types.ContainerStartOptions{})
|
||||||
|
case constant.ContainerOpStop:
|
||||||
|
err = dc.ContainerStop(ctx, req.ContainerID, nil)
|
||||||
|
case constant.ContainerOpRestart:
|
||||||
|
err = dc.ContainerRestart(ctx, req.ContainerID, nil)
|
||||||
|
case constant.ContainerOpKill:
|
||||||
|
err = dc.ContainerKill(ctx, req.ContainerID, "SIGKILL")
|
||||||
|
case constant.ContainerOpPause:
|
||||||
|
err = dc.ContainerPause(ctx, req.ContainerID)
|
||||||
|
case constant.ContainerOpUnpause:
|
||||||
|
err = dc.ContainerUnpause(ctx, req.ContainerID)
|
||||||
|
case constant.ContainerOpRename:
|
||||||
|
err = dc.ContainerRename(ctx, req.ContainerID, req.NewName)
|
||||||
|
case constant.ContainerOpRemove:
|
||||||
|
err = dc.ContainerRemove(ctx, req.ContainerID, types.ContainerRemoveOptions{RemoveVolumes: true, RemoveLinks: true, Force: true})
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) ContainerInspect(id string) (string, error) {
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
inspect, err := client.ContainerInspect(context.Background(), id)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
bytes, err := json.Marshal(inspect)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) {
|
||||||
|
var (
|
||||||
|
options types.ContainerLogsOptions
|
||||||
|
logs io.ReadCloser
|
||||||
|
buf *bytes.Buffer
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
client, err := docker.NewDockerClient()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
options = types.ContainerLogsOptions{
|
||||||
|
ShowStdout: true,
|
||||||
|
Timestamps: true,
|
||||||
|
}
|
||||||
|
if req.Mode != "all" {
|
||||||
|
options.Since = req.Mode
|
||||||
|
}
|
||||||
|
if logs, err = client.ContainerLogs(context.Background(), req.ContainerID, options); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer logs.Close()
|
||||||
|
buf = new(bytes.Buffer)
|
||||||
|
if _, err = stdcopy.StdCopy(buf, nil, logs); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
@ -7,6 +7,7 @@ type ServiceGroup struct {
|
|||||||
HostService
|
HostService
|
||||||
BackupService
|
BackupService
|
||||||
GroupService
|
GroupService
|
||||||
|
ContainerService
|
||||||
CommandService
|
CommandService
|
||||||
OperationService
|
OperationService
|
||||||
FileService
|
FileService
|
||||||
|
12
backend/constant/container.go
Normal file
12
backend/constant/container.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
const (
|
||||||
|
ContainerOpStart = "start"
|
||||||
|
ContainerOpStop = "stop"
|
||||||
|
ContainerOpRestart = "reStart"
|
||||||
|
ContainerOpKill = "kill"
|
||||||
|
ContainerOpPause = "pause"
|
||||||
|
ContainerOpUnpause = "unPause"
|
||||||
|
ContainerOpRename = "reName"
|
||||||
|
ContainerOpRemove = "remove"
|
||||||
|
)
|
@ -69,6 +69,7 @@ func Routers() *gin.Engine {
|
|||||||
systemRouter.InitHostRouter(PrivateGroup)
|
systemRouter.InitHostRouter(PrivateGroup)
|
||||||
systemRouter.InitBackupRouter(PrivateGroup)
|
systemRouter.InitBackupRouter(PrivateGroup)
|
||||||
systemRouter.InitGroupRouter(PrivateGroup)
|
systemRouter.InitGroupRouter(PrivateGroup)
|
||||||
|
systemRouter.InitContainerRouter(PrivateGroup)
|
||||||
systemRouter.InitCommandRouter(PrivateGroup)
|
systemRouter.InitCommandRouter(PrivateGroup)
|
||||||
systemRouter.InitTerminalRouter(PrivateGroup)
|
systemRouter.InitTerminalRouter(PrivateGroup)
|
||||||
systemRouter.InitMonitorRouter(PrivateGroup)
|
systemRouter.InitMonitorRouter(PrivateGroup)
|
||||||
|
28
backend/router/container.go
Normal file
28
backend/router/container.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1 "github.com/1Panel-dev/1Panel/app/api/v1"
|
||||||
|
"github.com/1Panel-dev/1Panel/middleware"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContainerRouter struct{}
|
||||||
|
|
||||||
|
func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
||||||
|
baRouter := Router.Group("containers").
|
||||||
|
Use(middleware.JwtAuth()).
|
||||||
|
Use(middleware.SessionAuth()).
|
||||||
|
Use(middleware.PasswordExpired())
|
||||||
|
withRecordRouter := Router.Group("containers").
|
||||||
|
Use(middleware.JwtAuth()).
|
||||||
|
Use(middleware.SessionAuth()).
|
||||||
|
Use(middleware.PasswordExpired()).
|
||||||
|
Use(middleware.OperationRecord())
|
||||||
|
baseApi := v1.ApiGroupApp.BaseApi
|
||||||
|
{
|
||||||
|
baRouter.POST("/search", baseApi.SearchContainer)
|
||||||
|
baRouter.GET("/detail/:id", baseApi.ContainerDetail)
|
||||||
|
withRecordRouter.POST("operate", baseApi.ContainerOperation)
|
||||||
|
withRecordRouter.POST("/log", baseApi.ContainerLogs)
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ type RouterGroup struct {
|
|||||||
HostRouter
|
HostRouter
|
||||||
BackupRouter
|
BackupRouter
|
||||||
GroupRouter
|
GroupRouter
|
||||||
|
ContainerRouter
|
||||||
CommandRouter
|
CommandRouter
|
||||||
MonitorRouter
|
MonitorRouter
|
||||||
OperationLogRouter
|
OperationLogRouter
|
||||||
|
@ -2,6 +2,7 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
@ -22,6 +23,14 @@ func NewClient() (Client, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewDockerClient() (*client.Client, error) {
|
||||||
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cli, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c Client) ListAllContainers() ([]types.Container, error) {
|
func (c Client) ListAllContainers() ([]types.Container, error) {
|
||||||
var options types.ContainerListOptions
|
var options types.ContainerListOptions
|
||||||
containers, err := c.cli.ContainerList(context.Background(), options)
|
containers, err := c.cli.ContainerList(context.Background(), options)
|
||||||
|
5357
frontend/package-lock.json
generated
5357
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,8 @@
|
|||||||
"prettier": "prettier --write ."
|
"prettier": "prettier --write ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/lang-javascript": "^6.1.0",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.0",
|
||||||
"@element-plus/icons-vue": "^1.1.4",
|
"@element-plus/icons-vue": "^1.1.4",
|
||||||
"@kangc/v-md-editor": "^2.3.15",
|
"@kangc/v-md-editor": "^2.3.15",
|
||||||
"@vueuse/core": "^8.0.1",
|
"@vueuse/core": "^8.0.1",
|
||||||
@ -40,6 +42,7 @@
|
|||||||
"unplugin-vue-define-options": "^0.7.3",
|
"unplugin-vue-define-options": "^0.7.3",
|
||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vue": "^3.2.25",
|
"vue": "^3.2.25",
|
||||||
|
"vue-codemirror": "^6.1.1",
|
||||||
"vue-i18n": "^9.1.9",
|
"vue-i18n": "^9.1.9",
|
||||||
"vue-router": "^4.0.12",
|
"vue-router": "^4.0.12",
|
||||||
"vue3-seamless-scroll": "^1.2.0",
|
"vue3-seamless-scroll": "^1.2.0",
|
||||||
|
24
frontend/src/api/interface/container.ts
Normal file
24
frontend/src/api/interface/container.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ReqPage } from '.';
|
||||||
|
|
||||||
|
export namespace Container {
|
||||||
|
export interface ContainerOperate {
|
||||||
|
containerID: string;
|
||||||
|
operation: string;
|
||||||
|
newName: string;
|
||||||
|
}
|
||||||
|
export interface ContainerSearch extends ReqPage {
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
export interface ContainerInfo {
|
||||||
|
containerID: string;
|
||||||
|
name: string;
|
||||||
|
imageName: string;
|
||||||
|
createTime: string;
|
||||||
|
state: string;
|
||||||
|
runTime: string;
|
||||||
|
}
|
||||||
|
export interface ContainerLogSearch {
|
||||||
|
containerID: string;
|
||||||
|
mode: string;
|
||||||
|
}
|
||||||
|
}
|
19
frontend/src/api/modules/container.ts
Normal file
19
frontend/src/api/modules/container.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import http from '@/api';
|
||||||
|
import { ResPage } from '../interface';
|
||||||
|
import { Container } from '../interface/container';
|
||||||
|
|
||||||
|
export const getContainerPage = (params: Container.ContainerSearch) => {
|
||||||
|
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getContainerLog = (params: Container.ContainerLogSearch) => {
|
||||||
|
return http.post<string>(`/containers/log`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ContainerOperator = (params: Container.ContainerOperate) => {
|
||||||
|
return http.post(`/containers/operate`, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getContainerInspect = (containerID: string) => {
|
||||||
|
return http.get<string>(`/containers/detail/${containerID}`);
|
||||||
|
};
|
@ -18,6 +18,7 @@ export default {
|
|||||||
login: 'Login',
|
login: 'Login',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
view: 'View',
|
view: 'View',
|
||||||
|
watch: 'Watch',
|
||||||
handle: 'Handle',
|
handle: 'Handle',
|
||||||
expand: 'Expand',
|
expand: 'Expand',
|
||||||
log: 'Log',
|
log: 'Log',
|
||||||
@ -147,6 +148,28 @@ export default {
|
|||||||
header: {
|
header: {
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
},
|
},
|
||||||
|
container: {
|
||||||
|
operatorHelper: '{0} will be performed on the selected container. Do you want to continue?',
|
||||||
|
start: 'Start',
|
||||||
|
stop: 'Stop',
|
||||||
|
reStart: 'ReStart',
|
||||||
|
kill: 'Kill',
|
||||||
|
pause: 'Pause',
|
||||||
|
unPause: 'UnPause',
|
||||||
|
reName: 'ReName',
|
||||||
|
remove: 'Remove',
|
||||||
|
container: 'Container',
|
||||||
|
image: 'Image',
|
||||||
|
network: 'Network',
|
||||||
|
storage: 'Storage',
|
||||||
|
schedule: 'Schedule',
|
||||||
|
upTime: 'UpTime',
|
||||||
|
all: 'All',
|
||||||
|
lastDay: 'Last Day',
|
||||||
|
last4Hour: 'Last 4 Hours',
|
||||||
|
lastHour: 'Last Hour',
|
||||||
|
last10Min: 'Last 10 Minutes',
|
||||||
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
cronTask: 'Task',
|
cronTask: 'Task',
|
||||||
taskType: 'Task type',
|
taskType: 'Task type',
|
||||||
|
@ -18,6 +18,7 @@ export default {
|
|||||||
login: '登录',
|
login: '登录',
|
||||||
close: '关闭',
|
close: '关闭',
|
||||||
view: '详情',
|
view: '详情',
|
||||||
|
watch: '追踪',
|
||||||
handle: '执行',
|
handle: '执行',
|
||||||
expand: '展开',
|
expand: '展开',
|
||||||
log: '日志',
|
log: '日志',
|
||||||
@ -144,6 +145,28 @@ export default {
|
|||||||
header: {
|
header: {
|
||||||
logout: '退出登录',
|
logout: '退出登录',
|
||||||
},
|
},
|
||||||
|
container: {
|
||||||
|
operatorHelper: '将对选中容器进行 {0} 操作,是否继续?',
|
||||||
|
start: '启动',
|
||||||
|
stop: '停止',
|
||||||
|
reStart: '重启',
|
||||||
|
kill: '强制停止',
|
||||||
|
pause: '暂停',
|
||||||
|
unPause: '恢复',
|
||||||
|
reName: '重命名',
|
||||||
|
remove: '移除',
|
||||||
|
container: '容器',
|
||||||
|
image: '镜像',
|
||||||
|
network: '网络',
|
||||||
|
storage: '数据卷',
|
||||||
|
schedule: '编排',
|
||||||
|
upTime: '运行时长',
|
||||||
|
all: '全部',
|
||||||
|
lastDay: '最近一天',
|
||||||
|
last4Hour: '最近 4 小时',
|
||||||
|
lastHour: '最近 1 小时',
|
||||||
|
last10Min: '最近 10 分钟',
|
||||||
|
},
|
||||||
cronjob: {
|
cronjob: {
|
||||||
cronTask: '计划任务',
|
cronTask: '计划任务',
|
||||||
taskType: '任务类型',
|
taskType: '任务类型',
|
||||||
|
361
frontend/src/views/container/container/index.vue
Normal file
361
frontend/src/views/container/container/index.vue
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
<template>
|
||||||
|
<el-card style="margin-top: 20px">
|
||||||
|
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data" @search="search">
|
||||||
|
<template #toolbar>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button :disabled="checkStatus('start')" @click="onOperate('start')">
|
||||||
|
{{ $t('container.start') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="checkStatus('stop')" @click="onOperate('stop')">
|
||||||
|
{{ $t('container.stop') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="checkStatus('reStart')" @click="onOperate('reStart')">
|
||||||
|
{{ $t('container.reStart') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="checkStatus('kill')" @click="onOperate('kill')">
|
||||||
|
{{ $t('container.kill') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="checkStatus('pause')" @click="onOperate('pause')">
|
||||||
|
{{ $t('container.pause') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="checkStatus('unPause')" @click="onOperate('unPause')">
|
||||||
|
{{ $t('container.unPause') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button :disabled="checkStatus('remove')" @click="onOperate('remove')">
|
||||||
|
{{ $t('container.remove') }}
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
<el-button icon="Plus" style="margin-left: 10px" @click="onCreate()">
|
||||||
|
{{ $t('commons.button.create') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<el-table-column type="selection" fix />
|
||||||
|
<el-table-column :label="$t('commons.table.name')" show-overflow-tooltip min-width="100" prop="name" fix />
|
||||||
|
<el-table-column :label="$t('container.image')" show-overflow-tooltip min-width="100" prop="imageName" />
|
||||||
|
<el-table-column :label="$t('commons.table.status')" min-width="50" prop="state" fix />
|
||||||
|
<el-table-column :label="$t('container.upTime')" min-width="100" prop="runTime" fix />
|
||||||
|
<el-table-column
|
||||||
|
prop="createTime"
|
||||||
|
:label="$t('commons.table.date')"
|
||||||
|
:formatter="dateFromat"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix />
|
||||||
|
</ComplexTable>
|
||||||
|
<el-dialog v-model="detailVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="70%">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ $t('commons.button.views') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<codemirror
|
||||||
|
:autofocus="true"
|
||||||
|
placeholder="None data"
|
||||||
|
:indent-with-tab="true"
|
||||||
|
:tabSize="4"
|
||||||
|
style="max-height: 500px"
|
||||||
|
:lineWrapping="true"
|
||||||
|
:matchBrackets="true"
|
||||||
|
theme="cobalt"
|
||||||
|
:styleActiveLine="true"
|
||||||
|
:extensions="extensions"
|
||||||
|
v-model="detailInfo"
|
||||||
|
:readOnly="true"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="detailVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
@close="onCloseLog"
|
||||||
|
v-model="logVisiable"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
width="70%"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ $t('commons.button.log') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div>
|
||||||
|
<el-select @change="searchLogs" style="width: 10%; float: left" v-model="logSearch.mode">
|
||||||
|
<el-option v-for="item in timeOptions" :key="item.label" :value="item.value" :label="item.label" />
|
||||||
|
</el-select>
|
||||||
|
<div style="margin-left: 20px; float: left">
|
||||||
|
<el-checkbox border v-model="logSearch.isWatch">{{ $t('commons.button.watch') }}</el-checkbox>
|
||||||
|
</div>
|
||||||
|
<el-button style="margin-left: 20px" @click="onDownload" icon="Download">
|
||||||
|
{{ $t('file.download') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<codemirror
|
||||||
|
:autofocus="true"
|
||||||
|
placeholder="None data"
|
||||||
|
:indent-with-tab="true"
|
||||||
|
:tabSize="4"
|
||||||
|
style="margin-top: 10px; max-height: 500px"
|
||||||
|
:lineWrapping="true"
|
||||||
|
:matchBrackets="true"
|
||||||
|
theme="cobalt"
|
||||||
|
:styleActiveLine="true"
|
||||||
|
:extensions="extensions"
|
||||||
|
v-model="logInfo"
|
||||||
|
:readOnly="true"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="logVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
@close="onCloseLog"
|
||||||
|
v-model="newNameVisiable"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
width="30%"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ $t('container.reName') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form ref="newNameRef" :model="reNameForm">
|
||||||
|
<el-form-item label="新名称" :rules="Rules.requiredInput" prop="newName">
|
||||||
|
<el-input v-model="reNameForm.newName"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="newNameVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||||
|
<el-button @click="onSubmitName(newNameRef)">{{ $t('commons.button.confirm') }}</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ComplexTable from '@/components/complex-table/index.vue';
|
||||||
|
import { Codemirror } from 'vue-codemirror';
|
||||||
|
import { javascript } from '@codemirror/lang-javascript';
|
||||||
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
|
import { reactive, onMounted, ref } from 'vue';
|
||||||
|
import { dateFromat, dateFromatForName } from '@/utils/util';
|
||||||
|
import { Rules } from '@/global/form-rules';
|
||||||
|
import { ContainerOperator, getContainerInspect, getContainerLog, getContainerPage } from '@/api/modules/container';
|
||||||
|
import { Container } from '@/api/interface/container';
|
||||||
|
import { ElForm, ElMessage, ElMessageBox, FormInstance } from 'element-plus';
|
||||||
|
import i18n from '@/lang';
|
||||||
|
|
||||||
|
const data = ref();
|
||||||
|
const selects = ref<any>([]);
|
||||||
|
const paginationConfig = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 100,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
const containerSearch = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 5,
|
||||||
|
status: 'all',
|
||||||
|
});
|
||||||
|
|
||||||
|
const detailVisiable = ref<boolean>(false);
|
||||||
|
const detailInfo = ref();
|
||||||
|
const extensions = [javascript(), oneDark];
|
||||||
|
const logVisiable = ref<boolean>(false);
|
||||||
|
const logInfo = ref();
|
||||||
|
const logSearch = reactive({
|
||||||
|
isWatch: false,
|
||||||
|
container: '',
|
||||||
|
containerID: '',
|
||||||
|
mode: 'all',
|
||||||
|
});
|
||||||
|
let timer: NodeJS.Timer | null = null;
|
||||||
|
|
||||||
|
const newNameVisiable = ref<boolean>(false);
|
||||||
|
type FormInstance = InstanceType<typeof ElForm>;
|
||||||
|
const newNameRef = ref<FormInstance>();
|
||||||
|
const reNameForm = reactive({
|
||||||
|
containerID: '',
|
||||||
|
operation: 'reName',
|
||||||
|
newName: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeOptions = ref([
|
||||||
|
{ label: i18n.global.t('container.all'), value: 'all' },
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.lastDay'),
|
||||||
|
value: new Date(new Date().getTime() - 3600 * 1000 * 24 * 1).getTime() / 1000 + '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.last4Hour'),
|
||||||
|
value: new Date(new Date().getTime() - 3600 * 1000 * 4).getTime() / 1000 + '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.lastHour'),
|
||||||
|
value: new Date(new Date().getTime() - 3600 * 1000).getTime() / 1000 + '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.last10Min'),
|
||||||
|
value: new Date(new Date().getTime() - 600 * 1000).getTime() / 1000 + '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const search = async () => {
|
||||||
|
containerSearch.page = paginationConfig.page;
|
||||||
|
containerSearch.pageSize = paginationConfig.pageSize;
|
||||||
|
await getContainerPage(containerSearch).then((res) => {
|
||||||
|
if (res.data) {
|
||||||
|
data.value = res.data.items;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCreate = async () => {};
|
||||||
|
const onDetail = async (row: Container.ContainerInfo) => {
|
||||||
|
const res = await getContainerInspect(row.containerID);
|
||||||
|
detailInfo.value = JSON.stringify(JSON.parse(res.data), null, 2);
|
||||||
|
detailVisiable.value = true;
|
||||||
|
};
|
||||||
|
const onLog = async (row: Container.ContainerInfo) => {
|
||||||
|
logSearch.container = row.name;
|
||||||
|
logSearch.containerID = row.containerID;
|
||||||
|
searchLogs();
|
||||||
|
logVisiable.value = true;
|
||||||
|
timer = setInterval(() => {
|
||||||
|
if (logVisiable.value && logSearch.isWatch) {
|
||||||
|
searchLogs();
|
||||||
|
}
|
||||||
|
}, 1000 * 5);
|
||||||
|
};
|
||||||
|
const onCloseLog = async () => {
|
||||||
|
clearInterval(Number(timer));
|
||||||
|
};
|
||||||
|
const searchLogs = async () => {
|
||||||
|
const res = await getContainerLog(logSearch);
|
||||||
|
logInfo.value = res.data;
|
||||||
|
};
|
||||||
|
const onDownload = async () => {
|
||||||
|
const downloadUrl = window.URL.createObjectURL(new Blob([logInfo.value]));
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.style.display = 'none';
|
||||||
|
a.href = downloadUrl;
|
||||||
|
a.download = logSearch.container + '-' + dateFromatForName(new Date()) + '.log';
|
||||||
|
const event = new MouseEvent('click');
|
||||||
|
a.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRename = async (row: Container.ContainerInfo) => {
|
||||||
|
reNameForm.containerID = row.containerID;
|
||||||
|
reNameForm.newName = '';
|
||||||
|
newNameVisiable.value = true;
|
||||||
|
};
|
||||||
|
const onSubmitName = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
formEl.validate(async (valid) => {
|
||||||
|
if (!valid) return;
|
||||||
|
ContainerOperator(reNameForm);
|
||||||
|
search();
|
||||||
|
newNameVisiable.value = false;
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkStatus = (operation: string) => {
|
||||||
|
if (selects.value.length < 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (operation) {
|
||||||
|
case 'start':
|
||||||
|
for (const item of selects.value) {
|
||||||
|
if (item.state === 'running') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case 'stop':
|
||||||
|
for (const item of selects.value) {
|
||||||
|
if (item.state === 'stopped') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case 'pause':
|
||||||
|
for (const item of selects.value) {
|
||||||
|
if (item.state === 'paused') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case 'unPause':
|
||||||
|
for (const item of selects.value) {
|
||||||
|
if (item.state !== 'paused') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onOperate = async (operation: string) => {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
i18n.global.t('container.operatorHelper', [operation]),
|
||||||
|
i18n.global.t('container.' + operation),
|
||||||
|
{
|
||||||
|
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||||
|
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||||
|
type: 'info',
|
||||||
|
},
|
||||||
|
).then(() => {
|
||||||
|
let ps = [];
|
||||||
|
for (const item of selects.value) {
|
||||||
|
const param = {
|
||||||
|
containerID: item.containerID,
|
||||||
|
operation: operation,
|
||||||
|
newName: '',
|
||||||
|
};
|
||||||
|
ps.push(ContainerOperator(param));
|
||||||
|
}
|
||||||
|
Promise.all(ps)
|
||||||
|
.then(() => {
|
||||||
|
search();
|
||||||
|
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
search();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttons = [
|
||||||
|
{
|
||||||
|
label: i18n.global.t('container.reName'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
onRename(row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.log'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
onLog(row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.global.t('commons.button.view'),
|
||||||
|
click: (row: Container.ContainerInfo) => {
|
||||||
|
onDetail(row);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
search();
|
||||||
|
});
|
||||||
|
</script>
|
0
frontend/src/views/container/image/index.vue
Normal file
0
frontend/src/views/container/image/index.vue
Normal file
@ -1,7 +1,71 @@
|
|||||||
<template>
|
<template>
|
||||||
<LayoutContent></LayoutContent>
|
<div>
|
||||||
|
<el-card class="topCard">
|
||||||
|
<el-radio-group v-model="activeNames">
|
||||||
|
<el-radio-button class="topButton" size="large" label="container">
|
||||||
|
{{ $t('container.container') }}
|
||||||
|
</el-radio-button>
|
||||||
|
<el-radio-button class="topButton" size="large" label="image">
|
||||||
|
{{ $t('container.image') }}
|
||||||
|
</el-radio-button>
|
||||||
|
<el-radio-button class="topButton" size="large" label="network">
|
||||||
|
{{ $t('container.network') }}
|
||||||
|
</el-radio-button>
|
||||||
|
<el-radio-button class="topButton" size="large" label="storage">
|
||||||
|
{{ $t('container.storage') }}
|
||||||
|
</el-radio-button>
|
||||||
|
<el-radio-button class="topButton" size="large" label="schedule">
|
||||||
|
{{ $t('container.schedule') }}
|
||||||
|
</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-card>
|
||||||
|
<Container v-if="activeNames === 'container'" />
|
||||||
|
<Safe v-if="activeNames === 'image'" />
|
||||||
|
<Backup v-if="activeNames === 'network'" />
|
||||||
|
<Monitor v-if="activeNames === 'storage'" />
|
||||||
|
<About v-if="activeNames === 'schedule'" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import LayoutContent from '@/layout/layout-content.vue';
|
import { ref } from 'vue';
|
||||||
|
import Container from '@/views/container/container/index.vue';
|
||||||
|
import Safe from '@/views/setting/tabs/safe.vue';
|
||||||
|
import Backup from '@/views/setting/tabs/backup.vue';
|
||||||
|
import Monitor from '@/views/setting/tabs/monitor.vue';
|
||||||
|
import About from '@/views/setting/tabs/about.vue';
|
||||||
|
|
||||||
|
const activeNames = ref('container');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.topCard {
|
||||||
|
--el-card-border-color: var(--el-border-color-light);
|
||||||
|
--el-card-border-radius: 4px;
|
||||||
|
--el-card-padding: 0px;
|
||||||
|
--el-card-bg-color: var(--el-fill-color-blank);
|
||||||
|
}
|
||||||
|
.topButton .el-radio-button__inner {
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
background: var(--el-button-bg-color, var(--el-fill-color-blank));
|
||||||
|
border: 0;
|
||||||
|
font-weight: 350;
|
||||||
|
border-left: 0;
|
||||||
|
color: var(--el-button-text-color, var(--el-text-color-regular));
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
outline: 0;
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--el-transition-all);
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
padding: 8px 15px;
|
||||||
|
font-size: var(--el-font-size-base);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="IP" prop="ip" />
|
<el-table-column label="IP" prop="ip" />
|
||||||
<el-table-column align="left" :label="$t('operations.request')" prop="path">
|
<el-table-column :label="$t('operations.request')" prop="path">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div>
|
<div>
|
||||||
<el-popover :width="500" v-if="row.body" placement="left-start" trigger="click">
|
<el-popover :width="500" v-if="row.body" placement="left-start" trigger="click">
|
||||||
@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column align="left" :label="$t('operations.response')" prop="path">
|
<el-table-column :label="$t('operations.response')" prop="path">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div>
|
<div>
|
||||||
<el-popover :width="500" v-if="row.resp" placement="left-start" trigger="click">
|
<el-popover :width="500" v-if="row.resp" placement="left-start" trigger="click">
|
||||||
|
@ -184,7 +184,7 @@ const form = withDefaults(defineProps<Props>(), {
|
|||||||
settingInfo: {
|
settingInfo: {
|
||||||
serverPort: '',
|
serverPort: '',
|
||||||
securityEntrance: '',
|
securityEntrance: '',
|
||||||
ExpirationTime: '',
|
expirationTime: '',
|
||||||
complexityVerification: '',
|
complexityVerification: '',
|
||||||
mfaStatus: '',
|
mfaStatus: '',
|
||||||
mfaSecret: '',
|
mfaSecret: '',
|
||||||
@ -237,14 +237,14 @@ const submitTimeout = async (formEl: FormInstance | undefined) => {
|
|||||||
formEl.validate(async (valid) => {
|
formEl.validate(async (valid) => {
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
let time = new Date(new Date().getTime() + 3600 * 1000 * 24 * timeoutForm.days);
|
let time = new Date(new Date().getTime() + 3600 * 1000 * 24 * timeoutForm.days);
|
||||||
await updateSetting({ key: 'ExpirationTime', value: dateFromat(0, 0, time) });
|
await updateSetting({ key: 'expirationTime', value: dateFromat(0, 0, time) });
|
||||||
form.settingInfo.ExpirationTime = dateFromat(0, 0, time);
|
form.settingInfo.expirationTime = dateFromat(0, 0, time);
|
||||||
timeoutVisiable.value = false;
|
timeoutVisiable.value = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function loadTimeOut() {
|
function loadTimeOut() {
|
||||||
let staytimeGap = new Date(form.settingInfo.ExpirationTime).getTime() - new Date().getTime();
|
let staytimeGap = new Date(form.settingInfo.expirationTime).getTime() - new Date().getTime();
|
||||||
return Math.floor(staytimeGap / (3600 * 1000 * 24));
|
return Math.floor(staytimeGap / (3600 * 1000 * 24));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
1
go.mod
1
go.mod
@ -143,4 +143,5 @@ require (
|
|||||||
google.golang.org/protobuf v1.28.0 // indirect
|
google.golang.org/protobuf v1.28.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.66.6 // indirect
|
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
|
gotest.tools/v3 v3.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
1
go.sum
1
go.sum
@ -883,6 +883,7 @@ gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE=
|
|||||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
|
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
|
||||||
|
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
76
package-lock.json
generated
Normal file
76
package-lock.json
generated
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"requires": true,
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/commands": {
|
||||||
|
"version": "6.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.1.tgz",
|
||||||
|
"integrity": "sha512-ibDohwkk7vyu3VsnZNlQhwk0OETBtlkYV+6AHfn5Zgq0sxa+yGVX+apwtC3M4wh6AH7yU5si/NysoECs5EGS3Q==",
|
||||||
|
"requires": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@codemirror/language": {
|
||||||
|
"version": "6.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.2.1.tgz",
|
||||||
|
"integrity": "sha512-MC3svxuvIj0MRpFlGHxLS6vPyIdbTr2KKPEW46kCoCXw2ktb4NTkpkPBI/lSP/FoNXLCBJ0mrnUi1OoZxtpW1Q==",
|
||||||
|
"requires": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@codemirror/state": {
|
||||||
|
"version": "6.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.1.2.tgz",
|
||||||
|
"integrity": "sha512-Mxff85Hp5va+zuj+H748KbubXjrinX/k28lj43H14T2D0+4kuvEFIEIO7hCEcvBT8ubZyIelt9yGOjj2MWOEQA=="
|
||||||
|
},
|
||||||
|
"@codemirror/view": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-jMN9OGKmzRPJ+kksfMrB5e/A9heQncirHsz8XNBpgEbYONCk5tWHMKVWKTNwznkUGD5mnigXI1i5YIcWpscSPg==",
|
||||||
|
"requires": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"style-mod": "^4.0.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@lezer/common": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-8TR5++Q/F//tpDsLd5zkrvEX5xxeemafEaek7mUp7Y+bI8cKQXdSqhzTOBaOogETcMOVr0pT3BBPXp13477ciw=="
|
||||||
|
},
|
||||||
|
"@lezer/highlight": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-duv9D23O9ghEDnnUDmxu+L8pJy4nYo4AbCOHIudUhscrLSazqeJeK1V50EU6ZufWF1zv0KJwu/frFRyZWXxHBQ==",
|
||||||
|
"requires": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@lezer/lr": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-qpB7rBzH8f6Mzjv2AVZRahcm+2Cf7nbIH++uXbvVOL1yIRvVWQ3HAM/saeBLCyz/togB7LGo76qdJYL1uKQlqA==",
|
||||||
|
"requires": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"style-mod": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw=="
|
||||||
|
},
|
||||||
|
"w3c-keyname": {
|
||||||
|
"version": "2.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
|
||||||
|
"integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user