Compare commits

..
8 Commits
28 changed files with 141 additions and 75 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// @Description 获取设备基础信息
// @Success 200 {object} dto.DeviceBaseInfo
// @Security ApiKeyAuth
// @Router /toolbox/device/base [get]
// @Router /toolbox/device/base [post]
func (b *BaseApi) LoadDeviceBaseInfo(c *gin.Context) {
data, err := deviceService.LoadBaseInfo()
if err != nil {
+4 -3
View File
@@ -77,9 +77,10 @@ type FilePathCheck struct {
}
type FileWget struct {
Url string `json:"url" validate:"required"`
Path string `json:"path" validate:"required"`
Name string `json:"name" validate:"required"`
Url string `json:"url" validate:"required"`
Path string `json:"path" validate:"required"`
Name string `json:"name" validate:"required"`
IgnoreCertificate bool `json:"ignoreCertificate"`
}
type FileMove struct {
+21 -1
View File
@@ -2,6 +2,7 @@ package service
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
@@ -26,6 +27,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/api/types/registry"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/gorilla/websocket"
@@ -805,7 +807,25 @@ func checkImageExist(client *client.Client, image string) bool {
}
func pullImages(ctx context.Context, client *client.Client, image string) error {
out, err := client.ImagePull(ctx, image, types.ImagePullOptions{})
options := types.ImagePullOptions{}
repos, _ := imageRepoRepo.List()
if len(repos) != 0 {
for _, repo := range repos {
if strings.HasPrefix(image, repo.DownloadUrl) && repo.Auth {
authConfig := registry.AuthConfig{
Username: repo.Username,
Password: repo.Password,
}
encodedJSON, err := json.Marshal(authConfig)
if err != nil {
return err
}
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
options.RegistryAuth = authStr
}
}
}
out, err := client.ImagePull(ctx, image, options)
if err != nil {
return err
}
+9 -4
View File
@@ -55,10 +55,15 @@ func (u *DeviceService) LoadBaseInfo() (dto.DeviceBaseInfo, error) {
baseInfo.Hosts = loadHosts()
baseInfo.Hostname = loadHostname()
baseInfo.User = loadUser()
ntp, _ := settingRepo.Get(settingRepo.WithByKey("NtpSite"))
baseInfo.Ntp = ntp.Value
ntp, err := settingRepo.Get(settingRepo.WithByKey("NtpSite"))
if err == nil {
baseInfo.Ntp = ntp.Value
}
swapInfo, _ := mem.SwapMemory()
swapInfo, err := mem.SwapMemory()
if err != nil {
return baseInfo, err
}
baseInfo.SwapMemoryTotal = swapInfo.Total
baseInfo.SwapMemoryAvailable = swapInfo.Free
baseInfo.SwapMemoryUsed = swapInfo.Used
@@ -375,7 +380,7 @@ func loadUser() string {
func loadSwap() []dto.SwapHelper {
var data []dto.SwapHelper
std, err := cmd.Execf("%s swapon --show --summary", cmd.SudoHandleCmd())
std, err := cmd.Execf("%s swapon --summary", cmd.SudoHandleCmd())
if err != nil {
return data
}
+7 -3
View File
@@ -39,12 +39,16 @@ func (u *Fail2BanService) LoadBaseInfo() (dto.Fail2BanBaseInfo, error) {
baseInfo.IsEnable, baseInfo.IsActive, baseInfo.IsExist = client.Status()
if !baseInfo.IsActive {
baseInfo.Version = "-"
return baseInfo, nil
} else {
baseInfo.Version = client.Version()
}
baseInfo.Version = client.Version()
conf, err := os.ReadFile(defaultFail2BanPath)
if err != nil {
return baseInfo, fmt.Errorf("read fail2ban conf of %s failed, err: %v", defaultFail2BanPath, err)
if baseInfo.IsActive {
return baseInfo, fmt.Errorf("read fail2ban conf of %s failed, err: %v", defaultFail2BanPath, err)
} else {
return baseInfo, nil
}
}
lines := strings.Split(string(conf), "\n")
+1 -1
View File
@@ -243,7 +243,7 @@ func (f *FileService) ChangeName(req request.FileRename) error {
func (f *FileService) Wget(w request.FileWget) (string, error) {
fo := files.NewFileOp()
key := "file-wget-" + common.GetUuid()
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key)
return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key, w.IgnoreCertificate)
}
func (f *FileService) MvFile(m request.FileMove) error {
+1 -1
View File
@@ -320,7 +320,7 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
case constant.StatusSuccess:
commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command)
case constant.StatusFailed:
commandItem = fmt.Sprintf("cat %s | grep -aE 'Failed password for|Connection closed by authenticating user' %s", file.Name, command)
commandItem = fmt.Sprintf("cat %s | grep -aE 'Failed password for|Connection closed by authenticating user' | grep -v 'invalid' %s", file.Name, command)
default:
commandItem = fmt.Sprintf("cat %s | grep -aE \"(Failed password for|Connection closed by authenticating user|Accepted)\" | grep -v 'invalid' %s", file.Name, command)
}
+1 -1
View File
@@ -16,7 +16,7 @@ func (s *ToolboxRouter) InitToolboxRouter(Router *gin.RouterGroup) {
Use(middleware.PasswordExpired())
baseApi := v1.ApiGroupApp.BaseApi
{
toolboxRouter.GET("/device/base", baseApi.LoadDeviceBaseInfo)
toolboxRouter.POST("/device/base", baseApi.LoadDeviceBaseInfo)
toolboxRouter.GET("/device/zone/options", baseApi.LoadTimeOption)
toolboxRouter.POST("/device/update/conf", baseApi.UpdateDeviceConf)
toolboxRouter.POST("/device/update/host", baseApi.UpdateDeviceHost)
+47 -31
View File
@@ -17,44 +17,60 @@ import (
"github.com/mozillazg/go-pinyin"
)
func CompareVersion(version1 string, version2 string) bool {
if version1 == version2 {
return false
}
version1s := strings.Split(version1, ".")
version2s := strings.Split(version2, ".")
func CompareVersion(version1, version2 string) bool {
v1s := extractNumbers(version1)
v2s := extractNumbers(version2)
if len(version2s) > len(version1s) {
for i := 0; i < len(version2s)-len(version1s); i++ {
version1s = append(version1s, "0")
}
}
if len(version1s) > len(version2s) {
for i := 0; i < len(version1s)-len(version2s); i++ {
version2s = append(version2s, "0")
}
}
maxLen := max(len(v1s), len(v2s))
v1s = append(v1s, make([]string, maxLen-len(v1s))...)
v2s = append(v2s, make([]string, maxLen-len(v2s))...)
n := min(len(version1s), len(version2s))
re := regexp.MustCompile("[0-9]+")
for i := 0; i < n; i++ {
sVersion1s := re.FindAllString(version1s[i], -1)
sVersion2s := re.FindAllString(version2s[i], -1)
if len(sVersion1s) == 0 {
return false
for i := 0; i < maxLen; i++ {
v1, err1 := strconv.Atoi(v1s[i])
v2, err2 := strconv.Atoi(v2s[i])
if err1 != nil {
v1 = 0
}
if len(sVersion2s) == 0 {
return false
if err2 != nil {
v2 = 0
}
v1num, _ := strconv.Atoi(sVersion1s[0])
v2num, _ := strconv.Atoi(sVersion2s[0])
if v1num == v2num {
continue
if v1 != v2 {
return v1 > v2
}
}
return false
}
func extractNumbers(version string) []string {
var numbers []string
start := -1
for i, r := range version {
if isDigit(r) {
if start == -1 {
start = i
}
} else {
return v1num > v2num
if start != -1 {
numbers = append(numbers, version[start:i])
start = -1
}
}
}
return true
if start != -1 {
numbers = append(numbers, version[start:])
}
return numbers
}
func isDigit(r rune) bool {
return r >= '0' && r <= '9'
}
func max(x, y int) int {
if x > y {
return x
}
return y
}
func GetSortedVersions(versions []string) []string {
+5 -4
View File
@@ -216,11 +216,12 @@ func (w *WriteCounter) SaveProcess() {
}
}
func (f FileOp) DownloadFileWithProcess(url, dst, key string) error {
client := &http.Client{
Transport: &http.Transport{
func (f FileOp) DownloadFileWithProcess(url, dst, key string, ignoreCertificate bool) error {
client := &http.Client{}
if ignoreCertificate {
client.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
}
request, err := http.NewRequest("GET", url, nil)
if err != nil {
+1 -1
View File
@@ -10102,7 +10102,7 @@ const docTemplate = `{
}
},
"/toolbox/device/base": {
"get": {
"post": {
"security": [
{
"ApiKeyAuth": []
+1 -1
View File
@@ -10095,7 +10095,7 @@
}
},
"/toolbox/device/base": {
"get": {
"post": {
"security": [
{
"ApiKeyAuth": []
+1 -1
View File
@@ -11190,7 +11190,7 @@ paths:
formatZH: 清理系统垃圾文件
paramKeys: []
/toolbox/device/base:
get:
post:
description: 获取设备基础信息
responses:
"200":
+1
View File
@@ -109,6 +109,7 @@ export namespace File {
path: string;
name: string;
url: string;
ignoreCertificate?: boolean;
}
export interface FileWgetRes {
+3 -3
View File
@@ -6,16 +6,16 @@ import { TimeoutEnum } from '@/enums/http-enum';
// device
export const getDeviceBase = () => {
return http.get<Toolbox.DeviceBaseInfo>(`/toolbox/device/base`);
return http.post<Toolbox.DeviceBaseInfo>(`/toolbox/device/base`, {}, TimeoutEnum.T_60S);
};
export const loadTimeZoneOptions = () => {
return http.get<Array<string>>(`/toolbox/device/zone/options`);
};
export const updateDevice = (key: string, value: string) => {
return http.post(`/toolbox/device/update/conf`, { key: key, value: value });
return http.post(`/toolbox/device/update/conf`, { key: key, value: value }, TimeoutEnum.T_60S);
};
export const updateDeviceHost = (param: Array<Toolbox.TimeZoneOptions>) => {
return http.post(`/toolbox/device/update/host`, param);
return http.post(`/toolbox/device/update/host`, param, TimeoutEnum.T_60S);
};
export const updateDevicePasswd = (user: string, passwd: string) => {
return http.post(`/toolbox/device/update/passwd`, { user: user, passwd: Base64.encode(passwd) });
+7 -3
View File
@@ -103,7 +103,7 @@ const rowName = ref('');
const data = ref();
const loading = ref(false);
const paths = ref<string[]>([]);
const req = reactive({ path: '/', expand: true, page: 1, pageSize: 300 });
const req = reactive({ path: '/', expand: true, page: 1, pageSize: 300, showHidden: true });
const selectRow = ref();
const popoverVisible = ref(false);
@@ -196,13 +196,17 @@ const search = async (req: File.ReqFile) => {
};
onMounted(() => {
req.path = props.path;
if (props.path != '') {
req.path = props.path;
}
rowName.value = '';
search(req);
});
onUpdated(() => {
req.path = props.path;
if (props.path != '') {
req.path = props.path;
}
search(req);
});
</script>
+3
View File
@@ -1105,6 +1105,9 @@ const message = {
wordWrap: 'Automatically wrap',
deleteHelper2:
'Are you sure you want to delete the selected file? The deletion operation cannot be rolled back',
ignoreCertificate: 'Ignore Certificate',
ignoreCertificateHelper:
'Ignoring untrusted certificates during downloads may lead to data leakage or tampering. Please use this option with caution, only when trusting the download source.',
},
ssh: {
autoStart: 'Auto Start',
+3
View File
@@ -1051,6 +1051,9 @@ const message = {
fileRecycleBinMsg: '已{0}回收站',
wordWrap: '自動換行',
deleteHelper2: '確定刪除所選檔案? 刪除操作不可回滾',
ignoreCertificate: '忽略不可信證書',
ignoreCertificateHelper:
'下載時忽略不可信證書可能導致數據洩露或篡改。請謹慎使用此選項,僅在信任下載源的情況下啟用',
},
ssh: {
autoStart: '開機自啟',
+3
View File
@@ -1052,6 +1052,9 @@ const message = {
fileRecycleBinMsg: '已{0}回收站',
wordWrap: '自动换行',
deleteHelper2: '确定删除所选文件?删除操作不可回滚',
ignoreCertificate: '忽略不可信证书',
ignoreCertificateHelper:
'下载时忽略不可信证书可能导致数据泄露或篡改。请谨慎使用此选项,仅在信任下载源的情况下启用',
},
ssh: {
autoStart: '开机自启',
+1 -1
View File
@@ -69,7 +69,7 @@
{{ $t('home.free') }}: {{ formatNumber(currentInfo.swapMemoryAvailable / 1024 / 1024) }} MB
</el-tag>
<el-tag class="tagClass">
{{ $t('home.percent') }}: {{ formatNumber(currentInfo.swapMemoryUsedPercent * 100) }}%
{{ $t('home.percent') }}: {{ formatNumber(currentInfo.swapMemoryUsedPercent) }}%
</el-tag>
</div>
<template #reference>
@@ -24,6 +24,12 @@
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input v-model="addForm.name"></el-input>
</el-form-item>
<el-form-item>
<el-checkbox v-model="addForm.ignoreCertificate">
{{ $t('file.ignoreCertificate') }}
</el-checkbox>
<span class="input-help">{{ $t('file.ignoreCertificateHelper') }}</span>
</el-form-item>
</el-form>
</el-col>
</el-row>
@@ -67,6 +73,7 @@ const addForm = reactive({
url: '',
path: '',
name: '',
ignoreCertificate: false,
});
const em = defineEmits(['close']);
@@ -111,6 +118,7 @@ const acceptParams = (props: WgetProps) => {
addForm.path = props.path;
open.value = true;
submitData.value = false;
addForm.ignoreCertificate = false;
};
defineExpose({ acceptParams });
@@ -5,7 +5,7 @@
<DrawerHeader header="DNS" :back="handleClose" />
</template>
<el-row type="flex" justify="center">
<el-row type="flex" justify="center" v-loading="loading">
<el-col :span="22">
<el-alert
:title="$t('toolbox.device.dnsAlert')"
@@ -24,7 +24,6 @@
label-position="top"
@submit.prevent
:model="form"
v-loading="loading"
>
<el-form-item label="DNS" prop="dns">
<el-input
@@ -5,7 +5,7 @@
<DrawerHeader :header="$t('toolbox.device.hostname')" :back="handleClose" />
</template>
<el-row type="flex" justify="center">
<el-row type="flex" justify="center" v-loading="loading">
<el-col :span="22">
<el-alert
:title="$t('toolbox.device.hostnameHelper')"
@@ -13,7 +13,7 @@
:closable="false"
type="warning"
/>
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent v-loading="loading">
<el-form ref="formRef" label-position="top" :model="form" @submit.prevent>
<el-form-item
:label="$t('toolbox.device.hostname')"
prop="hostname"
@@ -76,6 +76,7 @@ const onSaveHostame = async (formEl: FormInstance | undefined) => {
type: 'info',
},
).then(async () => {
loading.value = true;
await updateDevice('Hostname', form.hostname)
.then(async () => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
@@ -5,7 +5,7 @@
<DrawerHeader header="Hosts" :back="handleClose" />
</template>
<el-row type="flex" justify="center">
<el-row type="flex" justify="center" v-loading="loading">
<el-col :span="22">
<el-radio-group v-model="confShowType" @change="changeMode">
<el-radio-button label="base">{{ $t('database.baseConf') }}</el-radio-button>
@@ -1,11 +1,11 @@
<template>
<div v-loading="loading">
<div>
<el-drawer v-model="passwordVisible" :destroy-on-close="true" :close-on-click-modal="false" size="30%">
<template #header>
<DrawerHeader :header="$t('setting.changePassword')" :back="handleClose" />
</template>
<el-row type="flex" justify="center">
<el-row type="flex" justify="center" v-loading="loading">
<el-col :span="22">
<el-form ref="formRef" label-position="top" :model="form" :rules="passRules">
<el-alert
@@ -1,5 +1,5 @@
<template>
<div v-loading="loading">
<div>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" :close-on-click-modal="false" size="50%">
<template #header>
<DrawerHeader header="Swap" :back="handleClose" />
@@ -40,17 +40,14 @@
</div>
<div v-if="form.isExist">
<el-card v-if="!form.isActive" class="mask-prompt">
<span>{{ $t('toolbox.fail2ban.unActive') }}</span>
</el-card>
<LayoutContent title="Fail2ban" :divider="true" :class="{ mask: !form.isActive }">
<LayoutContent title="Fail2ban" :divider="true">
<template #toolbar>
<el-row>
<el-col :span="16">
<el-button type="primary" plain @click="onLoadList('ignore')">
<el-button :disabled="!form.isActive" type="primary" plain @click="onLoadList('ignore')">
{{ $t('toolbox.fail2ban.ignoreIP') }}
</el-button>
<el-button type="primary" plain @click="onLoadList('banned')">
<el-button :disabled="!form.isActive" type="primary" plain @click="onLoadList('banned')">
{{ $t('toolbox.fail2ban.bannedIP') }}
</el-button>
</el-col>
@@ -89,7 +89,7 @@ const setting = () => {
};
const toDoc = async () => {
window.open('https://1panel.cn/docs/user_manual/hosts/supervisor/', '_blank', 'noopener,noreferrer');
window.open('https://1panel.cn/docs/user_manual/toolbox/supervisor/', '_blank', 'noopener,noreferrer');
};
const init = async () => {