mirror of https://github.com/1Panel-dev/1Panel
fix: 修改容器部分 bug
parent
cd89caa0c4
commit
0a20aaa3fa
|
@ -40,6 +40,5 @@ type ImageRepoOption struct {
|
|||
}
|
||||
|
||||
type ImageRepoDelete struct {
|
||||
DeleteInsecure bool `json:"deleteInsecure"`
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
Ids []uint `json:"ids" validate:"required"`
|
||||
}
|
||||
|
|
|
@ -55,7 +55,10 @@ func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{
|
|||
for _, val := range item.Labels {
|
||||
tag = append(tag, val)
|
||||
}
|
||||
createTime, _ := time.Parse("2006-01-02T15:04:05Z", item.CreatedAt)
|
||||
if len(item.CreatedAt) > 19 {
|
||||
item.CreatedAt = item.CreatedAt[0:19]
|
||||
}
|
||||
createTime, _ := time.Parse("2006-01-02T15:04:05", item.CreatedAt)
|
||||
data = append(data, dto.Volume{
|
||||
CreatedAt: createTime,
|
||||
Name: item.Name,
|
||||
|
|
|
@ -92,32 +92,9 @@ func (u *ImageRepoService) BatchDelete(req dto.ImageRepoDelete) error {
|
|||
return errors.New("The default value cannot be edit !")
|
||||
}
|
||||
}
|
||||
if !req.DeleteInsecure {
|
||||
if err := imageRepoRepo.Delete(commonRepo.WithIdsIn(req.Ids)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
repos, err := imageRepoRepo.List(commonRepo.WithIdsIn(req.Ids))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, repo := range repos {
|
||||
if repo.Protocol == "http" {
|
||||
_ = u.handleRegistries("", repo.DownloadUrl, "delete")
|
||||
}
|
||||
if repo.Auth {
|
||||
_, _ = cmd.Execf("docker logout %s://%s", repo.Protocol, repo.DownloadUrl)
|
||||
}
|
||||
}
|
||||
if err := imageRepoRepo.Delete(commonRepo.WithIdsIn(req.Ids)); err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, err := cmd.Exec("systemctl restart docker")
|
||||
if err != nil {
|
||||
return errors.New(string(stdout))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -157,7 +157,6 @@ export namespace Container {
|
|||
}
|
||||
export interface RepoDelete {
|
||||
ids: Array<number>;
|
||||
deleteInsecure: boolean;
|
||||
}
|
||||
export interface RepoInfo {
|
||||
id: number;
|
||||
|
|
|
@ -336,11 +336,14 @@ export default {
|
|||
|
||||
log: 'Logs',
|
||||
slowLog: 'Slowlogs',
|
||||
noData: 'No slow log yet...',
|
||||
|
||||
isOn: 'Is on',
|
||||
longQueryTime: 'Slow query threshold',
|
||||
|
||||
status: 'The current state',
|
||||
baseParam: 'Basic parameter',
|
||||
performanceParam: 'Performance parameter',
|
||||
terminal: 'Terminal mode',
|
||||
second: 'Second',
|
||||
timeout: 'Timeout',
|
||||
|
@ -403,6 +406,8 @@ export default {
|
|||
last10Min: 'Last 10 Minutes',
|
||||
newName: 'New name',
|
||||
|
||||
user: 'User',
|
||||
command: 'Command',
|
||||
custom: 'Custom',
|
||||
emptyUser: 'When empty, you will log in as default',
|
||||
containerTerminal: 'Terminal',
|
||||
|
@ -490,6 +495,8 @@ export default {
|
|||
registrieHelper: 'One in a row, for example:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||
|
||||
compose: 'Compose',
|
||||
apps: 'Apps',
|
||||
local: 'Local',
|
||||
createCompose: 'Create compose',
|
||||
composeTemplate: 'Compose template',
|
||||
createComposeTemplate: 'Create compose template',
|
||||
|
|
|
@ -295,6 +295,8 @@ export default {
|
|||
zipFormat: 'zip、tar.gz 压缩包结构:test.zip 或 test.tar.gz 压缩包内,必需包含 test.sql',
|
||||
|
||||
currentStatus: '当前状态',
|
||||
baseParam: '基础参数',
|
||||
performanceParam: '性能参数',
|
||||
runTime: '启动时间',
|
||||
connections: '总连接数',
|
||||
bytesSent: '发送',
|
||||
|
@ -344,6 +346,7 @@ export default {
|
|||
|
||||
log: '日志',
|
||||
slowLog: '慢日志',
|
||||
noData: '暂无慢日志...',
|
||||
|
||||
isOn: '是否开启',
|
||||
longQueryTime: '慢查询阈值',
|
||||
|
@ -417,6 +420,8 @@ export default {
|
|||
last10Min: '最近 10 分钟',
|
||||
newName: '新名称',
|
||||
|
||||
user: '用户',
|
||||
command: '命令',
|
||||
custom: '自定义',
|
||||
containerTerminal: '终端',
|
||||
emptyUser: '为空时,将使用容器默认的用户登录',
|
||||
|
@ -504,6 +509,8 @@ export default {
|
|||
registrieHelper: '一行一个,例:\n172.16.10.111:8081 \n172.16.10.112:8081',
|
||||
|
||||
compose: '编排',
|
||||
apps: '应用商店',
|
||||
local: '本地',
|
||||
createCompose: '创建编排',
|
||||
composeTemplate: '编排模版',
|
||||
createComposeTemplate: '创建编排模版',
|
||||
|
|
|
@ -5,6 +5,10 @@ export function formatImageStdout(stdout: string) {
|
|||
for (let i = 0; i < lines.length; i++) {
|
||||
if (isJson(lines[i])) {
|
||||
const data = JSON.parse(lines[i]);
|
||||
if (data.stream) {
|
||||
lines[i] = data.stream;
|
||||
continue;
|
||||
}
|
||||
if (data.id) {
|
||||
lines[i] = data.id + ': ' + data.status;
|
||||
} else {
|
||||
|
|
|
@ -32,13 +32,12 @@
|
|||
</el-card>
|
||||
</div>
|
||||
<LayoutContent
|
||||
v-loading="loading"
|
||||
style="margin-top: 30px"
|
||||
back-name="Compose"
|
||||
:title="$t('container.containerList')"
|
||||
:reload="true"
|
||||
>
|
||||
<template #toolbar>
|
||||
<template #main>
|
||||
<el-button-group>
|
||||
<el-button :disabled="checkStatus('start')" @click="onOperate('start')">
|
||||
{{ $t('container.start') }}
|
||||
|
@ -62,11 +61,10 @@
|
|||
{{ $t('container.remove') }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</template>
|
||||
<template #main>
|
||||
<ComplexTable
|
||||
:pagination-config="paginationConfig"
|
||||
v-model:selects="selects"
|
||||
style="margin-top: 20px"
|
||||
:data="data"
|
||||
@search="search"
|
||||
>
|
||||
|
|
|
@ -60,8 +60,9 @@
|
|||
</el-table-column>
|
||||
<el-table-column :label="$t('container.from')" prop="createdBy" min-width="80" fix>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.createdBy === ''">Local</span>
|
||||
<span v-else>{{ row.createdBy }}</span>
|
||||
<span v-if="row.createdBy === ''">{{ $t('container.local') }}</span>
|
||||
<span v-if="row.createdBy === 'Apps'">{{ $t('container.apps') }}</span>
|
||||
<span v-if="row.createdBy === '1Panel'">1Panel</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
@ -194,7 +195,7 @@ const buttons = [
|
|||
onEdit(row);
|
||||
},
|
||||
disabled: (row: any) => {
|
||||
return row.createdBy !== '1Panel';
|
||||
return row.createdBy === 'Local';
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -4,43 +4,42 @@
|
|||
<DrawerHeader :header="$t('container.containerTerminal')" :back="handleClose" />
|
||||
</template>
|
||||
<el-form ref="formRef" :model="form" label-position="top">
|
||||
<el-row :gutter="20" type="flex" justify="center">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="User" prop="user">
|
||||
<el-input clearable v-model="form.user" />
|
||||
<span class="input-help">{{ $t('container.emptyUser') }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item v-if="form.isCustom" label="Command" prop="command" :rules="Rules.requiredInput">
|
||||
<el-checkbox style="width: 100px" border v-model="form.isCustom" @change="onChangeCommand">
|
||||
{{ $t('container.custom') }}
|
||||
</el-checkbox>
|
||||
<el-input style="width: calc(100% - 100px)" clearable v-model="form.command" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!form.isCustom" label="Command" prop="command" :rules="Rules.requiredSelect">
|
||||
<el-checkbox style="width: 100px" border v-model="form.isCustom" @change="onChangeCommand">
|
||||
{{ $t('container.custom') }}
|
||||
</el-checkbox>
|
||||
<el-select style="width: calc(100% - 100px)" filterable clearable v-model="form.command">
|
||||
<el-option value="/bin/ash" label="/bin/ash" />
|
||||
<el-option value="/bin/bash" label="/bin/bash" />
|
||||
<el-option value="/bin/sh" label="/bin/sh" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item :label="$t('container.user')" prop="user">
|
||||
<el-input placeholder="root" clearable v-model="form.user" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.isCustom"
|
||||
:label="$t('container.command')"
|
||||
prop="command"
|
||||
:rules="Rules.requiredInput"
|
||||
>
|
||||
<el-checkbox style="width: 100px" border v-model="form.isCustom" @change="onChangeCommand">
|
||||
{{ $t('container.custom') }}
|
||||
</el-checkbox>
|
||||
<el-input style="width: calc(100% - 100px)" clearable v-model="form.command" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="!form.isCustom"
|
||||
:label="$t('container.command')"
|
||||
prop="command"
|
||||
:rules="Rules.requiredSelect"
|
||||
>
|
||||
<el-checkbox style="width: 100px" border v-model="form.isCustom" @change="onChangeCommand">
|
||||
{{ $t('container.custom') }}
|
||||
</el-checkbox>
|
||||
<el-select style="width: calc(100% - 100px)" filterable clearable v-model="form.command">
|
||||
<el-option value="/bin/ash" label="/bin/ash" />
|
||||
<el-option value="/bin/bash" label="/bin/bash" />
|
||||
<el-option value="/bin/sh" label="/bin/sh" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-button type="primary" v-if="!terminalOpen" @click="initTerm(formRef)">
|
||||
{{ $t('commons.button.conn') }}
|
||||
</el-button>
|
||||
<el-button type="primary" v-else @click="handleClose()">{{ $t('commons.button.disconn') }}</el-button>
|
||||
<div style="height: calc(100vh - 290px)" :id="'terminal-exec'"></div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="terminalVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" v-if="!terminalOpen" @click="initTerm(formRef)">
|
||||
{{ $t('commons.button.conn') }}
|
||||
</el-button>
|
||||
<el-button type="primary" v-else @click="handleClose()">{{ $t('commons.button.disconn') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<el-col :span="22">
|
||||
<el-form ref="formRef" label-position="top" :model="form" label-width="80px">
|
||||
<el-form-item :label="$t('container.tag')" :rules="Rules.requiredSelect" prop="tagName">
|
||||
<el-select filterable v-model="form.tagName">
|
||||
<el-select filterable v-model="form.tagName" @change="form.name = form.tagName">
|
||||
<el-option v-for="item in form.tags" :key="item" :value="item" :label="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
@ -27,7 +27,7 @@
|
|||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.label')" :rules="Rules.requiredInput" prop="name">
|
||||
<el-form-item :label="$t('container.image')" :rules="Rules.requiredInput" prop="name">
|
||||
<el-input v-model.trim="form.name">
|
||||
<template #prepend>{{ loadDetailInfo(form.repoID) }}/</template>
|
||||
</el-input>
|
||||
|
@ -114,8 +114,8 @@ const acceptParams = async (params: DialogProps): Promise<void> => {
|
|||
drawerVisiable.value = true;
|
||||
form.tags = params.tags;
|
||||
form.repoID = 1;
|
||||
form.tagName = '';
|
||||
form.name = '';
|
||||
form.tagName = form.tags.length !== 0 ? form.tags[0] : '';
|
||||
form.name = form.tags.length !== 0 ? form.tags[0] : '';
|
||||
dialogData.value.repos = params.repos;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
<template>
|
||||
<el-dialog v-model="repoVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="30%">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('commons.button.delete') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form v-loading="loading" label-width="20px">
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="isDelete">{{ $t('container.delInsecure') }}</el-checkbox>
|
||||
<span class="input-help">{{ $t('container.delInsecureHelper') }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="repoVisiable = false">
|
||||
{{ $t('commons.button.cancel') }}
|
||||
</el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="onSubmit()">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm } from 'element-plus';
|
||||
import { deleteImageRepo } from '@/api/modules/container';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
|
||||
const loading = ref(false);
|
||||
const isDelete = ref(false);
|
||||
|
||||
interface DialogProps {
|
||||
ids?: Array<number>;
|
||||
}
|
||||
const repoVisiable = ref(false);
|
||||
const ids = ref();
|
||||
|
||||
const acceptParams = (params: DialogProps): void => {
|
||||
ids.value = params.ids;
|
||||
repoVisiable.value = true;
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
||||
const onSubmit = async () => {
|
||||
loading.value = true;
|
||||
await deleteImageRepo({ ids: ids.value, deleteInsecure: isDelete.value })
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
emit('search');
|
||||
repoVisiable.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
|
@ -71,7 +71,6 @@
|
|||
</template>
|
||||
</LayoutContent>
|
||||
<OperatorDialog @search="search" ref="dialogRef" />
|
||||
<DeleteDialog @search="search" ref="dialogDeleteRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -80,7 +79,6 @@ import LayoutContent from '@/layout/layout-content.vue';
|
|||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import TableSetting from '@/components/table-setting/index.vue';
|
||||
import OperatorDialog from '@/views/container/repo/operator/index.vue';
|
||||
import DeleteDialog from '@/views/container/repo/delete/index.vue';
|
||||
import { reactive, onMounted, ref } from 'vue';
|
||||
import { dateFormat } from '@/utils/util';
|
||||
import { Container } from '@/api/interface/container';
|
||||
|
@ -148,19 +146,14 @@ const onOpenDialog = async (
|
|||
dialogRef.value!.acceptParams(params);
|
||||
};
|
||||
|
||||
const dialogDeleteRef = ref();
|
||||
const onDelete = async (row: Container.RepoInfo) => {
|
||||
if (row.protocol === 'https') {
|
||||
ElMessageBox.confirm(i18n.global.t('commons.msg.delete'), i18n.global.t('commons.button.delete'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
await deleteImageRepo({ ids: [row.id], deleteInsecure: false });
|
||||
search();
|
||||
});
|
||||
return;
|
||||
}
|
||||
dialogDeleteRef.value!.acceptParams({ ids: [row.id] });
|
||||
ElMessageBox.confirm(i18n.global.t('commons.msg.delete'), i18n.global.t('commons.button.delete'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
}).then(async () => {
|
||||
await deleteImageRepo({ ids: [row.id] });
|
||||
search();
|
||||
});
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-loading="loading">
|
||||
<div class="a-card" style="margin-top: 20px">
|
||||
<el-card>
|
||||
<div>
|
||||
|
@ -35,7 +35,7 @@
|
|||
</el-card>
|
||||
</div>
|
||||
|
||||
<LayoutContent v-loading="loading" style="margin-top: 20px" :title="$t('container.setting')" :divider="true">
|
||||
<LayoutContent style="margin-top: 20px" :title="$t('container.setting')" :divider="true">
|
||||
<template #main>
|
||||
<el-radio-group v-model="confShowType" @change="changeMode">
|
||||
<el-radio-button label="base">{{ $t('database.baseConf') }}</el-radio-button>
|
||||
|
@ -169,10 +169,17 @@ const onOperator = async (operation: string) => {
|
|||
let param = {
|
||||
operation: operation,
|
||||
};
|
||||
await dockerOperate(param);
|
||||
search();
|
||||
changeMode();
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
loading.value = true;
|
||||
await dockerOperate(param)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
search();
|
||||
changeMode();
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmitSave = async () => {
|
||||
|
|
|
@ -43,15 +43,22 @@
|
|||
@search="search"
|
||||
>
|
||||
<el-table-column type="selection" fix />
|
||||
<el-table-column
|
||||
:label="$t('commons.table.name')"
|
||||
show-overflow-tooltip
|
||||
min-width="80"
|
||||
prop="name"
|
||||
fix
|
||||
>
|
||||
<el-table-column :label="$t('commons.table.name')" min-width="80" prop="name" fix>
|
||||
<template #default="{ row }">
|
||||
<el-link @click="onInspect(row.name)" type="primary">{{ row.name }}</el-link>
|
||||
<el-tooltip
|
||||
v-if="row.name.length > 20"
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
:content="row.name"
|
||||
placement="top"
|
||||
>
|
||||
<el-link @click="onInspect(row.name)" type="primary">
|
||||
{{ row.name.substring(0, 20) }}...
|
||||
</el-link>
|
||||
</el-tooltip>
|
||||
<el-link v-else @click="onInspect(row.name)" type="primary">
|
||||
{{ row.name }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
|
|
@ -135,7 +135,7 @@
|
|||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<PasswordDialog ref="passwordRef" />
|
||||
<PasswordDialog ref="passwordRef" @search="search" />
|
||||
<RootPasswordDialog ref="rootPasswordRef" />
|
||||
<RemoteAccessDialog ref="remoteAccessRef" />
|
||||
<UploadDialog ref="uploadRef" />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-show="onSetting">
|
||||
<LayoutContent :title="'MySQL ' + $t('database.setting')" :reload="true" v-loading="loading">
|
||||
<div v-show="onSetting" v-loading="loading">
|
||||
<LayoutContent :title="'MySQL ' + $t('database.setting')" :reload="true">
|
||||
<template #buttons>
|
||||
<el-button type="primary" :plain="activeName !== 'conf'" @click="activeName = 'conf'">
|
||||
{{ $t('database.confChange') }}
|
||||
|
@ -78,18 +78,17 @@
|
|||
<Status v-show="activeName === 'status'" ref="statusRef" />
|
||||
<Variables v-show="activeName === 'tuning'" ref="variablesRef" />
|
||||
<div v-show="activeName === 'port'">
|
||||
<el-form :model="baseInfo" ref="panelFormRef" label-width="120px">
|
||||
<el-form :model="baseInfo" ref="panelFormRef" label-position="top">
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="$t('setting.port')" prop="port" :rules="Rules.port">
|
||||
<el-input clearable type="number" v-model.number="baseInfo.port">
|
||||
<template #append>
|
||||
<el-button @click="onSavePort(panelFormRef)" icon="Collection">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input clearable type="number" v-model.number="baseInfo.port" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSavePort(panelFormRef)" icon="Collection">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
</el-button>
|
||||
<codemirror
|
||||
:autofocus="true"
|
||||
placeholder="None data"
|
||||
:placeholder="$t('database.noData')"
|
||||
:indent-with-tab="true"
|
||||
:tabSize="4"
|
||||
style="margin-top: 10px; height: calc(100vh - 392px)"
|
||||
|
|
|
@ -1,108 +1,143 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-row style="margin-top: 20px">
|
||||
<table style="width: 100%" class="myTable">
|
||||
<tr>
|
||||
<td>{{ $t('database.runTime') }}</td>
|
||||
<td>{{ mysqlStatus.run }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.connections') }}</td>
|
||||
<td>{{ mysqlStatus.connections }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.bytesSent') }}</td>
|
||||
<td>{{ mysqlStatus!.bytesSent }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.bytesReceived') }}</td>
|
||||
<td>{{ mysqlStatus!.bytesReceived }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</el-row>
|
||||
<el-row style="margin-top: 20px">
|
||||
<table style="width: 100%" class="myTable">
|
||||
<tr>
|
||||
<td>{{ $t('database.queryPerSecond') }}</td>
|
||||
<td>{{ mysqlStatus!.queryPerSecond }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.queryPerSecond') }}</td>
|
||||
<td>{{ mysqlStatus!.txPerSecond }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>File</td>
|
||||
<td>{{ mysqlStatus!.file }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Position</td>
|
||||
<td>{{ mysqlStatus!.position }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<table style="margin-top: 20px; width: 100%" class="myTable">
|
||||
<tr>
|
||||
<td>{{ $t('database.queryPerSecond') }}</td>
|
||||
<td>{{ mysqlStatus!.connInfo }}</td>
|
||||
<td>{{ $t('database.connInfoHelper') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.threadCacheHit') }}</td>
|
||||
<td>{{ mysqlStatus!.threadCacheHit }}</td>
|
||||
<td>{{ $t('database.threadCacheHitHelper') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.indexHit') }}</td>
|
||||
<td>{{ mysqlStatus!.indexHit }}</td>
|
||||
<td>{{ $t('database.indexHitHelper') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.innodbIndexHit') }}</td>
|
||||
<td>{{ mysqlStatus!.innodbIndexHit }}</td>
|
||||
<td>{{ $t('database.innodbIndexHitHelper') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.cacheHit') }}</td>
|
||||
<td>{{ mysqlStatus!.cacheHit }}</td>
|
||||
<td>{{ $t('database.cacheHitHelper') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.tmpTableToDB') }}</td>
|
||||
<td>{{ mysqlStatus!.tmpTableToDB }}</td>
|
||||
<td>{{ $t('database.tmpTableToDBHelper') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.openTables') }}</td>
|
||||
<td>{{ mysqlStatus!.openTables }}</td>
|
||||
<td>{{ $t('database.openTablesHelper') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.selectFullJoin') }}</td>
|
||||
<td>{{ mysqlStatus!.selectFullJoin }}</td>
|
||||
<td>{{ $t('database.selectFullJoinHelper') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.selectRangeCheck') }}</td>
|
||||
<td>{{ mysqlStatus!.selectRangeCheck }}</td>
|
||||
<td>{{ $t('database.selectRangeCheckHelper') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.sortMergePasses') }}</td>
|
||||
<td>{{ mysqlStatus!.sortMergePasses }}</td>
|
||||
<td>{{ $t('database.sortMergePassesHelper') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('database.tableLocksWaited') }}</td>
|
||||
<td>{{ mysqlStatus!.tableLocksWaited }}</td>
|
||||
<td>{{ $t('database.tableLocksWaitedHelper') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<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">{{ $t('database.runTime') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.run }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.connections') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.connections }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.bytesSent') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.bytesSent }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.bytesReceived') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.bytesReceived }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.queryPerSecond') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.queryPerSecond }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.txPerSecond') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.txPerSecond }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">File</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.file }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">Position</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.position }}</span>
|
||||
</el-form-item>
|
||||
</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">{{ $t('database.queryPerSecond') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.queryPerSecond }}</span>
|
||||
<span class="input-help">{{ $t('database.connInfoHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.threadCacheHit') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.threadCacheHit }}</span>
|
||||
<span class="input-help">{{ $t('database.threadCacheHitHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.indexHit') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.indexHit }}</span>
|
||||
<span class="input-help">{{ $t('database.indexHitHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.innodbIndexHit') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.innodbIndexHit }}</span>
|
||||
<span class="input-help">{{ $t('database.innodbIndexHitHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.cacheHit') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.cacheHit }}</span>
|
||||
<span class="input-help">{{ $t('database.cacheHitHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.tmpTableToDB') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.tmpTableToDB }}</span>
|
||||
<span class="input-help">{{ $t('database.tmpTableToDBHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.openTables') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.openTables }}</span>
|
||||
<span class="input-help">{{ $t('database.openTablesHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.selectFullJoin') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.selectFullJoin }}</span>
|
||||
<span class="input-help">{{ $t('database.selectFullJoinHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.selectRangeCheck') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.selectRangeCheck }}</span>
|
||||
<span class="input-help">{{ $t('database.selectRangeCheckHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.sortMergePasses') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.sortMergePasses }}</span>
|
||||
<span class="input-help">{{ $t('database.sortMergePassesHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%">
|
||||
<template #label>
|
||||
<span class="status-label">{{ $t('database.tableLocksWaited') }}</span>
|
||||
</template>
|
||||
<span class="status-count">{{ mysqlStatus.tableLocksWaited }}</span>
|
||||
<span class="input-help">{{ $t('database.tableLocksWaitedHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item style="width: 25%"></el-form-item>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
|
@ -176,3 +211,25 @@ defineExpose({
|
|||
acceptParams,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.status-count {
|
||||
font-size: 24px;
|
||||
}
|
||||
.status-label {
|
||||
font-size: 14px;
|
||||
color: #646a73;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -94,18 +94,17 @@
|
|||
</el-form>
|
||||
</div>
|
||||
<div v-if="activeName === 'port'">
|
||||
<el-form :model="form" ref="portRef" label-width="120px">
|
||||
<el-form :model="form" ref="portRef" label-position="top">
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="$t('setting.port')" prop="port" :rules="Rules.port">
|
||||
<el-input clearable type="number" v-model.number="form.port">
|
||||
<template #append>
|
||||
<el-button @click="onSavePort(portRef)" icon="Collection">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input clearable type="number" v-model.number="form.port" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="onSavePort(portRef)" icon="Collection">
|
||||
{{ $t('commons.button.save') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
|
|
@ -49,13 +49,13 @@
|
|||
<el-input type="number" v-model="row.second"></el-input>
|
||||
</td>
|
||||
<td width="60px">
|
||||
<span>{{ $t('database.rdbHelper1') }}</span>
|
||||
{{ $t('database.rdbHelper1') }}
|
||||
</td>
|
||||
<td width="32%">
|
||||
<el-input type="number" v-model="row.count"></el-input>
|
||||
</td>
|
||||
<td width="12%">
|
||||
<span>{{ $t('database.rdbHelper2') }}</span>
|
||||
{{ $t('database.rdbHelper2') }}
|
||||
</td>
|
||||
<td>
|
||||
<el-button link type="primary" style="font-size: 10px" @click="handleDelete(index)">
|
||||
|
|
|
@ -1,77 +1,110 @@
|
|||
<template>
|
||||
<div v-if="statusShow">
|
||||
<el-row>
|
||||
<el-col :span="1"><br /></el-col>
|
||||
<el-col :span="12">
|
||||
<table style="width: 100%" class="myTable">
|
||||
<tr>
|
||||
<td>uptime_in_days</td>
|
||||
<td>{{ redisStatus!.uptime_in_days }}</td>
|
||||
<td>{{ $t('database.uptimeInDays') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>tcp_port</td>
|
||||
<td>{{ redisStatus!.tcp_port }}</td>
|
||||
<td>{{ $t('database.tcpPort') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>connected_clients</td>
|
||||
<td>{{ redisStatus!.connected_clients }}</td>
|
||||
<td>{{ $t('database.connectedClients') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>used_memory_rss</td>
|
||||
<td>{{ redisStatus!.used_memory_rss }}</td>
|
||||
<td>{{ $t('database.usedMemoryRss') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>used_memory</td>
|
||||
<td>{{ redisStatus!.used_memory }}</td>
|
||||
<td>{{ $t('database.usedMemory') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mem_fragmentation_ratio</td>
|
||||
<td>{{ redisStatus!.mem_fragmentation_ratio }}</td>
|
||||
<td>{{ $t('database.tmpTableToDBHelper') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>total_connections_received</td>
|
||||
<td>{{ redisStatus!.total_connections_received }}</td>
|
||||
<td>{{ $t('database.totalConnectionsReceived') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>total_commands_processed</td>
|
||||
<td>{{ redisStatus!.total_commands_processed }}</td>
|
||||
<td>{{ $t('database.totalCommandsProcessed') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>instantaneous_ops_per_sec</td>
|
||||
<td>{{ redisStatus!.instantaneous_ops_per_sec }}</td>
|
||||
<td>{{ $t('database.instantaneousOpsPerSec') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>keyspace_hits</td>
|
||||
<td>{{ redisStatus!.keyspace_hits }}</td>
|
||||
<td>{{ $t('database.keyspaceHits') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>keyspace_misses</td>
|
||||
<td>{{ redisStatus!.keyspace_misses }}</td>
|
||||
<td>{{ $t('database.keyspaceMisses') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>hit</td>
|
||||
<td>{{ redisStatus!.hit }}</td>
|
||||
<td>{{ $t('database.hit') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>latest_fork_usec</td>
|
||||
<td>{{ redisStatus!.latest_fork_usec }}</td>
|
||||
<td>{{ $t('database.latestForkUsec') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -138,3 +171,25 @@ defineExpose({
|
|||
onClose,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.status-count {
|
||||
font-size: 24px;
|
||||
}
|
||||
.status-label {
|
||||
font-size: 14px;
|
||||
color: #646a73;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue