mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-09 16:00:29 +00:00
Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67ce559b12 | ||
|
|
71869b7264 | ||
|
|
03774bbba6 | ||
|
|
e07b6bc050 | ||
|
|
b1935d8368 | ||
|
|
f8e344db76 | ||
|
|
2c3a8dd6af | ||
|
|
86e5fc36f4 | ||
|
|
858cefc0c9 | ||
|
|
4535c0b90b | ||
|
|
136489e346 | ||
|
|
e2882fd1cc | ||
|
|
dba97076df | ||
|
|
000bcd37c1 | ||
|
|
bf28223745 | ||
|
|
1fed5c4d15 | ||
|
|
aa1f2d0f01 | ||
|
|
80c68b8e49 | ||
|
|
fb556d6149 | ||
|
|
78576a3724 | ||
|
|
e09cefd0f7 | ||
|
|
ed3262a07c | ||
|
|
b904650682 | ||
|
|
abb3073251 | ||
|
|
d8d46aa27c | ||
|
|
0737a75bd3 | ||
|
|
7667399c5e | ||
|
|
0a003d3750 |
@@ -44,6 +44,7 @@ curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_
|
||||
- [在线文档](https://1panel.cn/docs/)
|
||||
- [教学视频](https://space.bilibili.com/510493147/channel/collectiondetail?sid=1199760)
|
||||
- [社区论坛](https://bbs.fit2cloud.com/c/1p/7)
|
||||
- [如何加入微信交流群?](https://bbs.fit2cloud.com/t/topic/2147)
|
||||
|
||||
## 安全说明
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ type NginxScopeReq struct {
|
||||
type NginxConfigUpdate struct {
|
||||
Scope dto.NginxKey `json:"scope"`
|
||||
Operate string `json:"operate" validate:"required,oneof=add update delete"`
|
||||
WebsiteID uint `json:"websiteId" validate:"required"`
|
||||
WebsiteID uint `json:"websiteId"`
|
||||
Params interface{} `json:"params"`
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ type NginxProxyUpdate struct {
|
||||
type NginxAuthUpdate struct {
|
||||
WebsiteID uint `json:"websiteID" validate:"required"`
|
||||
Operate string `json:"operate" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
@@ -31,7 +33,7 @@ func NewIDashboardService() IDashboardService {
|
||||
}
|
||||
|
||||
func (u *DashboardService) Restart(operation string) error {
|
||||
if operation != "panel" && operation != "system" {
|
||||
if operation != "1panel" && operation != "system" {
|
||||
return fmt.Errorf("handle restart operation %s failed, err: nonsupport such operation", operation)
|
||||
}
|
||||
itemCmd := fmt.Sprintf("%s 1pctl restart", cmd.SudoHandleCmd())
|
||||
@@ -173,7 +175,7 @@ type diskInfo struct {
|
||||
|
||||
func loadDiskInfo() []dto.DiskInfo {
|
||||
var datas []dto.DiskInfo
|
||||
stdout, err := cmd.Exec("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")
|
||||
stdout, err := cmd.ExecWithTimeOut("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev", 2*time.Second)
|
||||
if err != nil {
|
||||
return datas
|
||||
}
|
||||
@@ -207,24 +209,52 @@ func loadDiskInfo() []dto.DiskInfo {
|
||||
mounts = append(mounts, diskInfo{Type: fields[1], Device: fields[0], Mount: fields[6]})
|
||||
}
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
)
|
||||
wg.Add(len(mounts))
|
||||
for i := 0; i < len(mounts); i++ {
|
||||
state, err := disk.Usage(mounts[i].Mount)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var itemData dto.DiskInfo
|
||||
itemData.Path = mounts[i].Mount
|
||||
itemData.Type = mounts[i].Type
|
||||
itemData.Device = mounts[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)
|
||||
go func(timeoutCh <-chan time.Time, mount diskInfo) {
|
||||
defer wg.Done()
|
||||
|
||||
var itemData dto.DiskInfo
|
||||
itemData.Path = mount.Mount
|
||||
itemData.Type = mount.Type
|
||||
itemData.Device = mount.Device
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: timeout", mount.Mount)
|
||||
default:
|
||||
state, err := disk.Usage(mount.Mount)
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err)
|
||||
return
|
||||
}
|
||||
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
|
||||
mu.Lock()
|
||||
datas = append(datas, itemData)
|
||||
mu.Unlock()
|
||||
}
|
||||
}(time.After(5*time.Second), mounts[i])
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
sort.Slice(datas, func(i, j int) bool {
|
||||
return datas[i].Path < datas[j].Path
|
||||
})
|
||||
return datas
|
||||
}
|
||||
|
||||
@@ -217,12 +217,12 @@ func (u *ImageRepoService) handleRegistries(newHost, delHost, handle string) err
|
||||
case "create":
|
||||
registries = common.RemoveRepeatElement(append(registries, newHost))
|
||||
case "update":
|
||||
registries = common.RemoveRepeatElement(append(registries, newHost))
|
||||
for i, regi := range registries {
|
||||
if regi == delHost {
|
||||
registries = append(registries[:i], registries[i+1:]...)
|
||||
}
|
||||
}
|
||||
registries = common.RemoveRepeatElement(append(registries, newHost))
|
||||
case "delete":
|
||||
for i, regi := range registries {
|
||||
if regi == delHost {
|
||||
|
||||
@@ -196,8 +196,11 @@ func opNginx(containerName, operate string) error {
|
||||
if operate == constant.NginxCheck {
|
||||
nginxCmd = fmt.Sprintf("docker exec -i %s %s", containerName, "nginx -t")
|
||||
}
|
||||
if out, err := cmd.ExecWithTimeOut(nginxCmd, 2*time.Second); err != nil {
|
||||
return errors.New(out)
|
||||
if out, err := cmd.ExecWithTimeOut(nginxCmd, 20*time.Second); err != nil {
|
||||
if out != "" {
|
||||
return errors.New(out)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2093,15 +2093,8 @@ func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error
|
||||
})
|
||||
}
|
||||
case "404":
|
||||
if req.KeepPath && !req.RedirectRoot {
|
||||
target = req.Target + "$request_uri"
|
||||
}
|
||||
if req.RedirectRoot {
|
||||
target = "/"
|
||||
} else {
|
||||
if req.KeepPath {
|
||||
target = req.Target + "$request_uri"
|
||||
}
|
||||
}
|
||||
block = &components.Block{
|
||||
Directives: []components.IDirective{
|
||||
@@ -2116,7 +2109,7 @@ func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error
|
||||
Directives: []components.IDirective{
|
||||
&components.Directive{
|
||||
Name: "return",
|
||||
Parameters: []string{"301", target},
|
||||
Parameters: []string{req.Redirect, target},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -220,7 +220,7 @@ func (r *Local) Backup(info BackupInfo) error {
|
||||
}
|
||||
outfile, _ := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, 0755)
|
||||
global.LOG.Infof("start to mysqldump | gzip > %s.gzip", info.TargetDir+"/"+info.FileName)
|
||||
cmd := exec.Command("docker", "exec", r.ContainerName, "mysqldump", "-uroot", "-p"+r.Password, info.Name)
|
||||
cmd := exec.Command("docker", "exec", r.ContainerName, "mysqldump", "-uroot", "-p"+r.Password, "--default-character-set="+info.Format, info.Name)
|
||||
gzipCmd := exec.Command("gzip", "-cf")
|
||||
gzipCmd.Stdin, _ = cmd.StdoutPipe()
|
||||
gzipCmd.Stdout = outfile
|
||||
|
||||
Generated
+12
-6
@@ -14,7 +14,7 @@
|
||||
"@codemirror/theme-one-dark": "^6.1.0",
|
||||
"@element-plus/icons-vue": "^1.1.4",
|
||||
"@vueuse/core": "^8.0.1",
|
||||
"axios": "^0.27.2",
|
||||
"axios": "^1.6.0",
|
||||
"echarts": "^5.3.0",
|
||||
"echarts-liquidfill": "^3.1.0",
|
||||
"element-plus": "^2.3.4",
|
||||
@@ -2736,12 +2736,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
|
||||
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-walk": {
|
||||
@@ -8109,6 +8110,11 @@
|
||||
"asap": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/pug": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pug/-/pug-3.0.2.tgz",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@codemirror/theme-one-dark": "^6.1.0",
|
||||
"@element-plus/icons-vue": "^1.1.4",
|
||||
"@vueuse/core": "^8.0.1",
|
||||
"axios": "^0.27.2",
|
||||
"axios": "^1.6.0",
|
||||
"echarts": "^5.3.0",
|
||||
"echarts-liquidfill": "^3.1.0",
|
||||
"element-plus": "^2.3.4",
|
||||
|
||||
@@ -119,6 +119,7 @@ export namespace Container {
|
||||
name: string;
|
||||
tags: Array<string>;
|
||||
size: string;
|
||||
isUsed: boolean;
|
||||
}
|
||||
export interface ImageBuild {
|
||||
from: string;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<span style="font-size: 12px">{{ operationInfo }}</span>
|
||||
<div style="margin-top: 10px">
|
||||
<span v-if="operationInfo" style="font-size: 12px">{{ operationInfo }}</span>
|
||||
<div :style="{ 'margin-top': operationInfo ? '10px' : '0px' }">
|
||||
<span style="font-size: 12px">{{ $t('commons.msg.operateConfirm') }}</span>
|
||||
<span style="font-size: 12px; color: red; font-weight: 500">'{{ submitInputInfo }}'</span>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import RouterButton from './router-button/index.vue';
|
||||
import ComplexTable from './complex-table/index.vue';
|
||||
import ErrPrompt from './error-prompt/index.vue';
|
||||
import OpDialog from './del-dialog/index.vue';
|
||||
import Tooltip from '@/components/tooltip/index.vue';
|
||||
export default {
|
||||
install(app: App) {
|
||||
app.component(LayoutContent.name, LayoutContent);
|
||||
@@ -11,5 +12,6 @@ export default {
|
||||
app.component(ComplexTable.name, ComplexTable);
|
||||
app.component(ErrPrompt.name, ErrPrompt);
|
||||
app.component(OpDialog.name, OpDialog);
|
||||
app.component(Tooltip.name, Tooltip);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -69,6 +69,11 @@ onMounted(() => {
|
||||
<style lang="scss">
|
||||
.router_card {
|
||||
--el-card-padding: 0;
|
||||
.el-card__body {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.router_card_button {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
defineOptions({ name: 'Tooltip' });
|
||||
|
||||
const showTooltip = ref();
|
||||
const tooltipBox = ref();
|
||||
const tooltipItem = ref();
|
||||
|
||||
@@ -286,10 +286,7 @@ const message = {
|
||||
home: {
|
||||
restart_1panel: 'Restart Panel',
|
||||
restart_system: 'Restart Server',
|
||||
panel: '1Panel',
|
||||
system: '1Panel Server',
|
||||
restartHelper: 'About to restart {0}, do you want to continue?',
|
||||
operationSuccess: 'Operation successful! Restarting, please wait...',
|
||||
operationSuccess: 'Operation succeeded, rebooting, please refresh the browser manually later!',
|
||||
overview: 'Overview',
|
||||
entranceHelper:
|
||||
'Enabling a secure entrance can help improve system security. If necessary, go to the Control Panel settings, select Security, and enable the secure entrance.',
|
||||
@@ -522,8 +519,10 @@ const message = {
|
||||
containerPruneHelper3: 'This operation cannot be rolled back. Do you want to continue?',
|
||||
imagePrune: 'Image prune',
|
||||
imagePruneSome: 'Clean unlabeled',
|
||||
imagePruneSomeEmpty: 'No image with the "none" tag is to be cleared',
|
||||
imagePruneSomeHelper: 'Clean the images with the tag "none" that are not used by any containers.',
|
||||
imagePruneAll: 'Clean unused',
|
||||
imagePruneAllEmpty: 'No unused images to be cleared',
|
||||
imagePruneAllHelper: 'Clean the images that are not used by any containers.',
|
||||
networkPrune: 'Network prune',
|
||||
networkPruneHelper: 'Remove all unused networks. Do you want to continue?',
|
||||
|
||||
@@ -284,10 +284,7 @@ const message = {
|
||||
home: {
|
||||
restart_1panel: '重啟面板',
|
||||
restart_system: '重啟服務器',
|
||||
panel: '1Panel 面板',
|
||||
system: '1Panel 服務器',
|
||||
restartHelper: '即將重新啟動 {0},是否繼續?',
|
||||
operationSuccess: '操作成功!正在重啟,請稍候...',
|
||||
operationSuccess: '操作成功,正在重啟,請稍後手動刷新瀏覽器!',
|
||||
overview: '概覽',
|
||||
entranceHelper: '設置安全入口有利於提高系統的安全性,如有需要,前往 面板設置-安全 中,啟用安全入口',
|
||||
appInstalled: '已安裝應用',
|
||||
@@ -510,9 +507,11 @@ const message = {
|
||||
containerPruneHelper3: '該操作無法回滾,是否繼續?',
|
||||
imagePrune: '清理鏡像',
|
||||
imagePruneSome: '未標簽鏡像',
|
||||
imagePruneSomeHelper: '清理下列標簽為 none 且未被任何容器使用的鏡像。',
|
||||
imagePruneSomeEmpty: '暫無待清理的未使用 none 標簽鏡像',
|
||||
imagePruneSomeHelper: '清理下列標簽為 none 且未被任何容器使用的鏡像',
|
||||
imagePruneAll: '未使用鏡像',
|
||||
imagePruneAllHelper: '清理下列未被任何容器使用的鏡像。',
|
||||
imagePruneAllEmpty: '暫無待清理的未使用鏡像',
|
||||
imagePruneAllHelper: '清理下列未被任何容器使用的鏡像',
|
||||
networkPrune: '清理網絡',
|
||||
networkPruneHelper: '清理網絡 將刪除所有未被使用的網絡,該操作無法回滾,是否繼續?',
|
||||
volumePrune: '清理存儲卷',
|
||||
|
||||
@@ -284,10 +284,7 @@ const message = {
|
||||
home: {
|
||||
restart_1panel: '重启面板',
|
||||
restart_system: '重启服务器',
|
||||
panel: '1Panel 面板',
|
||||
system: '1Panel 服务器',
|
||||
restartHelper: '即将重新启动 {0},是否继续?',
|
||||
operationSuccess: '操作成功!正在重启,请稍候...',
|
||||
operationSuccess: '操作成功,正在重启,请稍后手动刷新浏览器!',
|
||||
overview: '概览',
|
||||
entranceHelper: '设置安全入口有利于提高系统的安全性,如有需要,前往 面板设置-安全 中,启用安全入口',
|
||||
appInstalled: '已安装应用',
|
||||
@@ -511,9 +508,11 @@ const message = {
|
||||
containerPruneHelper3: '该操作无法回滚,是否继续?',
|
||||
imagePrune: '清理镜像',
|
||||
imagePruneSome: '未标签镜像',
|
||||
imagePruneSomeHelper: '清理下列标签为 none 且未被任何容器使用的镜像。',
|
||||
imagePruneSomeEmpty: '暂无待清理的未使用 none 标签镜像',
|
||||
imagePruneSomeHelper: '清理下列标签为 none 且未被任何容器使用的镜像',
|
||||
imagePruneAll: '未使用镜像',
|
||||
imagePruneAllHelper: '清理下列未被任何容器使用的镜像。',
|
||||
imagePruneAllEmpty: '暂无待清理的未使用镜像',
|
||||
imagePruneAllHelper: '清理下列未被任何容器使用的镜像',
|
||||
networkPrune: '清理网络',
|
||||
networkPruneHelper: '清理网络 将删除所有未被使用的网络,该操作无法回滚,是否继续?',
|
||||
volumePrune: '清理存储卷',
|
||||
|
||||
@@ -379,9 +379,7 @@ html {
|
||||
}
|
||||
|
||||
.router-button {
|
||||
margin-top: 12px;
|
||||
float: right;
|
||||
margin-right: 50px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.text-parent {
|
||||
|
||||
@@ -249,6 +249,7 @@ const onOperate = async (op: string) => {
|
||||
msg: msg,
|
||||
api: containerOperator,
|
||||
params: { names: names, operation: op },
|
||||
successMsg: `${i18n.global.t('container.' + op)}${i18n.global.t('commons.status.success')}`,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -567,12 +567,14 @@ const onOperate = async (op: string, row: Container.ContainerInfo | null) => {
|
||||
msg = i18n.global.t('container.operatorAppHelper', [i18n.global.t('container.' + op)]);
|
||||
}
|
||||
}
|
||||
const successMsg = `${i18n.global.t('container.' + op)}${i18n.global.t('commons.status.success')}`;
|
||||
opRef.value.acceptParams({
|
||||
title: i18n.global.t('container.' + op),
|
||||
names: names,
|
||||
msg: msg,
|
||||
api: containerOperator,
|
||||
params: { names: names, operation: op },
|
||||
successMsg,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -12,19 +12,25 @@
|
||||
<el-radio :label="true">{{ $t('container.imagePruneAll') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<span>
|
||||
{{ withTagAll ? $t('container.imagePruneAllHelper') : $t('container.imagePruneSomeHelper') }}
|
||||
<span v-if="withTagAll">
|
||||
{{ unUsedList.length !== 0 ? $t('container.imagePruneAllHelper') : $t('container.imagePruneAllEmpty') }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{
|
||||
unTagList.length !== 0 ? $t('container.imagePruneSomeHelper') : $t('container.imagePruneSomeEmpty')
|
||||
}}
|
||||
</span>
|
||||
<div v-if="!withTagAll">
|
||||
<ul v-for="(item, index) in imageList" :key="index">
|
||||
<li v-if="item.tags && item.tags.length === 1 && item.tags[0].indexOf('<none>') !== -1">
|
||||
<ul v-for="(item, index) in unTagList" :key="index">
|
||||
<li>
|
||||
{{ item.tags[0] }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else>
|
||||
<ul v-for="(item, index) in imageList" :key="index">
|
||||
<li v-if="item.tags && !item.isUsed">{{ item.tags.join(', ') }}</li>
|
||||
<ul v-for="(item, index) in unUsedList" :key="index">
|
||||
<li v-if="item.tags">{{ item.tags.join(', ') }}</li>
|
||||
<li v-else>{{ item.id.replaceAll('sha256:', '').substring(0, 12) }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -33,7 +39,7 @@
|
||||
<el-button @click="dialogVisible = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="onClean" :disabled="loading">
|
||||
<el-button type="primary" :disabled="buttonDisable() || loading" @click="onClean">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
@@ -52,19 +58,34 @@ import { ref } from 'vue';
|
||||
const dialogVisible = ref(false);
|
||||
const withTagAll = ref(false);
|
||||
const loading = ref();
|
||||
const imageList = ref();
|
||||
const unTagList = ref();
|
||||
const unUsedList = ref();
|
||||
|
||||
interface DialogProps {
|
||||
list: Array<Container.ImageInfo>;
|
||||
}
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
imageList.value = params.list;
|
||||
let list = params.list || [];
|
||||
unTagList.value = [];
|
||||
unUsedList.value = [];
|
||||
for (const item of list) {
|
||||
if (item.tags && item.tags.length === 1 && item.tags[0].indexOf('<none>') !== -1 && !item.isUsed) {
|
||||
unTagList.value.push(item);
|
||||
}
|
||||
if (!item.isUsed) {
|
||||
unUsedList.value.push(item);
|
||||
}
|
||||
}
|
||||
dialogVisible.value = true;
|
||||
withTagAll.value = false;
|
||||
};
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const buttonDisable = () => {
|
||||
return withTagAll.value ? unUsedList.value.length === 0 : unTagList.value.length === 0;
|
||||
};
|
||||
|
||||
const onClean = async () => {
|
||||
loading.value = true;
|
||||
let params = {
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
>
|
||||
<template #route-button>
|
||||
<div class="router-button">
|
||||
<el-button link type="primary" @click="restart('panel')">
|
||||
<el-button link type="primary" @click="onRestart('1panel')">
|
||||
{{ $t('home.restart_1panel') }}
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button link type="primary" @click="restart('system')">
|
||||
<el-button link type="primary" @click="onRestart('system')">
|
||||
{{ $t('home.restart_system') }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -230,10 +230,13 @@
|
||||
</CardWithHeader>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<ConfirmDialog ref="confirmDialogRef" @confirm="onSave"></ConfirmDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ConfirmDialog from '@/components/confirm-dialog/index.vue';
|
||||
import { onMounted, onBeforeUnmount, ref, reactive } from 'vue';
|
||||
import Status from '@/views/home/status/index.vue';
|
||||
import App from '@/views/home/app/index.vue';
|
||||
@@ -252,6 +255,8 @@ const router = useRouter();
|
||||
const globalStore = GlobalStore();
|
||||
|
||||
const statusRef = ref();
|
||||
const restartType = ref();
|
||||
const confirmDialogRef = ref();
|
||||
const appRef = ref();
|
||||
|
||||
const isSafety = ref();
|
||||
@@ -540,20 +545,19 @@ const loadSafeStatus = async () => {
|
||||
isSafety.value = res.data.securityEntrance;
|
||||
};
|
||||
|
||||
const restart = async (type: string) => {
|
||||
ElMessageBox.confirm(
|
||||
i18n.global.t('home.restartHelper', [i18n.global.t('home.' + type)]),
|
||||
i18n.global.t('commons.msg.operate'),
|
||||
{
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
type: 'info',
|
||||
},
|
||||
).then(async () => {
|
||||
globalStore.isOnRestart = true;
|
||||
MsgSuccess(i18n.global.t('home.operationSuccess'));
|
||||
await systemRestart(type);
|
||||
});
|
||||
const onRestart = (type: string) => {
|
||||
restartType.value = type;
|
||||
let params = {
|
||||
header: i18n.global.t('home.restart_' + type),
|
||||
operationInfo: '',
|
||||
submitInputInfo: i18n.global.t('database.restartNow'),
|
||||
};
|
||||
confirmDialogRef.value!.acceptParams(params);
|
||||
};
|
||||
const onSave = async () => {
|
||||
globalStore.isOnRestart = true;
|
||||
MsgSuccess(i18n.global.t('home.operationSuccess'));
|
||||
await systemRestart(restartType.value);
|
||||
};
|
||||
|
||||
const onFocus = () => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<el-row>
|
||||
<el-col :span="22" :offset="1" v-loading="loading">
|
||||
<FileRole :mode="mode" @get-mode="getMode"></FileRole>
|
||||
<FileRole :mode="mode" @get-mode="getMode" :key="open.toString()"></FileRole>
|
||||
<el-form ref="fileForm" label-position="left" :model="addForm" label-width="100px" :rules="rules">
|
||||
<el-form-item :label="$t('commons.table.user')" prop="user">
|
||||
<el-input v-model.trim="addForm.user" />
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<el-row>
|
||||
<el-col :span="22" :offset="1">
|
||||
<FileRole v-loading="loading" :mode="mode" @get-mode="getMode"></FileRole>
|
||||
<FileRole v-loading="loading" :mode="mode" @get-mode="getMode" :key="open.toString()"></FileRole>
|
||||
<el-form-item v-if="form.isDir">
|
||||
<el-checkbox v-model="form.sub">{{ $t('file.containSub') }}</el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<el-checkbox v-if="addForm.isDir" v-model="setRole" :label="$t('file.setRole')"></el-checkbox>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<FileRole v-if="setRole" :mode="'0755'" @get-mode="getMode"></FileRole>
|
||||
<FileRole v-if="setRole" :mode="'0755'" @get-mode="getMode" :key="open.toString()"></FileRole>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
<template>
|
||||
<el-dialog v-model="open" :title="$t('app.delete')" width="30%" :close-on-click-modal="false">
|
||||
<el-row>
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-alert class="mt-2" :show-icon="true" type="warning" :closable="false">
|
||||
<div style="line-height: 20px; word-wrap: break-word">
|
||||
<span>{{ $t('file.deleteHelper') }}</span>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-alert class="mt-2" :show-icon="true" type="warning" :closable="false">
|
||||
<div class="delete-warn">
|
||||
<span>{{ $t('file.deleteHelper') }}</span>
|
||||
</div>
|
||||
</el-alert>
|
||||
<div class="mt-4">
|
||||
<el-checkbox v-model="forceDelete">{{ $t('file.forceDeleteHelper') }}</el-checkbox>
|
||||
</div>
|
||||
</el-alert>
|
||||
<div class="mt-4">
|
||||
<el-checkbox v-model="forceDelete">{{ $t('file.forceDeleteHelper') }}</el-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="flx-align-center mb-1" v-for="(row, index) in files" :key="index">
|
||||
<div>
|
||||
<svg-icon v-if="row.isDir" className="table-icon mr-1 " iconName="p-file-folder"></svg-icon>
|
||||
<svg-icon v-else className="table-icon mr-1" :iconName="getIconName(row.extension)"></svg-icon>
|
||||
<div class="file-list">
|
||||
<div class="flx-align-center mb-1" v-for="(row, index) in files" :key="index">
|
||||
<div>
|
||||
<svg-icon
|
||||
v-if="row.isDir"
|
||||
className="table-icon mr-1 "
|
||||
iconName="p-file-folder"
|
||||
></svg-icon>
|
||||
<svg-icon
|
||||
v-else
|
||||
className="table-icon mr-1"
|
||||
:iconName="getIconName(row.extension)"
|
||||
></svg-icon>
|
||||
</div>
|
||||
<span class="sle">{{ row.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="sle">{{ row.name }}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -85,4 +96,14 @@ defineExpose({
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.delete-warn {
|
||||
line-height: 20px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -127,15 +127,18 @@ const removeFile = (index: number) => {
|
||||
};
|
||||
|
||||
const fileOnChange = (_uploadFile: UploadFile, uploadFiles: UploadFiles) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(_uploadFile.raw);
|
||||
reader.onload = async () => {
|
||||
if (_uploadFile.size == 64 || _uploadFile.size == 0) {
|
||||
uploaderFiles.value = uploadFiles;
|
||||
};
|
||||
reader.onerror = () => {
|
||||
uploaderFiles.value = uploaderFiles.value.filter((file) => file.uid !== _uploadFile.uid);
|
||||
MsgError(i18n.global.t('file.typeErrOrEmpty', [_uploadFile.name]));
|
||||
};
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(_uploadFile.raw);
|
||||
reader.onload = async () => {};
|
||||
reader.onerror = () => {
|
||||
uploaderFiles.value = uploaderFiles.value.filter((file) => file.uid !== _uploadFile.uid);
|
||||
MsgError(i18n.global.t('file.typeErrOrEmpty', [_uploadFile.name]));
|
||||
};
|
||||
} else {
|
||||
uploaderFiles.value = uploadFiles;
|
||||
}
|
||||
};
|
||||
|
||||
const clearFiles = () => {
|
||||
@@ -163,7 +166,7 @@ const submit = async () => {
|
||||
const fileSize = file.size;
|
||||
|
||||
uploadHelper.value = i18n.global.t('file.fileUploadStart', [file.name]);
|
||||
if (fileSize <= 1024 * 1024 * 50) {
|
||||
if (fileSize <= 1024 * 1024 * 10) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file.raw);
|
||||
if (file.raw.webkitRelativePath != '') {
|
||||
@@ -175,7 +178,7 @@ const submit = async () => {
|
||||
success++;
|
||||
uploaderFiles.value[i].status = 'success';
|
||||
} else {
|
||||
const CHUNK_SIZE = 1024 * 1024 * 20; // 10MB
|
||||
const CHUNK_SIZE = 1024 * 1024 * 10;
|
||||
const chunkCount = Math.ceil(fileSize / CHUNK_SIZE);
|
||||
let uploadedChunkCount = 0;
|
||||
for (let c = 0; c < chunkCount; c++) {
|
||||
|
||||
@@ -189,6 +189,7 @@ const loginRules = reactive({
|
||||
password: computed(() => [{ required: true, message: i18n.global.t('commons.rule.password'), trigger: 'blur' }]),
|
||||
});
|
||||
|
||||
let isLoggingIn = false;
|
||||
const mfaButtonFocused = ref();
|
||||
const mfaLoginForm = reactive({
|
||||
name: '',
|
||||
@@ -223,7 +224,7 @@ function handleCommand(command: string) {
|
||||
}
|
||||
|
||||
const login = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
if (!formEl || isLoggingIn) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
let requestLoginForm = {
|
||||
@@ -244,6 +245,7 @@ const login = (formEl: FormInstance | undefined) => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isLoggingIn = true;
|
||||
loading.value = true;
|
||||
const res = await loginApi(requestLoginForm);
|
||||
if (res.code === 406) {
|
||||
@@ -274,18 +276,22 @@ const login = (formEl: FormInstance | undefined) => {
|
||||
} catch (error) {
|
||||
loginVerify();
|
||||
} finally {
|
||||
isLoggingIn = false;
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const mfaLogin = async (auto: boolean) => {
|
||||
if (isLoggingIn) return;
|
||||
if ((!auto && mfaLoginForm.code) || (auto && mfaLoginForm.code.length === 6)) {
|
||||
isLoggingIn = true;
|
||||
mfaLoginForm.name = loginForm.name;
|
||||
mfaLoginForm.password = loginForm.password;
|
||||
const res = await mfaLoginApi(mfaLoginForm);
|
||||
if (res.code === 406) {
|
||||
errMfaInfo.value = true;
|
||||
isLoggingIn = false;
|
||||
return;
|
||||
}
|
||||
globalStore.setLogStatus(true);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="login-backgroud" v-if="isSafety && !isErr && !isNotFound">
|
||||
<div class="login-background" v-if="isSafety && !isErr && !isNotFound">
|
||||
<div class="login-wrapper">
|
||||
<div :class="screenWidth > 1110 ? 'left inline-block' : ''">
|
||||
<div class="login-title">
|
||||
@@ -100,7 +100,7 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.login-backgroud {
|
||||
.login-background {
|
||||
height: 100vh;
|
||||
background: url(@/assets/images/1panel-login-bg.png) no-repeat,
|
||||
radial-gradient(153.25% 257.2% at 118.99% 181.67%, rgba(50, 132, 255, 0.2) 0%, rgba(82, 120, 255, 0) 100%)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="login-backgroud">
|
||||
<div class="login-background">
|
||||
<div class="login-wrapper">
|
||||
<div :class="screenWidth > 1110 ? 'left inline-block' : ''">
|
||||
<div class="login-title">
|
||||
@@ -54,7 +54,7 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.login-backgroud {
|
||||
.login-background {
|
||||
height: 100vh;
|
||||
background: url(@/assets/images/1panel-login-bg.png) no-repeat,
|
||||
radial-gradient(153.25% 257.2% at 118.99% 181.67%, rgba(50, 132, 255, 0.2) 0%, rgba(82, 120, 255, 0) 100%)
|
||||
|
||||
@@ -204,14 +204,14 @@ require (
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.44.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.44.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 // indirect
|
||||
go.opentelemetry.io/otel v1.18.0 // indirect
|
||||
go.opentelemetry.io/otel v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.18.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.18.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.18.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
|
||||
@@ -810,22 +810,22 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.44.0 h1:b8xjZxHbLrXAum4SxJd1Rlm7Y/fKaB+6ACI7/e5EfSA=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.44.0/go.mod h1:1ei0a32xOGkFoySu7y1DAHfcuIhC0pNZpvY2huXuMy4=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 h1:RsQi0qJ2imFfCvZabqzM9cNXBG8k6gXMv1A0cXRmH6A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.44.0 h1:ewRgsETI7b5nPCK3FqKdY9mFR/9ZwtexwC26//Srjn0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.44.0/go.mod h1:+BrAX3hlRmkYIKl2e/eSRaKLkClDTY19gzegkQ+KeEQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 h1:KfYpVmrjI7JuToy5k8XV3nkapjWx48k4E4JOtVstzQI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0/go.mod h1:SeQhzAEccGVZVEy7aH87Nh0km+utSpo1pTv6eMMop48=
|
||||
go.opentelemetry.io/otel v1.18.0 h1:TgVozPGZ01nHyDZxK5WGPFB9QexeTMXEH7+tIClWfzs=
|
||||
go.opentelemetry.io/otel v1.18.0/go.mod h1:9lWqYO0Db579XzVuCKFNPDl4s73Voa+zEck3wHaAYQI=
|
||||
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
|
||||
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA=
|
||||
go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ=
|
||||
go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k=
|
||||
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
|
||||
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
|
||||
go.opentelemetry.io/otel/sdk v1.18.0 h1:e3bAB0wB3MljH38sHzpV/qWrOTCFrdZF2ct9F8rBkcY=
|
||||
go.opentelemetry.io/otel/sdk v1.18.0/go.mod h1:1RCygWV7plY2KmdskZEDDBs4tJeHG92MdHZIluiYs/M=
|
||||
go.opentelemetry.io/otel/trace v1.18.0 h1:NY+czwbHbmndxojTEKiSMHkG2ClNH2PwmcHrdo0JY10=
|
||||
go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5nS8Mjvzckz+0=
|
||||
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
|
||||
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
sonar.projectKey=1Panel-dev_1Panel
|
||||
sonar.organization=1panel-dev
|
||||
|
||||
sonar.exclusions= frontend/src/lang/modules/*
|
||||
sonar.exclusions=frontend/src/lang/modules/*,backend/i18n/lang/*
|
||||
|
||||
# This is the name and version displayed in the SonarCloud UI.
|
||||
#sonar.projectName=1Panel
|
||||
@@ -12,4 +12,4 @@ sonar.exclusions= frontend/src/lang/modules/*
|
||||
#sonar.sources=.
|
||||
|
||||
# Encoding of the source code. Default is default system encoding
|
||||
#sonar.sourceEncoding=UTF-8
|
||||
#sonar.sourceEncoding=UTF-8
|
||||
|
||||
Reference in New Issue
Block a user