Compare commits

...
11 Commits
Author SHA1 Message Date
zhengkunwang223 a18105349b fix: 解决某些版本的 ubuntu 启动不了的 BUG (#451) 2023-03-30 07:08:13 +00:00
wangdan-fit2cloud 6472227b6b fix: 修复登录页面从大屏切换小屏填写信息消失问题 (#450)
#### What this PR does / why we need it?

#### Summary of your change

#### Please indicate you've done the following:

- [ ] Made sure tests are passing and test coverage is added if needed.
- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).
- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed.
2023-03-30 06:42:13 +00:00
ssongliu c927132aa6 fix: 优化了概览页、监控页选项排序 2023-03-29 23:00:32 +08:00
ssongliu b06058ec18 fix: 修复了概览页未显示挂载磁盘信息的问题 (#442) 2023-03-29 13:28:13 +00:00
ssongliu 57329a26c8 fix: 修复了概览页 io 延迟数据错误的问题 (#441) 2023-03-29 12:56:13 +00:00
ssongliu 4a1aa84fa8 fix: 修改 jwt 超时时间为 1 小时 (#437) 2023-03-29 07:34:13 +00:00
ssongliu cbe9c83515 fix: 修复了 jwt 无法登录的问题 (#436) 2023-03-29 03:46:14 +00:00
ssongliu 67479e7060 fix: 修复了创建容器自动拉取镜像失败的问题 (#433) 2023-03-28 11:02:11 +00:00
ssongliu b454c959b4 fix: 修复了部分容器创建完直接退出的问题 (#432) 2023-03-28 09:50:11 +00:00
wangdan-fit2cloud e2d39b9ed0 fix: 登录页面优化适配 (#425) 2023-03-27 23:07:32 +08:00
maninhill b9fbcb0e73 chore: 简化社区软件协议文案 2023-03-26 10:14:25 +08:00
24 changed files with 319 additions and 797 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ build_web:
build_bin:
cd $(SERVER_PATH) \
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -tags osusergo -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
&& CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w --extldflags "-static -fpic"' -tags 'osusergo,netgo' -o $(BUILD_PATH)/$(APP_NAME) $(MAIN)
build_linux_on_mac:
cd $(SERVER_PATH) \
+5 -3
View File
@@ -28,9 +28,11 @@ func (b *BaseApi) Login(c *gin.Context) {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
if req.AuthMethod != "jwt" {
if err := captcha.VerifyCode(req.CaptchaID, req.Captcha); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
}
user, err := authService.Login(c, req)
+3
View File
@@ -1,6 +1,7 @@
package v1
import (
"sort"
"time"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
@@ -88,6 +89,7 @@ func (b *BaseApi) GetNetworkOptions(c *gin.Context) {
for _, net := range netStat {
options = append(options, net.Name)
}
sort.Strings(options)
helper.SuccessWithData(c, options)
}
@@ -98,5 +100,6 @@ func (b *BaseApi) GetIOOptions(c *gin.Context) {
for _, net := range diskStat {
options = append(options, net.Name)
}
sort.Strings(options)
helper.SuccessWithData(c, options)
}
+14 -13
View File
@@ -3,13 +3,6 @@ package dto
import "time"
type DashboardBase struct {
HaloID uint `json:"haloID"`
DateeaseID uint `json:"dateeaseID"`
JumpServerID uint `json:"jumpserverID"`
MeterSphereID uint `json:"metersphereID"`
KubeoperatorID uint `json:"kubeoperatorID"`
KubepiID uint `json:"kubepiID"`
WebsiteNumber int `json:"websiteNumber"`
DatabaseNumber int `json:"databaseNumber"`
CronjobNumber int `json:"cronjobNumber"`
@@ -55,8 +48,21 @@ type DashboardCurrent struct {
IOReadBytes uint64 `json:"ioReadBytes"`
IOWriteBytes uint64 `json:"ioWriteBytes"`
IOCount uint64 `json:"ioCount"`
IOTime uint64 `json:"ioTime"`
IOReadTime uint64 `json:"ioReadTime"`
IOWriteTime uint64 `json:"ioWriteTime"`
DiskData []DiskInfo `json:"diskData"`
NetBytesSent uint64 `json:"netBytesSent"`
NetBytesRecv uint64 `json:"netBytesRecv"`
ShotTime time.Time `json:"shotTime"`
}
type DiskInfo struct {
Path string `json:"path"`
Type string `json:"type"`
Device string `json:"device"`
Total uint64 `json:"total"`
Free uint64 `json:"free"`
Used uint64 `json:"used"`
@@ -66,9 +72,4 @@ type DashboardCurrent struct {
InodesUsed uint64 `json:"inodesUsed"`
InodesFree uint64 `json:"inodesFree"`
InodesUsedPercent float64 `json:"inodesUsedPercent"`
NetBytesSent uint64 `json:"netBytesSent"`
NetBytesRecv uint64 `json:"netBytesRecv"`
ShotTime time.Time `json:"shotTime"`
}
+1 -1
View File
@@ -118,7 +118,7 @@ func (u *AuthService) generateSession(c *gin.Context, name, authMethod string) (
j := jwt.NewJWT()
claims := j.CreateClaims(jwt.BaseClaims{
Name: name,
}, lifeTime)
})
token, err := j.CreateToken(claims)
if err != nil {
return nil, err
+30 -8
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os/exec"
"sort"
@@ -22,6 +23,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
@@ -156,10 +158,12 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
return err
}
config := &container.Config{
Image: req.Image,
Cmd: req.Cmd,
Env: req.Env,
Labels: stringsToMap(req.Labels),
Image: req.Image,
Cmd: req.Cmd,
Env: req.Env,
Labels: stringsToMap(req.Labels),
Tty: true,
OpenStdin: true,
}
hostConf := &container.HostConfig{
AutoRemove: req.AutoRemove,
@@ -191,14 +195,19 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
}
global.LOG.Infof("new container info %s has been made, now start to create", req.Name)
container, err := client.ContainerCreate(context.TODO(), config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
ctx := context.Background()
if err := pullImages(ctx, client, req.Image); err != nil {
return err
}
container, err := client.ContainerCreate(ctx, config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
if err != nil {
_ = client.ContainerRemove(context.Background(), req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
return err
}
global.LOG.Infof("create container %s successful! now check if the container is started and delete the container information if it is not.", req.Name)
if err := client.ContainerStart(context.TODO(), container.ID, types.ContainerStartOptions{}); err != nil {
_ = client.ContainerRemove(context.Background(), req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
if err := client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
_ = client.ContainerRemove(ctx, req.Name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
return fmt.Errorf("create successful but start failed, err: %v", err)
}
return nil
@@ -321,3 +330,16 @@ func calculateNetwork(network map[string]types.NetworkStats) (float64, float64)
}
return rx, tx
}
func pullImages(ctx context.Context, client *client.Client, image string) error {
out, err := client.ImagePull(ctx, image, types.ImagePullOptions{})
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(ioutil.Discard, out)
if err != nil {
return err
}
return nil
}
+42 -38
View File
@@ -39,27 +39,6 @@ func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto
ss, _ := json.Marshal(hostInfo)
baseInfo.VirtualizationSystem = string(ss)
apps, err := appRepo.GetBy()
if err != nil {
return nil, err
}
for _, app := range apps {
switch app.Key {
case "dateease":
baseInfo.DateeaseID = app.ID
case "halo":
baseInfo.HaloID = app.ID
case "metersphere":
baseInfo.MeterSphereID = app.ID
case "jumpserver":
baseInfo.JumpServerID = app.ID
case "kubeoperator":
baseInfo.KubeoperatorID = app.ID
case "kubepi":
baseInfo.KubepiID = app.ID
}
}
appInstall, err := appInstallRepo.ListBy()
if err != nil {
return nil, err
@@ -120,15 +99,7 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
currentInfo.MemoryUsed = memoryInfo.Used
currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent
state, _ := disk.Usage("/")
currentInfo.Total = state.Total
currentInfo.Free = state.Free
currentInfo.Used = state.Used
currentInfo.UsedPercent = state.UsedPercent
currentInfo.InodesTotal = state.InodesTotal
currentInfo.InodesUsed = state.InodesUsed
currentInfo.InodesFree = state.InodesFree
currentInfo.InodesUsedPercent = state.InodesUsedPercent
currentInfo.DiskData = loadDiskInfo()
if ioOption == "all" {
diskInfo, _ := disk.IOCounters()
@@ -136,20 +107,17 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOTime += state.ReadTime / 1000 / 1000
if state.WriteTime > state.ReadTime {
currentInfo.IOTime += state.WriteTime / 1000 / 1000
}
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
}
} else {
diskInfo, _ := disk.IOCounters(ioOption)
for _, state := range diskInfo {
currentInfo.IOReadBytes += state.ReadBytes
currentInfo.IOWriteBytes += state.WriteBytes
currentInfo.IOTime += state.ReadTime / 1000 / 1000
if state.WriteTime > state.ReadTime {
currentInfo.IOTime += state.WriteTime / 1000 / 1000
}
currentInfo.IOCount += (state.ReadCount + state.WriteCount)
currentInfo.IOReadTime += state.ReadTime
currentInfo.IOWriteTime += state.WriteTime
}
}
@@ -172,3 +140,39 @@ func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *d
currentInfo.ShotTime = time.Now()
return &currentInfo
}
func loadDiskInfo() []dto.DiskInfo {
var datas []dto.DiskInfo
parts, err := disk.Partitions(false)
if err != nil {
return datas
}
var excludes = []string{"/mnt/cdrom", "/boot", "/boot/efi", "/dev", "/dev/shm", "/run/lock", "/run", "/run/shm", "/run/user"}
for i := 0; i < len(parts); i++ {
isExclude := false
for _, exclude := range excludes {
if parts[i].Mountpoint == exclude {
isExclude = true
break
}
}
if isExclude {
continue
}
state, _ := disk.Usage(parts[i].Mountpoint)
var itemData dto.DiskInfo
itemData.Path = parts[i].Mountpoint
itemData.Type = parts[i].Fstype
itemData.Device = parts[i].Device
itemData.Total = state.Total
itemData.Free = state.Free
itemData.Used = state.Used
itemData.UsedPercent = state.UsedPercent
itemData.InodesTotal = state.InodesTotal
itemData.InodesUsed = state.InodesUsed
itemData.InodesFree = state.InodesFree
itemData.InodesUsedPercent = state.InodesUsedPercent
datas = append(datas, itemData)
}
return datas
}
+1 -1
View File
@@ -6,7 +6,7 @@ const (
AuthMethodJWT = "jwt"
JWTHeaderName = "Authorization"
JWTBufferTime = 86400
JWTBufferTime = 3600
JWTIssuer = "1Panel"
PasswordExpiredName = "expired"
+4
View File
@@ -63,6 +63,10 @@ func Routers() *gin.Engine {
systemRouter := rou.RouterGroupApp
swaggerRouter := Router.Group("1panel")
docs.SwaggerInfo.BasePath = "/api/v1"
swaggerRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth()).GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
PublicGroup := Router.Group("")
{
PublicGroup.GET("/health", func(c *gin.Context) {
-16
View File
@@ -1,22 +1,15 @@
package middleware
import (
"strconv"
"time"
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
jwtUtils "github.com/1Panel-dev/1Panel/backend/utils/jwt"
"github.com/golang-jwt/jwt/v4"
"github.com/gin-gonic/gin"
)
func JwtAuth() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("authMethod", "")
token := c.Request.Header.Get(constant.JWTHeaderName)
if token == "" {
c.Next()
@@ -28,15 +21,6 @@ func JwtAuth() gin.HandlerFunc {
helper.ErrorWithDetail(c, constant.CodeErrUnauthorized, constant.ErrTypeInternalServer, err)
return
}
if claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime {
settingRepo := repo.NewISettingRepo()
setting, err := settingRepo.Get(settingRepo.WithByKey("SessionTimeout"))
if err != nil {
global.LOG.Errorf("create operation record failed, err: %v", err)
}
lifeTime, _ := strconv.Atoi(setting.Value)
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(lifeTime)))
}
c.Set("claims", claims)
c.Set("authMethod", constant.AuthMethodJWT)
c.Next()
+1
View File
@@ -14,6 +14,7 @@ func SessionAuth() gin.HandlerFunc {
return func(c *gin.Context) {
if method, exist := c.Get("authMethod"); exist && method == constant.AuthMethodJWT {
c.Next()
return
}
sId, err := c.Cookie(constant.SessionName)
if err != nil {
+2 -2
View File
@@ -38,12 +38,12 @@ func NewJWT() *JWT {
}
}
func (j *JWT) CreateClaims(baseClaims BaseClaims, ttl int) CustomClaims {
func (j *JWT) CreateClaims(baseClaims BaseClaims) CustomClaims {
claims := CustomClaims{
BaseClaims: baseClaims,
BufferTime: constant.JWTBufferTime,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(ttl))),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(constant.JWTBufferTime))),
Issuer: constant.JWTIssuer,
},
}
+48 -226
View File
@@ -1073,7 +1073,7 @@ var doc = `{
],
"responses": {
"200": {
"description": "OK"
"description": ""
}
},
"x-panel-log": {
@@ -6914,7 +6914,7 @@ var doc = `{
],
"responses": {
"200": {
"description": "OK"
"description": ""
}
}
},
@@ -7814,161 +7814,6 @@ var doc = `{
}
}
},
"/websites/groups": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取网站组",
"tags": [
"Website Group"
],
"summary": "List website groups",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "anrry"
}
}
}
},
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "创建网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Create website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteGroupCreate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "Create website groups [name]",
"formatZH": "创建网站组 [name]",
"paramKeys": []
}
}
},
"/websites/groups/del": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "删除网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Delete website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteResourceReq"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "website_groups",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Delete website group [name]",
"formatZH": "删除网站组 [name]",
"paramKeys": []
}
}
},
"/websites/groups/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Update website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteGroupUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "Update website groups [name]",
"formatZH": "更新网站组 [name]",
"paramKeys": []
}
}
},
"/websites/list": {
"get": {
"security": [
@@ -9326,33 +9171,15 @@ var doc = `{
"databaseNumber": {
"type": "integer"
},
"dateeaseID": {
"type": "integer"
},
"haloID": {
"type": "integer"
},
"hostname": {
"type": "string"
},
"jumpserverID": {
"type": "integer"
},
"kernelArch": {
"type": "string"
},
"kernelVersion": {
"type": "string"
},
"kubeoperatorID": {
"type": "integer"
},
"kubepiID": {
"type": "integer"
},
"metersphereID": {
"type": "integer"
},
"os": {
"type": "string"
},
@@ -9394,20 +9221,11 @@ var doc = `{
"cpuUsedPercent": {
"type": "number"
},
"free": {
"type": "integer"
},
"inodesFree": {
"type": "integer"
},
"inodesTotal": {
"type": "integer"
},
"inodesUsed": {
"type": "integer"
},
"inodesUsedPercent": {
"type": "number"
"diskData": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.DiskInfo"
}
},
"ioCount": {
"type": "integer"
@@ -9415,12 +9233,15 @@ var doc = `{
"ioReadBytes": {
"type": "integer"
},
"ioTime": {
"ioReadTime": {
"type": "integer"
},
"ioWriteBytes": {
"type": "integer"
},
"ioWriteTime": {
"type": "integer"
},
"load1": {
"type": "number"
},
@@ -9457,11 +9278,40 @@ var doc = `{
"timeSinceUptime": {
"type": "string"
},
"uptime": {
"type": "integer"
}
}
},
"dto.DiskInfo": {
"type": "object",
"properties": {
"device": {
"type": "string"
},
"free": {
"type": "integer"
},
"inodesFree": {
"type": "integer"
},
"inodesTotal": {
"type": "integer"
},
"inodesUsed": {
"type": "integer"
},
"inodesUsedPercent": {
"type": "number"
},
"path": {
"type": "string"
},
"total": {
"type": "integer"
},
"uptime": {
"type": "integer"
"type": {
"type": "string"
},
"used": {
"type": "integer"
@@ -9583,6 +9433,9 @@ var doc = `{
},
"dto.GroupUpdate": {
"type": "object",
"required": [
"type"
],
"properties": {
"id": {
"type": "integer"
@@ -9592,6 +9445,9 @@ var doc = `{
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
@@ -9913,9 +9769,6 @@ var doc = `{
},
"password": {
"type": "string"
},
"secret": {
"type": "string"
}
}
},
@@ -10761,9 +10614,6 @@ var doc = `{
"dto.UserLoginInfo": {
"type": "object",
"properties": {
"mfaSecret": {
"type": "string"
},
"mfaStatus": {
"type": "string"
},
@@ -11869,34 +11719,6 @@ var doc = `{
}
}
},
"request.WebsiteGroupCreate": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
},
"request.WebsiteGroupUpdate": {
"type": "object",
"required": [
"id"
],
"properties": {
"default": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"request.WebsiteHTTPSOp": {
"type": "object",
"required": [
+48 -226
View File
@@ -1059,7 +1059,7 @@
],
"responses": {
"200": {
"description": "OK"
"description": ""
}
},
"x-panel-log": {
@@ -6900,7 +6900,7 @@
],
"responses": {
"200": {
"description": "OK"
"description": ""
}
}
},
@@ -7800,161 +7800,6 @@
}
}
},
"/websites/groups": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "获取网站组",
"tags": [
"Website Group"
],
"summary": "List website groups",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "anrry"
}
}
}
},
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "创建网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Create website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteGroupCreate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "Create website groups [name]",
"formatZH": "创建网站组 [name]",
"paramKeys": []
}
}
},
"/websites/groups/del": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "删除网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Delete website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteResourceReq"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [
{
"db": "website_groups",
"input_colume": "id",
"input_value": "id",
"isList": false,
"output_colume": "name",
"output_value": "name"
}
],
"bodyKeys": [
"id"
],
"formatEN": "Delete website group [name]",
"formatZH": "删除网站组 [name]",
"paramKeys": []
}
}
},
"/websites/groups/update": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "更新网站组",
"consumes": [
"application/json"
],
"tags": [
"Website Group"
],
"summary": "Update website group",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.WebsiteGroupUpdate"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"x-panel-log": {
"BeforeFuntions": [],
"bodyKeys": [
"name"
],
"formatEN": "Update website groups [name]",
"formatZH": "更新网站组 [name]",
"paramKeys": []
}
}
},
"/websites/list": {
"get": {
"security": [
@@ -9312,33 +9157,15 @@
"databaseNumber": {
"type": "integer"
},
"dateeaseID": {
"type": "integer"
},
"haloID": {
"type": "integer"
},
"hostname": {
"type": "string"
},
"jumpserverID": {
"type": "integer"
},
"kernelArch": {
"type": "string"
},
"kernelVersion": {
"type": "string"
},
"kubeoperatorID": {
"type": "integer"
},
"kubepiID": {
"type": "integer"
},
"metersphereID": {
"type": "integer"
},
"os": {
"type": "string"
},
@@ -9380,20 +9207,11 @@
"cpuUsedPercent": {
"type": "number"
},
"free": {
"type": "integer"
},
"inodesFree": {
"type": "integer"
},
"inodesTotal": {
"type": "integer"
},
"inodesUsed": {
"type": "integer"
},
"inodesUsedPercent": {
"type": "number"
"diskData": {
"type": "array",
"items": {
"$ref": "#/definitions/dto.DiskInfo"
}
},
"ioCount": {
"type": "integer"
@@ -9401,12 +9219,15 @@
"ioReadBytes": {
"type": "integer"
},
"ioTime": {
"ioReadTime": {
"type": "integer"
},
"ioWriteBytes": {
"type": "integer"
},
"ioWriteTime": {
"type": "integer"
},
"load1": {
"type": "number"
},
@@ -9443,11 +9264,40 @@
"timeSinceUptime": {
"type": "string"
},
"uptime": {
"type": "integer"
}
}
},
"dto.DiskInfo": {
"type": "object",
"properties": {
"device": {
"type": "string"
},
"free": {
"type": "integer"
},
"inodesFree": {
"type": "integer"
},
"inodesTotal": {
"type": "integer"
},
"inodesUsed": {
"type": "integer"
},
"inodesUsedPercent": {
"type": "number"
},
"path": {
"type": "string"
},
"total": {
"type": "integer"
},
"uptime": {
"type": "integer"
"type": {
"type": "string"
},
"used": {
"type": "integer"
@@ -9569,6 +9419,9 @@
},
"dto.GroupUpdate": {
"type": "object",
"required": [
"type"
],
"properties": {
"id": {
"type": "integer"
@@ -9578,6 +9431,9 @@
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
@@ -9899,9 +9755,6 @@
},
"password": {
"type": "string"
},
"secret": {
"type": "string"
}
}
},
@@ -10747,9 +10600,6 @@
"dto.UserLoginInfo": {
"type": "object",
"properties": {
"mfaSecret": {
"type": "string"
},
"mfaStatus": {
"type": "string"
},
@@ -11855,34 +11705,6 @@
}
}
},
"request.WebsiteGroupCreate": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
},
"request.WebsiteGroupUpdate": {
"type": "object",
"required": [
"id"
],
"properties": {
"default": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
}
}
},
"request.WebsiteHTTPSOp": {
"type": "object",
"required": [
+34 -148
View File
@@ -443,24 +443,12 @@ definitions:
$ref: '#/definitions/dto.DashboardCurrent'
databaseNumber:
type: integer
dateeaseID:
type: integer
haloID:
type: integer
hostname:
type: string
jumpserverID:
type: integer
kernelArch:
type: string
kernelVersion:
type: string
kubeoperatorID:
type: integer
kubepiID:
type: integer
metersphereID:
type: integer
os:
type: string
platform:
@@ -488,24 +476,20 @@ definitions:
type: number
cpuUsedPercent:
type: number
free:
type: integer
inodesFree:
type: integer
inodesTotal:
type: integer
inodesUsed:
type: integer
inodesUsedPercent:
type: number
diskData:
items:
$ref: '#/definitions/dto.DiskInfo'
type: array
ioCount:
type: integer
ioReadBytes:
type: integer
ioTime:
ioReadTime:
type: integer
ioWriteBytes:
type: integer
ioWriteTime:
type: integer
load1:
type: number
load5:
@@ -530,10 +514,29 @@ definitions:
type: string
timeSinceUptime:
type: string
total:
type: integer
uptime:
type: integer
type: object
dto.DiskInfo:
properties:
device:
type: string
free:
type: integer
inodesFree:
type: integer
inodesTotal:
type: integer
inodesUsed:
type: integer
inodesUsedPercent:
type: number
path:
type: string
total:
type: integer
type:
type: string
used:
type: integer
usedPercent:
@@ -622,6 +625,10 @@ definitions:
type: boolean
name:
type: string
type:
type: string
required:
- type
type: object
dto.HostConnTest:
properties:
@@ -837,8 +844,6 @@ definitions:
type: string
password:
type: string
secret:
type: string
type: object
dto.MfaCredential:
properties:
@@ -1398,8 +1403,6 @@ definitions:
type: object
dto.UserLoginInfo:
properties:
mfaSecret:
type: string
mfaStatus:
type: string
name:
@@ -2135,24 +2138,6 @@ definitions:
required:
- id
type: object
request.WebsiteGroupCreate:
properties:
name:
type: string
required:
- name
type: object
request.WebsiteGroupUpdate:
properties:
default:
type: boolean
id:
type: integer
name:
type: string
required:
- id
type: object
request.WebsiteHTTPSOp:
properties:
HttpConfig:
@@ -3291,7 +3276,7 @@ paths:
$ref: '#/definitions/dto.ComposeCreate'
responses:
"200":
description: OK
description: ""
security:
- ApiKeyAuth: []
summary: Test compose
@@ -7008,7 +6993,7 @@ paths:
$ref: '#/definitions/dto.Upgrade'
responses:
"200":
description: OK
description: ""
security:
- ApiKeyAuth: []
summary: Load release notes by version
@@ -7584,105 +7569,6 @@ paths:
formatEN: Delete domain [domain]
formatZH: 删除域名 [domain]
paramKeys: []
/websites/groups:
get:
description: 获取网站组
responses:
"200":
description: OK
schema:
type: anrry
security:
- ApiKeyAuth: []
summary: List website groups
tags:
- Website Group
post:
consumes:
- application/json
description: 创建网站组
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteGroupCreate'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Create website group
tags:
- Website Group
x-panel-log:
BeforeFuntions: []
bodyKeys:
- name
formatEN: Create website groups [name]
formatZH: 创建网站组 [name]
paramKeys: []
/websites/groups/del:
post:
consumes:
- application/json
description: 删除网站组
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteResourceReq'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Delete website group
tags:
- Website Group
x-panel-log:
BeforeFuntions:
- db: website_groups
input_colume: id
input_value: id
isList: false
output_colume: name
output_value: name
bodyKeys:
- id
formatEN: Delete website group [name]
formatZH: 删除网站组 [name]
paramKeys: []
/websites/groups/update:
post:
consumes:
- application/json
description: 更新网站组
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/request.WebsiteGroupUpdate'
responses:
"200":
description: ""
security:
- ApiKeyAuth: []
summary: Update website group
tags:
- Website Group
x-panel-log:
BeforeFuntions: []
bodyKeys:
- name
formatEN: Update website groups [name]
formatZH: 更新网站组 [name]
paramKeys: []
/websites/list:
get:
description: 获取网站列表
+13 -13
View File
@@ -1,12 +1,5 @@
export namespace Dashboard {
export interface BaseInfo {
haloID: number;
dateeaseID: number;
jumpserverID: number;
metersphereID: number;
kubeoperatorID: number;
kubepiID: number;
websiteNumber: number;
databaseNumber: number;
cronjobNumber: number;
@@ -49,9 +42,21 @@ export namespace Dashboard {
ioReadBytes: number;
ioWriteBytes: number;
ioTime: number;
ioCount: number;
ioReadTime: number;
ioWriteTime: number;
diskData: Array<DiskInfo>;
netBytesSent: number;
netBytesRecv: number;
shotTime: Date;
}
export interface DiskInfo {
path: string;
type: string;
device: string;
total: number;
free: number;
used: number;
@@ -61,10 +66,5 @@ export namespace Dashboard {
inodesUsed: number;
inodesFree: number;
inodesUsedPercent: number;
netBytesSent: number;
netBytesRecv: number;
shotTime: Date;
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ export const searchContainer = (params: Container.ContainerSearch) => {
return http.post<ResPage<Container.ContainerInfo>>(`/containers/search`, params);
};
export const createContainer = (params: Container.ContainerCreate) => {
return http.post(`/containers`, params, 1200000);
return http.post(`/containers`, params, 3000000);
};
export const logContainer = (params: Container.ContainerLogSearch) => {
return http.post<string>(`/containers/search/log`, params, 400000);
+2
View File
@@ -229,6 +229,7 @@ const message = {
kernelArch: 'Kernel arch',
network: 'Network',
io: 'Disk IO',
baseInfo: 'Base info',
totalSend: 'Total send',
totalRecv: 'Total recv',
rwPerSecond: 'IO times',
@@ -251,6 +252,7 @@ const message = {
loadAverage: 'Average load in the last {0} minutes',
load: 'Load',
mount: 'Mount point',
fileSystem: 'File system',
total: 'Total',
used: 'Used',
free: 'Free',
+3 -1
View File
@@ -127,7 +127,7 @@ const message = {
mfaCode: 'MFA 验证码',
title: 'Linux 服务器运维管理面板',
licenseHelper:
'同意 FIT2CLOUD 飞致云 &laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank"> 社区软件许可协议</a> &raquo;',
'同意 &laquo; <a href="https://www.fit2cloud.com/legal/licenses.html" target="_blank"> 飞致云社区软件许可协议</a> &raquo;',
errorAgree: '请点击同意社区软件许可协议',
},
rule: {
@@ -233,6 +233,7 @@ const message = {
kernelArch: '系统类型',
network: '流量',
io: '磁盘 IO',
baseInfo: '基本信息',
totalSend: '总发送',
totalRecv: '总接收',
rwPerSecond: '读写次数',
@@ -255,6 +256,7 @@ const message = {
loadAverage: '最近 {0} 分钟平均负载',
load: '负载',
mount: '挂载点',
fileSystem: '文件系统',
total: '总数',
used: '已用',
free: '可用',
+1 -1
View File
@@ -202,7 +202,7 @@ const buttons = [
{
label: i18n.global.t('commons.button.delete'),
click: async (row: Container.ImageInfo) => {
if (row.tags.length <= 1) {
if (!row.tags?.length || row.tags.length <= 1) {
await useDeleteData(imageRemove, { names: [row.id] }, 'commons.msg.delete');
search();
return;
+1 -18
View File
@@ -41,7 +41,6 @@
<script lang="ts" setup>
import { App } from '@/api/interface/app';
import { Dashboard } from '@/api/interface/dashboard';
import { SearchApp } from '@/api/modules/app';
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
@@ -57,26 +56,10 @@ let req = reactive({
recommend: true,
});
const baseInfo = ref({
haloID: 0,
dateeaseID: 0,
jumpserverID: 0,
metersphereID: 0,
kubeoperatorID: 0,
kubepiID: 0,
});
let loading = ref(false);
let apps = ref<App.App[]>([]);
const acceptParams = (base: Dashboard.BaseInfo): void => {
baseInfo.value.haloID = base.haloID;
baseInfo.value.dateeaseID = base.dateeaseID;
baseInfo.value.jumpserverID = base.jumpserverID;
baseInfo.value.metersphereID = base.metersphereID;
baseInfo.value.kubeoperatorID = base.kubeoperatorID;
baseInfo.value.kubepiID = base.kubepiID;
const acceptParams = (): void => {
search(req);
};
+11 -20
View File
@@ -104,7 +104,7 @@
<el-tag>
{{ $t('home.rwPerSecond') }}: {{ currentChartInfo.ioCount }} {{ $t('home.time') }}
</el-tag>
<el-tag>{{ $t('home.ioDelay') }}: {{ currentInfo.ioTime }} ms</el-tag>
<el-tag>{{ $t('home.ioDelay') }}: {{ currentChartInfo.ioTime }} ms</el-tag>
</div>
<div v-if="chartOption === 'io'" style="margin-top: 40px">
@@ -234,13 +234,6 @@ const searchInfo = reactive({
});
const baseInfo = ref<Dashboard.BaseInfo>({
haloID: 0,
dateeaseID: 0,
jumpserverID: 0,
metersphereID: 0,
kubeoperatorID: 0,
kubepiID: 0,
websiteNumber: 0,
databaseNumber: 0,
cronjobNumber: 0,
@@ -282,18 +275,11 @@ const currentInfo = ref<Dashboard.CurrentInfo>({
ioReadBytes: 0,
ioWriteBytes: 0,
ioTime: 0,
ioCount: 0,
ioReadTime: 0,
ioWriteTime: 0,
total: 0,
free: 0,
used: 0,
usedPercent: 0,
inodesTotal: 0,
inodesUsed: 0,
inodesFree: 0,
inodesUsedPercent: 0,
diskData: [],
netBytesSent: 0,
netBytesRecv: 0,
@@ -304,6 +290,7 @@ const currentChartInfo = reactive({
ioReadBytes: 0,
ioWriteBytes: 0,
ioCount: 0,
ioTime: 0,
netBytesSent: 0,
netBytesRecv: 0,
@@ -347,7 +334,7 @@ const onLoadBaseInfo = async (isInit: boolean, range: string) => {
currentInfo.value = baseInfo.value.currentInfo;
onLoadCurrentInfo();
statuRef.value.acceptParams(currentInfo.value, baseInfo.value);
appRef.value.acceptParams(baseInfo.value);
appRef.value.acceptParams();
if (isInit) {
// window.addEventListener('resize', changeChartSize);
timer = setInterval(async () => {
@@ -389,7 +376,11 @@ const onLoadCurrentInfo = async () => {
if (ioWriteBytes.value.length > 20) {
ioWriteBytes.value.splice(0, 1);
}
currentChartInfo.ioCount = Number(((res.data.ioCount - currentInfo.value.ioCount) / 3).toFixed(2));
currentChartInfo.ioCount = Math.round(Number((res.data.ioCount - currentInfo.value.ioCount) / 3));
let ioReadTime = res.data.ioReadTime - currentInfo.value.ioReadTime;
let ioWriteTime = res.data.ioWriteTime - currentInfo.value.ioWriteTime;
let ioChoose = ioReadTime > ioWriteTime ? ioReadTime : ioWriteTime;
currentChartInfo.ioTime = Math.round(Number(ioChoose / 3));
timeIODatas.value.push(dateFormatForSecond(res.data.shotTime));
if (timeIODatas.value.length > 20) {
+32 -35
View File
@@ -60,45 +60,48 @@
</el-popover>
<span class="input-help">{{ loadStatus(currentInfo.loadUsagePercent) }}</span>
</el-col>
<el-col :span="6" align="center">
<el-popover placement="bottom" :width="260" trigger="hover">
<el-col :span="6" align="center" v-for="(item, index) of currentInfo.diskData" :key="index">
<el-popover placement="bottom" :width="300" trigger="hover">
<el-row :gutter="5">
<el-col :span="12">
<el-tag>{{ $t('home.mount') }}: /</el-tag>
<div><el-tag class="tagClass">iNode</el-tag></div>
<el-tag class="tagClass">{{ $t('home.total') }}: {{ currentInfo.inodesTotal }}</el-tag>
<el-tag class="tagClass">{{ $t('home.used') }}: {{ currentInfo.inodesUsed }}</el-tag>
<el-tag class="tagClass">{{ $t('home.free') }}: {{ currentInfo.inodesFree }}</el-tag>
<el-tag style="font-weight: 500">{{ $t('home.baseInfo') }}:</el-tag>
<el-tag class="tagClass">{{ $t('home.mount') }}: {{ item.path }}</el-tag>
<el-tag class="tagClass">{{ $t('commons.table.type') }}: {{ item.type }}</el-tag>
<el-tag class="tagClass">{{ $t('home.fileSystem') }}: {{ item.device }}</el-tag>
<div><el-tag class="tagClass" style="font-weight: 500">Inode:</el-tag></div>
<el-tag class="tagClass">{{ $t('home.total') }}: {{ item.inodesTotal }}</el-tag>
<el-tag class="tagClass">{{ $t('home.used') }}: {{ item.inodesUsed }}</el-tag>
<el-tag class="tagClass">{{ $t('home.free') }}: {{ item.inodesFree }}</el-tag>
<el-tag class="tagClass">
{{ $t('home.percent') }}: {{ formatNumber(currentInfo.inodesUsedPercent) }}%
{{ $t('home.percent') }}: {{ formatNumber(item.inodesUsedPercent) }}%
</el-tag>
</el-col>
<el-col :span="12">
<div>
<el-tag style="margin-top: 27px">{{ $t('monitor.disk') }}</el-tag>
<el-tag style="margin-top: 108px; font-weight: 500">{{ $t('monitor.disk') }}:</el-tag>
</div>
<el-tag class="tagClass">
{{ $t('home.total') }}: {{ formatNumber(currentInfo.total / 1024 / 1024 / 1024) }} GB
{{ $t('home.total') }}: {{ formatNumber(item.total / 1024 / 1024 / 1024) }} GB
</el-tag>
<el-tag class="tagClass">
{{ $t('home.used') }}: {{ formatNumber(currentInfo.used / 1024 / 1024 / 1024) }} GB
{{ $t('home.used') }}: {{ formatNumber(item.used / 1024 / 1024 / 1024) }} GB
</el-tag>
<el-tag class="tagClass">
{{ $t('home.free') }}: {{ formatNumber(currentInfo.free / 1024 / 1024 / 1024) }} GB
{{ $t('home.free') }}: {{ formatNumber(item.free / 1024 / 1024 / 1024) }} GB
</el-tag>
<el-tag class="tagClass">
{{ $t('home.percent') }}: {{ formatNumber(currentInfo.usedPercent) }}%
{{ $t('home.percent') }}: {{ formatNumber(item.usedPercent) }}%
</el-tag>
</el-col>
</el-row>
<template #reference>
<div id="disk" class="chartClass"></div>
<div :id="`disk${index}`" class="chartClass"></div>
</template>
</el-popover>
<span class="input-help">
( {{ formatNumber(currentInfo.used / 1024 / 1024 / 1024) }} /
{{ formatNumber(currentInfo.total / 1024 / 1024 / 1024) }} ) GB
( {{ formatNumber(item.used / 1024 / 1024 / 1024) }} /
{{ formatNumber(item.total / 1024 / 1024 / 1024) }} ) GB
</span>
</el-col>
</el-row>
@@ -108,18 +111,11 @@
import { Dashboard } from '@/api/interface/dashboard';
import i18n from '@/lang';
import * as echarts from 'echarts';
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
const baseInfo = ref<Dashboard.BaseInfo>({
haloID: 0,
dateeaseID: 0,
jumpserverID: 0,
metersphereID: 0,
kubeoperatorID: 0,
kubepiID: 0,
websiteNumber: 0,
databaseNumber: 0,
cronjobNumber: 0,
@@ -161,18 +157,11 @@ const currentInfo = ref<Dashboard.CurrentInfo>({
ioReadBytes: 0,
ioWriteBytes: 0,
ioTime: 0,
ioCount: 0,
ioReadTime: 0,
ioWriteTime: 0,
total: 0,
free: 0,
used: 0,
usedPercent: 0,
inodesTotal: 0,
inodesUsed: 0,
inodesFree: 0,
inodesUsedPercent: 0,
diskData: [],
netBytesSent: 0,
netBytesRecv: 0,
@@ -185,7 +174,15 @@ const acceptParams = (current: Dashboard.CurrentInfo, base: Dashboard.BaseInfo):
freshChart('cpu', 'CPU', formatNumber(currentInfo.value.cpuUsedPercent));
freshChart('memory', i18n.global.t('monitor.memory'), formatNumber(currentInfo.value.MemoryUsedPercent));
freshChart('load', i18n.global.t('home.load'), formatNumber(currentInfo.value.loadUsagePercent));
freshChart('disk', i18n.global.t('monitor.disk'), formatNumber(currentInfo.value.usedPercent));
nextTick(() => {
for (let i = 0; i < currentInfo.value.diskData.length; i++) {
freshChart(
'disk' + i,
currentInfo.value.diskData[i].path,
formatNumber(currentInfo.value.diskData[i].usedPercent),
);
}
});
};
const freshChart = (chartName: string, Title: string, Data: number) => {
+21 -25
View File
@@ -1,27 +1,19 @@
<template>
<div>
<div class="login-backgroud" v-if="statusCode == 1">
<div class="login-wrapper" v-if="screenWidth > 1000">
<div class="login-title">
<span>{{ $t('commons.login.title') }}</span>
<div class="login-wrapper">
<div :class="screenWidth > 1110 ? 'left inline-block' : ''">
<div class="login-title">
<span>{{ $t('commons.login.title') }}</span>
</div>
<img src="@/assets/images/1panel-login.png" alt="" v-if="screenWidth > 1110" />
</div>
<div class="left inline-block">
<img src="@/assets/images/1panel-login.png" alt="" />
</div>
<div class="right inline-block">
<div :class="screenWidth > 1110 ? 'right inline-block' : ''">
<div class="login-container">
<LoginForm ref="loginRef"></LoginForm>
</div>
</div>
</div>
<div class="login-wrapper" v-else>
<div class="login-title">
<span>{{ $t('commons.login.title') }}</span>
</div>
<div class="login-container">
<LoginForm ref="loginRef"></LoginForm>
</div>
</div>
</div>
<div style="margin-left: 50px" v-if="statusCode == -1">
<h1>{{ $t('commons.login.safeEntrance') }}</h1>
@@ -89,10 +81,10 @@ onMounted(() => {
padding-top: 8%;
width: 80%;
margin: 0 auto;
@media only screen and (max-width: 1440px) {
width: 100%;
padding-top: 6%;
}
// @media only screen and (max-width: 1440px) {
// width: 100%;
// padding-top: 6%;
// }
.left {
vertical-align: middle;
text-align: right;
@@ -100,6 +92,9 @@ onMounted(() => {
img {
object-fit: contain;
width: 100%;
@media only screen and (min-width: 1440px) {
width: 85%;
}
}
}
.right {
@@ -109,17 +104,18 @@ onMounted(() => {
}
.login-title {
margin-left: 10%;
text-align: right;
margin-right: 10%;
span:first-child {
color: $primary-color;
font-size: 40px;
font-family: pingFangSC-Regular;
font-weight: 600;
@media only screen and (max-width: 1440px) {
margin-left: 0;
}
// @media only screen and (max-width: 1440px) {
// margin-left: 0;
// }
}
@media only screen and (max-width: 1000px) {
@media only screen and (max-width: 1110px) {
margin-bottom: 20px;
font-size: 35px;
text-align: center;
@@ -137,7 +133,7 @@ onMounted(() => {
@media only screen and (max-width: 1440px) {
margin-top: 60px;
}
@media only screen and (max-width: 1000px) {
@media only screen and (max-width: 1110px) {
margin: 60px auto 0;
}
}