Compare commits

...
14 Commits
Author SHA1 Message Date
zhengkunwang223 11ae05163c fix: 解决旧版本安装的应用升级之后状态异常的问题 (#1327) 2023-06-09 15:40:11 +00:00
wangdan-fit2cloud 0275716ff5 feat: 优化页面兼容移动端问题 (#1320)
#### 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-06-09 15:38:15 +00:00
ssongliu e9bdca5279 fix: 解决终端全屏及缩放的菜单显示问题 (#1316) 2023-06-09 08:56:11 +00:00
ssongliu cd84116a03 fix: 解决终端意外退出全屏导致菜单无法显示的问题 (#1314) 2023-06-09 08:02:12 +00:00
wangdan-fit2cloud 457effae85 fix: 终端全屏问题 (#1313)
#### 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-06-09 07:52:10 +00:00
zhengkunwang223 9361c358f1 style: 修改安装应用提示语句 (#1308) 2023-06-09 03:48:11 +00:00
ssongliu 39f952e460 fix: 优化 ssh 登录日志查询速度 (#1307) 2023-06-09 03:44:12 +00:00
ssongliu 7c600a357c fix: 解决应用备份或升级失败的问题 (#1306) 2023-06-09 03:42:11 +00:00
吴小白 8f036a0c3d feat: 上传文件到 OSS (#1304) 2023-06-09 10:47:09 +08:00
ssongliu 1184ae2b52 fix: 容器日志切割样式修改 (#1303) 2023-06-09 02:30:11 +00:00
zhengkunwang223 de7ef19034 fix: 解决系统启动时同步应用导致的报错 (#1301) 2023-06-08 15:20:11 +00:00
ssongliu e91b7caf4f fix: 解决 mysql 日志清空错误的问题 (#1298) 2023-06-08 13:50:11 +00:00
zhengkunwang223 033b3c10c0 feat: 应用详情适配夜间模式 (#1297) 2023-06-08 11:00:11 +00:00
ssongliu 97774c88d5 fix: 解决 sftp 上传大文件失败的问题 (#1294) 2023-06-08 10:58:15 +00:00
25 changed files with 551 additions and 423 deletions
+10 -1
View File
@@ -40,4 +40,13 @@ jobs:
dist/*.tar.gz
dist/checksums.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup OSSUTIL
uses: yizhoumo/setup-ossutil@v1
with:
endpoint: ${{ secrets.OSS_ENDPOINT }}
access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }}
access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
ossutil-version: '1.7.14'
- name: Upload Assets to OSS
- run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir
+4 -4
View File
@@ -296,6 +296,10 @@ func upgradeInstall(installId uint, detailId uint) error {
return
}
config := getAppCommonConfig(envs)
if config.ContainerName == "" {
config.ContainerName = install.ContainerName
envs[constant.ContainerName] = install.ContainerName
}
config.Advanced = true
if upErr = addDockerComposeCommonParam(composeMap, install.ServiceName, config, envs); upErr != nil {
return
@@ -314,10 +318,6 @@ func upgradeInstall(installId uint, detailId uint) error {
install.Version = detail.Version
install.AppDetailId = detailId
go func() {
_, _ = http.Get(detail.DownloadCallBackUrl)
}()
if out, err := compose.Down(install.GetComposePath()); err != nil {
if out != "" {
upErr = errors.New(out)
+1 -1
View File
@@ -99,7 +99,7 @@ func handleAppBackup(install *model.AppInstall, backupDir, fileName string) erro
return err
}
appPath := fmt.Sprintf("%s/%s", install.GetPath(), install.Name)
appPath := install.GetPath()
if err := handleTar(appPath, tmpDir, "app.tar.gz", ""); err != nil {
return err
}
+1 -1
View File
@@ -196,7 +196,7 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
path := ""
if strings.Contains(sourceDir, "/") {
itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "")
aheadDir := strings.ReplaceAll(sourceDir, itemDir, "")
aheadDir := sourceDir[:strings.LastIndex(sourceDir, "/")]
path += fmt.Sprintf("-C %s %s", aheadDir, itemDir)
} else {
path = sourceDir
+18 -15
View File
@@ -231,7 +231,8 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
}
for i := 0; i < len(fileList); i++ {
if strings.HasPrefix(path.Base(fileList[i]), "secure") {
dataItem := loadFailedSecureDatas(fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", fileList[i], command))
commandItem := fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", fileList[i], command)
dataItem := loadFailedSecureDatas(commandItem)
data.FailedCount += len(dataItem)
data.TotalCount += len(dataItem)
if req.Status != constant.StatusSuccess {
@@ -239,14 +240,16 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
}
}
if strings.HasPrefix(path.Base(fileList[i]), "auth.log") {
dataItem := loadFailedAuthDatas(fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth' %s", fileList[i], command))
commandItem := fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth' %s", fileList[i], command)
dataItem := loadFailedAuthDatas(commandItem)
data.FailedCount += len(dataItem)
data.TotalCount += len(dataItem)
if req.Status != constant.StatusSuccess {
data.Logs = append(data.Logs, dataItem...)
}
}
dataItem := loadSuccessDatas(fmt.Sprintf("cat %s | grep Accepted %s", fileList[i], command))
commandItem := fmt.Sprintf("cat %s | grep Accepted %s", fileList[i], command)
dataItem := loadSuccessDatas(commandItem)
data.TotalCount += len(dataItem)
if req.Status != constant.StatusFailed {
data.Logs = append(data.Logs, dataItem...)
@@ -257,6 +260,18 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
return nil, nil
}
var itemDatas []dto.SSHHistory
total, start, end := len(data.Logs), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
itemDatas = make([]dto.SSHHistory, 0)
} else {
if end >= total {
end = total
}
itemDatas = data.Logs[start:end]
}
data.Logs = itemDatas
timeNow := time.Now()
nyc, _ := time.LoadLocation(common.LoadTimeZone())
qqWry, err := qqwry.NewQQwry()
@@ -271,18 +286,6 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
}
data.Logs = itemLogs
var itemDatas []dto.SSHHistory
total, start, end := len(data.Logs), (req.Page-1)*req.PageSize, req.Page*req.PageSize
if start > total {
itemDatas = make([]dto.SSHHistory, 0)
} else {
if end >= total {
end = total
}
itemDatas = data.Logs[start:end]
}
data.Logs = itemDatas
return &data, nil
}
+1 -1
View File
@@ -22,7 +22,7 @@ func syncApp() {
}
global.LOG.Info("sync app start...")
if err := service.NewIAppService().SyncAppListFromRemote(); err != nil {
global.LOG.Errorf("sync app error: %s", err.Error())
global.LOG.Errorf("sync app error")
return
}
global.LOG.Info("sync app successful")
+12 -5
View File
@@ -1,8 +1,8 @@
package client
import (
"bufio"
"fmt"
"io"
"net"
"os"
"path"
@@ -75,11 +75,18 @@ func (s sftpClient) Upload(src, target string) (bool, error) {
return false, err
}
defer dstFile.Close()
ff, err := io.ReadAll(srcFile)
if err != nil {
return false, err
reader := bufio.NewReaderSize(srcFile, 128*1024*1024)
for {
chunk, err := reader.Peek(8 * 1024 * 1024)
if len(chunk) != 0 {
_, _ = dstFile.Write(chunk)
_, _ = reader.Discard(len(chunk))
}
if err != nil {
break
}
}
_, _ = dstFile.Write(ff)
return true, nil
}
@@ -56,7 +56,7 @@
<MdEditor
v-model="upgradeInfo.releaseNote"
previewOnly
:theme="globalStore.$state.themeConfig.theme || 'light'"
:theme="globalStore.$state.themeConfig.theme === 'dark' ? 'dark' : 'light'"
/>
</div>
<template #footer>
+2 -2
View File
@@ -1157,8 +1157,8 @@ const message = {
containerName: '容器名称',
containerNameHelper: '可以为空,为空自动生成',
allowPort: '端口外部访问',
allowPortHelper: '允许外部端口访问会放开防火墙端口,php运行环境请勿放开',
appInstallWarn: '应用默认不放开外部访问端口,可以在高级设置中选择放开',
allowPortHelper: '允许端口外部访问会放开防火墙端口,php运行环境请勿放开',
appInstallWarn: '应用端口默认不允许外部访问,可以在下方高级设置中选择放开',
upgradeStart: '开始升级!请稍后刷新页面',
toFolder: '进入安装目录',
editCompose: '编辑 compose 文件',
+7
View File
@@ -39,6 +39,7 @@ let timer: NodeJS.Timer | null = null;
const classObj = computed(() => {
return {
fullScreen: globalStore.isFullScreen,
hideSidebar: menuStore.isCollapse,
openSidebar: !menuStore.isCollapse,
mobile: globalStore.device === DeviceType.Mobile,
@@ -162,6 +163,12 @@ onMounted(() => {
width: calc(100% - var(--panel-menu-hide-width));
}
}
.fullScreen {
.main-container {
margin-left: 0px;
}
}
// for mobile response 适配移动端
.mobile {
.main-container {
+5
View File
@@ -13,6 +13,11 @@
align-items: center;
}
.flx-wrap {
display: flex;
flex-wrap: wrap;
}
.clearfix::after {
display: block;
height: 0;
+15
View File
@@ -65,4 +65,19 @@
}
}
}
.database-status {
.title {
margin-left: 10px !important;
}
.content {
margin-left: 10px !important;
}
}
@media only screen and (max-width: 768px) {
.el-col-xs-24 {
margin-bottom: 10px;
}
}
}
@@ -83,7 +83,11 @@
</div>
</div>
<div style="margin-left: 10px">
<MdEditor v-model="app.readMe" previewOnly :themes="globalStore.$state.themeConfig.theme || 'light'" />
<MdEditor
v-model="app.readMe"
previewOnly
:theme="globalStore.$state.themeConfig.theme === 'dark' ? 'dark' : 'light'"
/>
</div>
</template>
</LayoutContent>
@@ -11,7 +11,7 @@
</template>
<el-row v-loading="loading">
<el-col :span="22" :offset="1">
<el-alert type="info" :closable="false">
<el-alert type="error" :closable="false">
<p>{{ $t('app.appInstallWarn') }}</p>
</el-alert>
<el-form
@@ -43,8 +43,8 @@
</el-radio-group>
<el-row style="margin-top: 20px" v-if="confShowType === 'base'">
<el-col :span="1"><br /></el-col>
<el-col :span="10">
<el-form :model="form" label-position="left" :rules="rules" ref="formRef" label-width="150px">
<el-col :xs="24" :sm="24" :md="15" :lg="12" :xl="10">
<el-form :model="form" label-position="left" :rules="rules" ref="formRef" label-width="120px">
<el-form-item :label="$t('container.mirrors')" prop="mirrors">
<div style="width: 100%" v-if="form.mirrors">
<el-input
@@ -103,7 +103,7 @@
<el-form-item :label="$t('container.cutLog')" prop="hasLogOption">
<el-switch v-model="form.logOptionShow" @change="handleLogOption"></el-switch>
<span class="input-help"></span>
<div v-if="form.logOptionShow">
<div v-if="logOptionShow">
<el-tag>{{ $t('container.maxSize') }}: {{ form.logMaxSize }}</el-tag>
<el-tag style="margin-left: 5px">
{{ $t('container.maxFile') }}: {{ form.logMaxFile }}
@@ -251,6 +251,7 @@ const registriesRef = ref();
const confirmDialogRefLive = ref();
const confirmDialogRefCgroup = ref();
const confirmDialogRefIptable = ref();
const logOptionShow = ref();
const form = reactive({
isSwarm: false,
@@ -437,10 +438,12 @@ const search = async () => {
form.registries = res.data.insecureRegistries ? res.data.insecureRegistries.join('\n') : '';
if (res.data.logMaxFile || res.data.logMaxSize) {
form.logOptionShow = true;
logOptionShow.value = true;
form.logMaxFile = Number(res.data.logMaxFile);
form.logMaxSize = res.data.logMaxSize;
} else {
form.logOptionShow = false;
logOptionShow.value = false;
}
};
@@ -26,10 +26,10 @@
<el-input v-model.number="form.logMaxSize">
<template #append>
<el-select v-model="form.sizeUnit" style="width: 70px">
<el-option label="byte" value="b"></el-option>
<el-option label="kb" value="k"></el-option>
<el-option label="mb" value="m"></el-option>
<el-option label="gb" value="g"></el-option>
<el-option label="Byte" value="b"></el-option>
<el-option label="KB" value="k"></el-option>
<el-option label="MB" value="m"></el-option>
<el-option label="GB" value="g"></el-option>
</el-select>
</template>
</el-input>
+173 -169
View File
@@ -123,194 +123,198 @@
</el-row>
</template>
<template #main>
<el-row :gutter="20" v-show="hasRecords">
<el-col :span="8">
<div>
<ul class="infinite-list" style="overflow: auto">
<li
v-for="(item, index) in records"
:key="index"
@click="forDetail(item)"
class="infinite-list-item"
>
<el-icon v-if="item.status === 'Success'"><Select /></el-icon>
<el-icon v-if="item.status === 'Waiting'"><Loading /></el-icon>
<el-icon v-if="item.status === 'Failed'"><CloseBold /></el-icon>
<span v-if="item.id === currentRecord.id" style="color: red">
{{ dateFormat(0, 0, item.startTime) }}
</span>
<span v-else>{{ dateFormat(0, 0, item.startTime) }}</span>
</li>
</ul>
<div style="margin-top: 10px; font-size: 12px; float: right">
<el-pagination
:page-size="searchInfo.pageSize"
:current-page="searchInfo.page"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
:pager-count="5"
:page-sizes="[6, 8, 10, 12, 14]"
small
layout="total, sizes, prev, pager, next"
:total="searchInfo.recordTotal"
/>
</div>
</div>
</el-col>
<el-col :span="16">
<el-form label-position="top" :v-key="refresh">
<el-row type="flex" justify="center">
<el-form-item class="descriptionWide" v-if="isBackup()">
<template #label>
<span class="status-label">{{ $t('cronjob.target') }}</span>
</template>
<span class="status-count">{{ dialogData.rowData!.targetDir }}</span>
<el-button
v-if="currentRecord?.status === 'Success'"
type="primary"
style="margin-left: 10px"
link
icon="Download"
@click="onDownload(currentRecord, dialogData.rowData!.targetDirID)"
<div style="overflow: auto">
<el-row :gutter="20" v-show="hasRecords" style="min-width: 900px">
<el-col :span="8">
<div>
<ul class="infinite-list" style="overflow: auto">
<li
v-for="(item, index) in records"
:key="index"
@click="forDetail(item)"
class="infinite-list-item"
>
{{ $t('file.download') }}
</el-button>
</el-form-item>
<el-form-item class="description" v-if="dialogData.rowData!.type === 'website'">
<el-icon v-if="item.status === 'Success'"><Select /></el-icon>
<el-icon v-if="item.status === 'Waiting'"><Loading /></el-icon>
<el-icon v-if="item.status === 'Failed'"><CloseBold /></el-icon>
<span v-if="item.id === currentRecord.id" style="color: red">
{{ dateFormat(0, 0, item.startTime) }}
</span>
<span v-else>{{ dateFormat(0, 0, item.startTime) }}</span>
</li>
</ul>
<div style="margin-top: 10px; font-size: 12px; float: right">
<el-pagination
:page-size="searchInfo.pageSize"
:current-page="searchInfo.page"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
:pager-count="5"
:page-sizes="[6, 8, 10, 12, 14]"
small
layout="total, sizes, prev, pager, next"
:total="searchInfo.recordTotal"
/>
</div>
</div>
</el-col>
<el-col :span="16">
<el-form label-position="top" :v-key="refresh">
<el-row type="flex" justify="center">
<el-form-item class="descriptionWide" v-if="isBackup()">
<template #label>
<span class="status-label">{{ $t('cronjob.target') }}</span>
</template>
<span class="status-count">{{ dialogData.rowData!.targetDir }}</span>
<el-button
v-if="currentRecord?.status === 'Success'"
type="primary"
style="margin-left: 10px"
link
icon="Download"
@click="onDownload(currentRecord, dialogData.rowData!.targetDirID)"
>
{{ $t('file.download') }}
</el-button>
</el-form-item>
<el-form-item class="description" v-if="dialogData.rowData!.type === 'website'">
<template #label>
<span class="status-label">{{ $t('cronjob.website') }}</span>
</template>
<span v-if="dialogData.rowData!.website !== 'all'" class="status-count">
{{ dialogData.rowData!.website }}
</span>
<span v-else class="status-count">
{{ $t('commons.table.all') }}
</span>
</el-form-item>
<el-form-item class="description" v-if="dialogData.rowData!.type === 'database'">
<template #label>
<span class="status-label">{{ $t('cronjob.database') }}</span>
</template>
<span v-if="dialogData.rowData!.dbName !== 'all'" class="status-count">
{{ dialogData.rowData!.dbName }}
</span>
<span v-else class="status-count">
{{ $t('commons.table.all') }}
</span>
</el-form-item>
<el-form-item class="description" v-if="dialogData.rowData!.type === 'directory'">
<template #label>
<span class="status-label">{{ $t('cronjob.directory') }}</span>
</template>
<span v-if="dialogData.rowData!.sourceDir.length <= 12" class="status-count">
{{ dialogData.rowData!.sourceDir }}
</span>
<div v-else>
<el-popover
placement="top-start"
trigger="hover"
width="250"
:content="dialogData.rowData!.sourceDir"
>
<template #reference>
<span class="status-count">
{{ dialogData.rowData!.sourceDir.substring(0, 12) }}...
</span>
</template>
</el-popover>
</div>
</el-form-item>
<el-form-item class="description" v-if="isBackup()">
<template #label>
<span class="status-label">{{ $t('cronjob.retainCopies') }}</span>
</template>
<span class="status-count">{{ dialogData.rowData!.retainCopies }}</span>
</el-form-item>
</el-row>
<el-form-item
class="description"
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'directory'"
>
<template #label>
<span class="status-label">{{ $t('cronjob.website') }}</span>
<span class="status-label">{{ $t('cronjob.exclusionRules') }}</span>
</template>
<span v-if="dialogData.rowData!.website !== 'all'" class="status-count">
{{ dialogData.rowData!.website }}
</span>
<span v-else class="status-count">
{{ $t('commons.table.all') }}
</span>
</el-form-item>
<el-form-item class="description" v-if="dialogData.rowData!.type === 'database'">
<template #label>
<span class="status-label">{{ $t('cronjob.database') }}</span>
</template>
<span v-if="dialogData.rowData!.dbName !== 'all'" class="status-count">
{{ dialogData.rowData!.dbName }}
</span>
<span v-else class="status-count">
{{ $t('commons.table.all') }}
</span>
</el-form-item>
<el-form-item class="description" v-if="dialogData.rowData!.type === 'directory'">
<template #label>
<span class="status-label">{{ $t('cronjob.directory') }}</span>
</template>
<span v-if="dialogData.rowData!.sourceDir.length <= 12" class="status-count">
{{ dialogData.rowData!.sourceDir }}
<span v-if="dialogData.rowData!.exclusionRules.length <= 12" class="status-count">
{{ dialogData.rowData!.exclusionRules }}
</span>
<div v-else>
<el-popover
placement="top-start"
trigger="hover"
width="250"
:content="dialogData.rowData!.sourceDir"
:content="dialogData.rowData!.exclusionRules"
>
<template #reference>
<span class="status-count">
{{ dialogData.rowData!.sourceDir.substring(0, 12) }}...
{{ dialogData.rowData!.exclusionRules.substring(0, 12) }}...
</span>
</template>
</el-popover>
</div>
</el-form-item>
<el-form-item class="description" v-if="isBackup()">
<template #label>
<span class="status-label">{{ $t('cronjob.retainCopies') }}</span>
</template>
<span class="status-count">{{ dialogData.rowData!.retainCopies }}</span>
</el-form-item>
</el-row>
<el-form-item
class="description"
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'directory'"
>
<template #label>
<span class="status-label">{{ $t('cronjob.exclusionRules') }}</span>
</template>
<span v-if="dialogData.rowData!.exclusionRules.length <= 12" class="status-count">
{{ dialogData.rowData!.exclusionRules }}
</span>
<div v-else>
<el-popover
placement="top-start"
trigger="hover"
width="250"
:content="dialogData.rowData!.exclusionRules"
>
<template #reference>
<span class="status-count">
{{ dialogData.rowData!.exclusionRules.substring(0, 12) }}...
</span>
<el-row type="flex" justify="center">
<el-form-item class="descriptionWide">
<template #label>
<span class="status-label">{{ $t('commons.search.timeStart') }}</span>
</template>
</el-popover>
</div>
</el-form-item>
<el-row type="flex" justify="center">
<el-form-item class="descriptionWide">
<template #label>
<span class="status-label">{{ $t('commons.search.timeStart') }}</span>
</template>
<span class="status-count">{{ dateFormat(0, 0, currentRecord?.startTime) }}</span>
</el-form-item>
<el-form-item class="description">
<template #label>
<span class="status-label">{{ $t('commons.table.interval') }}</span>
</template>
<span class="status-count" v-if="currentRecord?.interval! <= 1000">
{{ currentRecord?.interval }} ms
</span>
<span class="status-count" v-if="currentRecord?.interval! > 1000">
{{ currentRecord?.interval! / 1000 }} s
</span>
</el-form-item>
<el-form-item class="description">
<template #label>
<span class="status-label">{{ $t('commons.table.status') }}</span>
</template>
<el-tooltip v-if="currentRecord?.status === 'Failed'" placement="top">
<template #content>
<div style="width: 300px; word-break: break-all">
{{ currentRecord?.message }}
</div>
<span class="status-count">
{{ dateFormat(0, 0, currentRecord?.startTime) }}
</span>
</el-form-item>
<el-form-item class="description">
<template #label>
<span class="status-label">{{ $t('commons.table.interval') }}</span>
</template>
<el-tag type="danger">{{ $t('commons.table.statusFailed') }}</el-tag>
</el-tooltip>
<el-tag type="success" v-if="currentRecord?.status === 'Success'">
{{ $t('commons.table.statusSuccess') }}
</el-tag>
<el-tag type="info" v-if="currentRecord?.status === 'Waiting'">
{{ $t('commons.table.statusWaiting') }}
</el-tag>
</el-form-item>
</el-row>
<el-row v-if="currentRecord?.records">
<span>{{ $t('commons.table.records') }}</span>
<codemirror
ref="mymirror"
:autofocus="true"
:placeholder="$t('cronjob.noLogs')"
:indent-with-tab="true"
:tabSize="4"
style="height: calc(100vh - 484px); width: 100%; margin-top: 5px"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
:styleActiveLine="true"
:extensions="extensions"
v-model="currentRecordDetail"
:disabled="true"
/>
</el-row>
</el-form>
</el-col>
</el-row>
<span class="status-count" v-if="currentRecord?.interval! <= 1000">
{{ currentRecord?.interval }} ms
</span>
<span class="status-count" v-if="currentRecord?.interval! > 1000">
{{ currentRecord?.interval! / 1000 }} s
</span>
</el-form-item>
<el-form-item class="description">
<template #label>
<span class="status-label">{{ $t('commons.table.status') }}</span>
</template>
<el-tooltip v-if="currentRecord?.status === 'Failed'" placement="top">
<template #content>
<div style="width: 300px; word-break: break-all">
{{ currentRecord?.message }}
</div>
</template>
<el-tag type="danger">{{ $t('commons.table.statusFailed') }}</el-tag>
</el-tooltip>
<el-tag type="success" v-if="currentRecord?.status === 'Success'">
{{ $t('commons.table.statusSuccess') }}
</el-tag>
<el-tag type="info" v-if="currentRecord?.status === 'Waiting'">
{{ $t('commons.table.statusWaiting') }}
</el-tag>
</el-form-item>
</el-row>
<el-row v-if="currentRecord?.records">
<span>{{ $t('commons.table.records') }}</span>
<codemirror
ref="mymirror"
:autofocus="true"
:placeholder="$t('cronjob.noLogs')"
:indent-with-tab="true"
:tabSize="4"
style="height: calc(100vh - 484px); width: 100%; margin-top: 5px"
:lineWrapping="true"
:matchBrackets="true"
theme="cobalt"
:styleActiveLine="true"
:extensions="extensions"
v-model="currentRecordDetail"
:disabled="true"
/>
</el-row>
</el-form>
</el-col>
</el-row>
</div>
<div class="app-warn" v-show="!hasRecords">
<div>
<span>{{ $t('cronjob.noRecord') }}</span>
@@ -263,7 +263,7 @@ const onSaveConf = async () => {
};
const loadContainerLog = async (containerID: string) => {
dialogContainerLogRef.value!.acceptParams({ containerID: containerID, container: mysqlName.value });
dialogContainerLogRef.value!.acceptParams({ containerID: containerID, container: containerID });
};
const loadBaseInfo = async () => {
@@ -1,7 +1,7 @@
<template>
<div v-if="persistenceShow">
<el-row :gutter="20" style="margin-top: 5px" class="row-box">
<el-col :span="12">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-card class="el-card">
<template #header>
<div class="card-header">
@@ -36,7 +36,7 @@
</el-form>
</el-card>
</el-col>
<el-col :span="12">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-card class="el-card">
<template #header>
<div class="card-header">
@@ -1,108 +1,131 @@
<template>
<div v-if="statusShow">
<div v-if="statusShow" class="database-status">
<el-form label-position="top">
<span class="title">{{ $t('database.baseParam') }}</span>
<el-divider class="devider" />
<el-row type="flex" style="margin-left: 50px" justify="center">
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">uptime_in_days</span>
</template>
<span class="status-count">{{ redisStatus.uptime_in_days }}</span>
<span class="input-help">{{ $t('database.uptimeInDays') }}</span>
</el-form-item>
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">tcp_port</span>
</template>
<span class="status-count">{{ redisStatus.tcp_port }}</span>
<span class="input-help">{{ $t('database.tcpPort') }}</span>
</el-form-item>
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">connected_clients</span>
</template>
<span class="status-count">{{ redisStatus.connected_clients }}</span>
<span class="input-help">{{ $t('database.connectedClients') }}</span>
</el-form-item>
<el-form-item style="width: 25%"></el-form-item>
<el-row class="content">
<el-col :xs="8" :sm="6" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">uptime_in_days</span>
</template>
<span class="status-count">{{ redisStatus.uptime_in_days }}</span>
<span class="input-help">{{ $t('database.uptimeInDays') }}</span>
</el-form-item>
</el-col>
<el-col :xs="8" :sm="6" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">tcp_port</span>
</template>
<span class="status-count">{{ redisStatus.tcp_port }}</span>
<span class="input-help">{{ $t('database.tcpPort') }}</span>
</el-form-item>
</el-col>
<el-col :xs="8" :sm="6" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">connected_clients</span>
</template>
<span class="status-count">{{ redisStatus.connected_clients }}</span>
<span class="input-help">{{ $t('database.connectedClients') }}</span>
</el-form-item>
</el-col>
</el-row>
<span class="title">{{ $t('database.performanceParam') }}</span>
<el-divider class="devider" />
<el-row type="flex" style="margin-left: 50px" justify="center">
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">used_memory_rss</span>
</template>
<span class="status-count">{{ redisStatus.used_memory_rss }}</span>
<span class="input-help">{{ $t('database.usedMemoryRss') }}</span>
</el-form-item>
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">used_memory</span>
</template>
<span class="status-count">{{ redisStatus.used_memory }}</span>
<span class="input-help">{{ $t('database.usedMemory') }}</span>
</el-form-item>
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">mem_fragmentation_ratio</span>
</template>
<span class="status-count">{{ redisStatus.mem_fragmentation_ratio }}</span>
<span class="input-help">{{ $t('database.tmpTableToDBHelper') }}</span>
</el-form-item>
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">total_connections_received</span>
</template>
<span class="status-count">{{ redisStatus.total_connections_received }}</span>
<span class="input-help">{{ $t('database.totalConnectionsReceived') }}</span>
</el-form-item>
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">total_commands_processed</span>
</template>
<span class="status-count">{{ redisStatus.total_commands_processed }}</span>
<span class="input-help">{{ $t('database.totalCommandsProcessed') }}</span>
</el-form-item>
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">instantaneous_ops_per_sec</span>
</template>
<span class="status-count">{{ redisStatus.instantaneous_ops_per_sec }}</span>
<span class="input-help">{{ $t('database.instantaneousOpsPerSec') }}</span>
</el-form-item>
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">keyspace_hits</span>
</template>
<span class="status-count">{{ redisStatus.keyspace_hits }}</span>
<span class="input-help">{{ $t('database.keyspaceHits') }}</span>
</el-form-item>
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">keyspace_misses</span>
</template>
<span class="status-count">{{ redisStatus.keyspace_misses }}</span>
<span class="input-help">{{ $t('database.keyspaceMisses') }}</span>
</el-form-item>
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">hit</span>
</template>
<span class="status-count">{{ redisStatus.hit }}</span>
<span class="input-help">{{ $t('database.hit') }}</span>
</el-form-item>
<el-form-item style="width: 25%">
<template #label>
<span class="status-label">latest_fork_usec</span>
</template>
<span class="status-count">{{ redisStatus.latest_fork_usec }}</span>
<span class="input-help">{{ $t('database.latestForkUsec') }}</span>
</el-form-item>
<el-form-item style="width: 25%"></el-form-item>
<el-form-item style="width: 25%"></el-form-item>
<el-row class="content">
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">used_memory_rss</span>
</template>
<span class="status-count">{{ redisStatus.used_memory_rss }}</span>
<span class="input-help">{{ $t('database.usedMemoryRss') }}</span>
</el-form-item>
</el-col>
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">used_memory</span>
</template>
<span class="status-count">{{ redisStatus.used_memory }}</span>
<span class="input-help">{{ $t('database.usedMemory') }}</span>
</el-form-item>
</el-col>
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">mem_fragmentation_ratio</span>
</template>
<span class="status-count">{{ redisStatus.mem_fragmentation_ratio }}</span>
<span class="input-help">{{ $t('database.tmpTableToDBHelper') }}</span>
</el-form-item>
</el-col>
<el-col :xs="8" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">total_connections_received</span>
</template>
<span class="status-count">{{ redisStatus.total_connections_received }}</span>
<span class="input-help">{{ $t('database.totalConnectionsReceived') }}</span>
</el-form-item>
</el-col>
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">total_commands_processed</span>
</template>
<span class="status-count">{{ redisStatus.total_commands_processed }}</span>
<span class="input-help">{{ $t('database.totalCommandsProcessed') }}</span>
</el-form-item>
</el-col>
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">instantaneous_ops_per_sec</span>
</template>
<span class="status-count">{{ redisStatus.instantaneous_ops_per_sec }}</span>
<span class="input-help">{{ $t('database.instantaneousOpsPerSec') }}</span>
</el-form-item>
</el-col>
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">keyspace_hits</span>
</template>
<span class="status-count">{{ redisStatus.keyspace_hits }}</span>
<span class="input-help">{{ $t('database.keyspaceHits') }}</span>
</el-form-item>
</el-col>
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">keyspace_misses</span>
</template>
<span class="status-count">{{ redisStatus.keyspace_misses }}</span>
<span class="input-help">{{ $t('database.keyspaceMisses') }}</span>
</el-form-item>
</el-col>
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">hit</span>
</template>
<span class="status-count">{{ redisStatus.hit }}</span>
<span class="input-help">{{ $t('database.hit') }}</span>
</el-form-item>
</el-col>
<el-col :xs="12" :sm="12" :md="6" :lg="6" :xl="6">
<el-form-item>
<template #label>
<span class="status-label">latest_fork_usec</span>
</template>
<span class="status-count">{{ redisStatus.latest_fork_usec }}</span>
<span class="input-help">{{ $t('database.latestForkUsec') }}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
@@ -169,16 +192,21 @@ defineExpose({
</script>
<style lang="scss" scoped>
.devider {
display: block;
height: 1px;
width: 100%;
margin: 12px 0;
border-top: 1px var(--el-border-color) var(--el-border-style);
}
.title {
font-size: 20px;
font-weight: 500;
margin-left: 50px;
.database-status {
.devider {
display: block;
height: 1px;
width: 100%;
margin: 12px 0;
border-top: 1px var(--el-border-color) var(--el-border-style);
}
.title {
font-size: 20px;
font-weight: 500;
margin-left: 50px;
}
.content {
margin-left: 50px;
}
}
</style>
+101 -73
View File
@@ -6,17 +6,20 @@
<el-col :span="24">
<el-card style="overflow: inherit">
<template #header>
<span class="title">{{ $t('monitor.avgLoad') }}</span>
<el-date-picker
@change="search('load')"
v-model="timeRangeLoad"
type="datetimerange"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; margin-top: -5px; width: 360px"
></el-date-picker>
<div :class="mobile ? 'flx-wrap' : 'flx-justify-between'">
<span class="title">{{ $t('monitor.avgLoad') }}</span>
<el-date-picker
@change="search('load')"
v-model="timeRangeLoad"
type="datetimerange"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="max-width: 360px; width: 100%"
:size="mobile ? 'small' : 'default'"
></el-date-picker>
</div>
</template>
<div id="loadLoadChart" class="chart"></div>
</el-card>
@@ -26,17 +29,20 @@
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-card style="overflow: inherit">
<template #header>
<span class="title">CPU</span>
<el-date-picker
@change="search('cpu')"
v-model="timeRangeCpu"
type="datetimerange"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; margin-top: -5px; width: 360px"
></el-date-picker>
<div :class="mobile ? 'flx-wrap' : 'flx-justify-between'">
<span class="title">CPU</span>
<el-date-picker
@change="search('cpu')"
v-model="timeRangeCpu"
type="datetimerange"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="max-width: 360px; width: 100%"
:size="mobile ? 'small' : 'default'"
></el-date-picker>
</div>
</template>
<div id="loadCPUChart" class="chart"></div>
</el-card>
@@ -44,17 +50,20 @@
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-card style="overflow: inherit">
<template #header>
<span class="title">{{ $t('monitor.memory') }}</span>
<el-date-picker
@change="search('memory')"
v-model="timeRangeMemory"
type="datetimerange"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; margin-top: -5px; width: 360px"
></el-date-picker>
<div :class="mobile ? 'flx-wrap' : 'flx-justify-between'">
<span class="title">{{ $t('monitor.memory') }}</span>
<el-date-picker
@change="search('memory')"
v-model="timeRangeMemory"
type="datetimerange"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="max-width: 360px; width: 100%"
:size="mobile ? 'small' : 'default'"
></el-date-picker>
</div>
</template>
<div id="loadMemoryChart" class="chart"></div>
</el-card>
@@ -64,17 +73,20 @@
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-card style="overflow: inherit">
<template #header>
<span class="title">{{ $t('monitor.disk') }} IO</span>
<el-date-picker
@change="search('io')"
v-model="timeRangeIO"
type="datetimerange"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; margin-top: -5px; width: 360px"
></el-date-picker>
<div :class="mobile ? 'flx-wrap' : 'flx-justify-between'">
<span class="title">{{ $t('monitor.disk') }} IO</span>
<el-date-picker
@change="search('io')"
v-model="timeRangeIO"
type="datetimerange"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="max-width: 360px; width: 100%"
:size="mobile ? 'small' : 'default'"
></el-date-picker>
</div>
</template>
<div id="loadIOChart" class="chart"></div>
</el-card>
@@ -82,34 +94,43 @@
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-card style="overflow: inherit">
<template #header>
<span class="title">{{ $t('monitor.network') }} IO:</span>
<el-popover placement="bottom" :width="200" trigger="click">
<el-select @change="search('network')" v-model="networkChoose">
<template #prefix>{{ $t('monitor.networkCard') }}</template>
<div v-for="item in netOptions" :key="item">
<el-option v-if="item === 'all'" :label="$t('commons.table.all')" :value="item" />
<el-option v-else :label="item" :value="item" />
</div>
</el-select>
<template #reference>
<span class="networkOption" v-if="networkChoose === 'all'">
{{ $t('commons.table.all') }}
</span>
<span v-else class="networkOption">
{{ networkChoose }}
</span>
</template>
</el-popover>
<el-date-picker
@change="search('network')"
v-model="timeRangeNetwork"
type="datetimerange"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="float: right; margin-top: -5px; width: 360px"
></el-date-picker>
<div :class="mobile ? 'flx-wrap' : 'flx-justify-between'">
<div>
<span class="title">{{ $t('monitor.network') }} IO:</span>
<el-popover placement="bottom" :width="200" trigger="click">
<el-select @change="search('network')" v-model="networkChoose">
<template #prefix>{{ $t('monitor.networkCard') }}</template>
<div v-for="item in netOptions" :key="item">
<el-option
v-if="item === 'all'"
:label="$t('commons.table.all')"
:value="item"
/>
<el-option v-else :label="item" :value="item" />
</div>
</el-select>
<template #reference>
<span class="networkOption" v-if="networkChoose === 'all'">
{{ $t('commons.table.all') }}
</span>
<span v-else class="networkOption">
{{ networkChoose }}
</span>
</template>
</el-popover>
</div>
<el-date-picker
@change="search('network')"
v-model="timeRangeNetwork"
type="datetimerange"
:range-separator="$t('commons.search.timeRange')"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
style="max-width: 360px; width: 100%"
:size="mobile ? 'small' : 'default'"
></el-date-picker>
</div>
</template>
<div id="loadNetworkChart" class="chart"></div>
</el-card>
@@ -119,13 +140,20 @@
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue';
import * as echarts from 'echarts';
import { loadMonitor, getNetworkOptions } from '@/api/modules/monitor';
import { Monitor } from '@/api/interface/monitor';
import { dateFormatWithoutYear } from '@/utils/util';
import i18n from '@/lang';
import MonitorRouter from '@/views/host/monitor/index.vue';
import { GlobalStore } from '@/store';
const globalStore = GlobalStore();
const mobile = computed(() => {
return globalStore.isMobile();
});
const zoomStart = ref();
const monitorBase = ref();
@@ -149,9 +149,11 @@ const globalStore = GlobalStore();
function toggleFullscreen() {
if (screenfull.isEnabled) {
screenfull.toggle();
globalStore.setScreenFull();
}
}
screenfull.on('change', () => {
globalStore.isFullScreen = screenfull.isFullscreen;
});
const localHostID = ref();
@@ -181,6 +183,7 @@ interface Tree {
}
const acceptParams = async () => {
globalStore.isFullScreen = false;
loadCommand();
const res = await getHostTree({});
hostTree.value = res.data;
+28 -23
View File
@@ -15,39 +15,44 @@
</el-button>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<TableSetting @search="search()" />
<div class="search-button">
<el-input
v-model="searchIP"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search') + ' ip'"
></el-input>
<div class="flx-align-center">
<div class="search-button">
<el-input
v-model="searchIP"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@change="search()"
:placeholder="$t('commons.button.search') + ' ip'"
></el-input>
</div>
<TableSetting @search="search()" />
</div>
</el-col>
</el-row>
</template>
<template #search>
<el-select v-model="searchStatus" @change="search()" clearable>
<template #prefix>{{ $t('commons.table.status') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
<el-option :label="$t('commons.status.failed')" value="Failed"></el-option>
</el-select>
<el-button type="primary" plain @click="onClean()" style="margin-left: 10px">
{{ $t('logs.deleteLogs') }}
</el-button>
<div class="flx-align-center">
<el-select v-model="searchStatus" @change="search()" clearable>
<template #prefix>{{ $t('commons.table.status') }}</template>
<el-option :label="$t('commons.table.all')" value=""></el-option>
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
<el-option :label="$t('commons.status.failed')" value="Failed"></el-option>
</el-select>
<el-button type="primary" plain @click="onClean()" style="margin-left: 10px">
{{ $t('logs.deleteLogs') }}
</el-button>
</div>
</template>
<template #main>
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
<el-table-column min-width="40" :label="$t('logs.loginIP')" prop="ip" />
<el-table-column min-width="40" :label="$t('logs.loginAddress')" prop="address" />
<el-table-column :label="$t('logs.loginIP')" prop="ip" />
<el-table-column :label="$t('logs.loginAddress')" prop="address" />
<el-table-column :label="$t('logs.loginAgent')" show-overflow-tooltip prop="agent" />
<el-table-column min-width="40" :label="$t('logs.loginStatus')" prop="status">
<el-table-column :label="$t('logs.loginStatus')" prop="status">
<template #default="{ row }">
<div v-if="row.status === 'Success'">
<el-tag type="success">{{ $t('commons.status.success') }}</el-tag>
+6 -3
View File
@@ -145,9 +145,9 @@ onMounted(() => {
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: 768px) {
font-size: 35px;
}
}
@media only screen and (max-width: 1110px) {
margin-bottom: 20px;
@@ -170,6 +170,9 @@ onMounted(() => {
@media only screen and (max-width: 1110px) {
margin: 60px auto 0;
}
@media only screen and (max-width: 768px) {
width: 100%;
}
}
}
</style>
+7 -3
View File
@@ -100,9 +100,9 @@ onMounted(() => {
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: 768px) {
font-size: 35px;
}
}
@media only screen and (max-width: 1110px) {
margin-bottom: 20px;
@@ -125,6 +125,10 @@ onMounted(() => {
@media only screen and (max-width: 1110px) {
margin: 60px auto 0;
}
@media only screen and (max-width: 768px) {
width: 100%;
}
}
}
</style>