779 lines
22 KiB
Vue
779 lines
22 KiB
Vue
<template>
|
|
<Header class="sticky top-0 z-10 bg-white">
|
|
<div
|
|
class="flex w-full flex-col gap-2 md:flex-row md:items-center md:justify-between"
|
|
>
|
|
<div class="flex flex-row items-center gap-2">
|
|
<!-- 标题 -->
|
|
<Breadcrumbs
|
|
:items="[
|
|
{ label: '开发工具', route: '/database-analyzer' },
|
|
{ label: '数据库分析器', route: '/database-analyzer' },
|
|
]"
|
|
/>
|
|
</div>
|
|
<div class="flex flex-row gap-2">
|
|
<LinkControl
|
|
class="cursor-pointer"
|
|
:options="{ pagetype: 'Site', filters: { status: 'Active' } }"
|
|
placeholder="选择一个站点"
|
|
v-model="site"
|
|
/>
|
|
<Button
|
|
iconLeft="refresh-ccw"
|
|
variant="subtle"
|
|
:loading="site && !isRequiredInformationReceived"
|
|
:disabled="!site"
|
|
@click="
|
|
() =>
|
|
fetchTableSchemas({
|
|
reload: true,
|
|
})
|
|
"
|
|
>
|
|
<span class="md:hidden">架构</span>
|
|
<span class="hidden md:inline">刷新架构</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Header>
|
|
<div class="m-5">
|
|
<!-- 主体 -->
|
|
<div class="mt-2 flex flex-col" v-if="isRequiredInformationReceived">
|
|
<!-- 数据库大小分析器 -->
|
|
<div>
|
|
<div class="flex flex-row items-center justify-between">
|
|
<p class="text-base font-medium text-gray-800">
|
|
数据库大小分解
|
|
</p>
|
|
<div class="flex flex-row gap-2">
|
|
<Button @click="this.showTableSchemaSizeDetailsDialog = true">
|
|
查看详情
|
|
</Button>
|
|
<Button
|
|
@click="optimizeTable"
|
|
:loading="this.$resources.optimizeTable.loading"
|
|
>
|
|
优化表
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 滑块 -->
|
|
<div
|
|
class="mb-4 mt-4 flex h-7 w-full cursor-pointer items-start justify-start overflow-clip rounded border bg-gray-50 pl-0"
|
|
@click="showTableSchemaSizeDetailsDialog = true"
|
|
>
|
|
<div
|
|
class="h-7"
|
|
:style="{
|
|
backgroundColor: '#E86C13',
|
|
width: `${databaseSizeBreakup.data_size_percentage}%`,
|
|
}"
|
|
></div>
|
|
<div
|
|
class="h-7"
|
|
:style="{
|
|
backgroundColor: '#34BAE3',
|
|
width: `${databaseSizeBreakup.index_size_percentage}%`,
|
|
}"
|
|
></div>
|
|
</div>
|
|
<!-- 表格 -->
|
|
<div
|
|
class="full flex w-full flex-col items-start justify-start overflow-y-auto rounded px-1.5"
|
|
>
|
|
<div class="flex w-full items-center justify-start gap-x-2 py-3">
|
|
<div
|
|
class="h-2 w-2 rounded-full"
|
|
style="background-color: #e86c13"
|
|
></div>
|
|
<span class="text-sm text-gray-800">数据大小</span
|
|
><span class="ml-auto text-sm text-gray-800">{{
|
|
formatSizeInMB(this.databaseSizeBreakup.data_size)
|
|
}}</span>
|
|
</div>
|
|
<div
|
|
class="flex w-full items-center justify-start gap-x-2 border-t py-3"
|
|
>
|
|
<div
|
|
class="h-2 w-2 rounded-full"
|
|
style="background-color: #34bae3"
|
|
></div>
|
|
<span class="text-sm text-gray-800">索引大小</span
|
|
><span class="ml-auto text-sm text-gray-800"
|
|
>{{ formatSizeInMB(this.databaseSizeBreakup.index_size) }}
|
|
</span>
|
|
</div>
|
|
<div
|
|
class="flex w-full items-center justify-start gap-x-2 border-t py-3"
|
|
>
|
|
<div
|
|
class="h-2 w-2 rounded-full"
|
|
style="background-color: #e2e2e2"
|
|
></div>
|
|
<span class="text-sm text-gray-800">空闲空间</span
|
|
><span class="ml-auto text-sm text-gray-800"
|
|
>{{ formatSizeInMB(this.databaseSizeBreakup.free_size) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 数据库进程 -->
|
|
<ToggleContent
|
|
class="mt-3"
|
|
label="数据库进程"
|
|
subLabel="分析数据库的进程"
|
|
>
|
|
<template #actions>
|
|
<div>
|
|
<Button
|
|
:loading="this.$resources.databaseProcesses.loading"
|
|
loading-text="刷新中"
|
|
icon-left="rotate-ccw"
|
|
@click.stop="this.$resources.databaseProcesses.submit()"
|
|
>刷新</Button
|
|
>
|
|
</div>
|
|
</template>
|
|
<template #default>
|
|
<div
|
|
v-if="this.$resources.databaseProcesses.loading"
|
|
class="flex h-60 w-full items-center justify-center gap-2 text-base text-gray-700"
|
|
>
|
|
<Spinner class="w-4" /> 正在加载数据库进程
|
|
</div>
|
|
<ResultTable
|
|
v-else
|
|
class="mt-2"
|
|
:columns="databaseProcesses.columns"
|
|
:data="databaseProcesses.data"
|
|
:alignColumns="alignColumns"
|
|
:cellFormatters="cellFormatters"
|
|
:fullViewFormatters="fullViewFormatters"
|
|
actionHeaderLabel="终止"
|
|
:actionComponent="DatabaseProcessKillButton"
|
|
:actionComponentProps="{
|
|
site: this.site,
|
|
}"
|
|
:enableCSVExport="false"
|
|
:borderLess="true"
|
|
/>
|
|
</template>
|
|
</ToggleContent>
|
|
|
|
<!-- 查询信息 -->
|
|
<ToggleContent
|
|
class="mt-3"
|
|
label="SQL 查询分析"
|
|
subLabel="检查可能影响数据库性能的相关查询"
|
|
>
|
|
<div class="mt-1">
|
|
<FTabs
|
|
:tabs="queryTabs"
|
|
v-model="queryTabIndex"
|
|
v-if="queryTabs.length"
|
|
>
|
|
<template #tab-panel="{ tab }">
|
|
<DatabasePerformanceSchemaDisabledNotice
|
|
v-if="
|
|
(tab.label === '耗时查询' ||
|
|
tab.label === '全表扫描查询') &&
|
|
!isPerformanceSchemaEnabled
|
|
"
|
|
/>
|
|
<ResultTable
|
|
v-else
|
|
:columns="tab.columns"
|
|
:data="tab.data"
|
|
:alignColumns="alignColumns"
|
|
:cellFormatters="cellFormatters"
|
|
:fullViewFormatters="fullViewFormatters"
|
|
:enableCSVExport="false"
|
|
:borderLess="true"
|
|
:isTruncateText="true"
|
|
/>
|
|
</template>
|
|
</FTabs>
|
|
</div>
|
|
</ToggleContent>
|
|
|
|
<!-- 索引信息 -->
|
|
<ToggleContent
|
|
class="mt-3"
|
|
label="数据库索引分析"
|
|
subLabel="分析数据库的索引"
|
|
>
|
|
<div class="mt-1">
|
|
<FTabs
|
|
:tabs="databaseIndexesTab"
|
|
v-model="dbIndexTabIndex"
|
|
v-if="databaseIndexesTab.length"
|
|
>
|
|
<template #tab-panel="{ tab }">
|
|
<DatabasePerformanceSchemaDisabledNotice
|
|
v-if="
|
|
(tab.label === '未使用索引' ||
|
|
tab.label === '建议索引') &&
|
|
!isPerformanceSchemaEnabled
|
|
"
|
|
/>
|
|
<div v-else-if="tab.label === '建议索引'">
|
|
<div
|
|
v-if="
|
|
!isIndexSuggestionTriggered ||
|
|
this.$resources.suggestDatabaseIndexes.loading ||
|
|
this.fetchingDatabaseIndex
|
|
"
|
|
class="flex h-60 flex-col items-center justify-center gap-4"
|
|
>
|
|
<Button
|
|
variant="outline"
|
|
@click="
|
|
() => {
|
|
this.isIndexSuggestionTriggered = true;
|
|
this.$resources.suggestDatabaseIndexes.submit();
|
|
}
|
|
"
|
|
:loading="
|
|
this.$resources.suggestDatabaseIndexes.loading ||
|
|
this.fetchingDatabaseIndex
|
|
"
|
|
>建议索引</Button
|
|
>
|
|
<p class="text-base text-gray-700">
|
|
分析可能需要一些时间
|
|
</p>
|
|
</div>
|
|
<ResultTable
|
|
v-else
|
|
:columns="suggestedDatabaseIndexes.columns"
|
|
:data="suggestedDatabaseIndexes.data"
|
|
:alignColumns="alignColumns"
|
|
:cellFormatters="cellFormatters"
|
|
:fullViewFormatters="fullViewFormatters"
|
|
:enableCSVExport="false"
|
|
:borderLess="true"
|
|
actionHeaderLabel="添加索引"
|
|
:actionComponent="DatabaseAddIndexButton"
|
|
:actionComponentProps="{
|
|
site: this.site,
|
|
}"
|
|
:isTruncateText="true"
|
|
/>
|
|
</div>
|
|
<ResultTable
|
|
v-else
|
|
:columns="tab.columns"
|
|
:data="tab.data"
|
|
:alignColumns="alignColumns"
|
|
:cellFormatters="cellFormatters"
|
|
:fullViewFormatters="fullViewFormatters"
|
|
:isTruncateText="true"
|
|
:enableCSVExport="false"
|
|
:borderLess="true"
|
|
/>
|
|
</template>
|
|
</FTabs>
|
|
</div>
|
|
</ToggleContent>
|
|
|
|
<DatabaseTableSchemaSizeDetailsDialog
|
|
v-if="this.site"
|
|
:site="this.site"
|
|
:tableSchemas="tableSchemas"
|
|
v-model="showTableSchemaSizeDetailsDialog"
|
|
:viewSchemaDetails="viewTableSchemaDetails"
|
|
/>
|
|
|
|
<DatabaseTableSchemaDialog
|
|
v-if="this.site"
|
|
:site="this.site"
|
|
:tableSchemas="tableSchemas"
|
|
:pre-selected-schema="preSelectedSchemaForSchemaDialog"
|
|
v-model="showTableSchemasDialog"
|
|
/>
|
|
</div>
|
|
<div
|
|
v-else-if="!site"
|
|
class="flex h-full min-h-[80vh] w-full items-center justify-center gap-2 text-gray-700"
|
|
>
|
|
选择一个站点以开始
|
|
</div>
|
|
<div
|
|
class="flex h-full min-h-[80vh] w-full items-center justify-center gap-2 text-gray-700"
|
|
v-else
|
|
>
|
|
<Spinner class="w-4" /> 正在加载表结构
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
import Header from '../../../components/Header.vue';
|
|
import { Tabs, Breadcrumbs } from 'jingrow-ui';
|
|
import LinkControl from '../../../components/LinkControl.vue';
|
|
import ObjectList from '../../../components/ObjectList.vue';
|
|
import { h, markRaw } from 'vue';
|
|
import { toast } from 'vue-sonner';
|
|
import { formatValue } from '../../../utils/format';
|
|
import ToggleContent from '../../../components/ToggleContent.vue';
|
|
import ResultTable from '../../../components/devtools/database/ResultTable.vue';
|
|
import DatabaseProcessKillButton from '../../../components/devtools/database/DatabaseProcessKillButton.vue';
|
|
import DatabaseTableSchemaDialog from '../../../components/devtools/database/DatabaseTableSchemaDialog.vue';
|
|
import DatabaseTableSchemaSizeDetailsDialog from '../../../components/devtools/database/DatabaseTableSchemaSizeDetailsDialog.vue';
|
|
import DatabaseAddIndexButton from '../../../components/devtools/database/DatabaseAddIndexButton.vue';
|
|
import DatabasePerformanceSchemaDisabledNotice from '../../../components/devtools/database/DatabasePerformanceSchemaDisabledNotice.vue';
|
|
|
|
export default {
|
|
name: 'DatabaseAnalyzer',
|
|
components: {
|
|
Header,
|
|
Breadcrumbs,
|
|
FTabs: Tabs,
|
|
LinkControl,
|
|
ObjectList,
|
|
ToggleContent,
|
|
ResultTable,
|
|
DatabaseTableSchemaDialog,
|
|
DatabaseTableSchemaSizeDetailsDialog,
|
|
DatabaseProcessKillButton,
|
|
DatabasePerformanceSchemaDisabledNotice,
|
|
},
|
|
data() {
|
|
return {
|
|
site: null,
|
|
errorMessage: null,
|
|
isIndexSuggestionTriggered: false,
|
|
queryTabIndex: 0,
|
|
dbIndexTabIndex: 0,
|
|
showTableSchemaSizeDetailsDialog: false,
|
|
preSelectedSchemaForSchemaDialog: null,
|
|
showTableSchemasDialog: false,
|
|
fetchingDatabaseIndex: false,
|
|
DatabaseProcessKillButton: markRaw(DatabaseProcessKillButton),
|
|
DatabaseAddIndexButton: markRaw(DatabaseAddIndexButton),
|
|
};
|
|
},
|
|
mounted() {
|
|
const url = new URL(window.location.href);
|
|
const site_name = url.searchParams.get('site');
|
|
if (site_name) {
|
|
this.site = site_name;
|
|
}
|
|
},
|
|
watch: {
|
|
site(site_name) {
|
|
if (!site_name) return;
|
|
// set site to query param ?site=site_name
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.set('site', site_name);
|
|
window.history.pushState({}, '', url);
|
|
|
|
// reset state
|
|
this.data = null;
|
|
this.errorMessage = null;
|
|
this.fetchTableSchemas({
|
|
site_name: site_name,
|
|
});
|
|
this.$resources.site.submit();
|
|
this.$resources.databasePerformanceReport.submit({
|
|
dt: 'Site',
|
|
dn: site_name,
|
|
method: 'get_database_performance_report',
|
|
});
|
|
this.$resources.databaseProcesses.submit({
|
|
dt: 'Site',
|
|
dn: site_name,
|
|
method: 'fetch_database_processes',
|
|
});
|
|
},
|
|
},
|
|
resources: {
|
|
site() {
|
|
return {
|
|
url: 'jcloud.api.client.get',
|
|
initialData: {},
|
|
makeParams: () => {
|
|
return { pagetype: 'Site', name: this.site };
|
|
},
|
|
auto: false,
|
|
};
|
|
},
|
|
tableSchemas() {
|
|
return {
|
|
url: 'jcloud.api.client.run_pg_method',
|
|
initialData: {},
|
|
auto: false,
|
|
makeParams: () => {
|
|
return {
|
|
dt: 'Site',
|
|
dn: this.site,
|
|
method: 'fetch_database_table_schema',
|
|
};
|
|
},
|
|
onSuccess: (data) => {
|
|
if (data?.message?.loading) {
|
|
setTimeout(this.fetchTableSchemas, 5000);
|
|
}
|
|
},
|
|
};
|
|
},
|
|
optimizeTable() {
|
|
return {
|
|
url: 'jcloud.api.client.run_pg_method',
|
|
initialData: {},
|
|
auto: false,
|
|
onSuccess: (data) => {
|
|
if (data?.message) {
|
|
if (data?.message?.success) {
|
|
toast.success(data?.message?.message);
|
|
this.$router.push(
|
|
`/sites/${this.site}/insights/jobs/${data?.message?.job_name}`,
|
|
);
|
|
} else {
|
|
toast.error(data?.message?.message);
|
|
}
|
|
}
|
|
},
|
|
};
|
|
},
|
|
databasePerformanceReport() {
|
|
return {
|
|
url: 'jcloud.api.client.run_pg_method',
|
|
initialData: {},
|
|
makeParams: () => {
|
|
return {
|
|
dt: 'Site',
|
|
dn: this.site,
|
|
method: 'get_database_performance_report',
|
|
};
|
|
},
|
|
auto: false,
|
|
};
|
|
},
|
|
suggestDatabaseIndexes() {
|
|
return {
|
|
url: 'jcloud.api.client.run_pg_method',
|
|
initialData: {},
|
|
makeParams: () => {
|
|
return {
|
|
dt: 'Site',
|
|
dn: this.site,
|
|
method: 'suggest_database_indexes',
|
|
};
|
|
},
|
|
onSuccess: (data) => {
|
|
if (data?.message) {
|
|
this.fetchingDatabaseIndex =
|
|
this.$resources.suggestDatabaseIndexes?.data?.message?.loading ??
|
|
false;
|
|
if (this.fetchingDatabaseIndex) {
|
|
setTimeout(() => {
|
|
this.$resources.suggestDatabaseIndexes.submit();
|
|
}, 5000);
|
|
}
|
|
}
|
|
},
|
|
auto: false,
|
|
};
|
|
},
|
|
databaseProcesses() {
|
|
return {
|
|
url: 'jcloud.api.client.run_pg_method',
|
|
initialData: {},
|
|
makeParams: () => {
|
|
return {
|
|
dt: 'Site',
|
|
dn: this.site,
|
|
method: 'fetch_database_processes',
|
|
};
|
|
},
|
|
auto: false,
|
|
};
|
|
},
|
|
},
|
|
computed: {
|
|
site_info() {
|
|
return this.$resources.site.data;
|
|
},
|
|
isRequiredInformationReceived() {
|
|
if (this.$resources.site?.loading ?? true) return false;
|
|
if (this.$resources.tableSchemas.loading) return false;
|
|
if (this.$resources.tableSchemas?.data?.message?.loading) return false;
|
|
if (!this.$resources.tableSchemas?.data?.message?.data) return false;
|
|
if (this.$resources.tableSchemas?.data?.message?.data == {}) return false;
|
|
if (!this.$resources.databasePerformanceReport?.data?.message)
|
|
return false;
|
|
return true;
|
|
},
|
|
tableSchemas() {
|
|
if (!this.isRequiredInformationReceived) return [];
|
|
let result = this.$resources.tableSchemas?.data?.message?.data ?? [];
|
|
return result;
|
|
},
|
|
tableSizeInfo() {
|
|
if (!this.isRequiredInformationReceived) return [];
|
|
let data = [];
|
|
for (const tableName in this.tableSchemas) {
|
|
const table = this.tableSchemas[tableName];
|
|
data.push({
|
|
table_name: tableName,
|
|
data_size_mb: (table.size.data_length / (1024 * 1024)).toFixed(3),
|
|
index_size_mb: (table.size.index_length / (1024 * 1024)).toFixed(3),
|
|
total_size_mb: (table.size.total_size / (1024 * 1024)).toFixed(3),
|
|
no_of_columns: table.columns.length,
|
|
});
|
|
}
|
|
return data;
|
|
},
|
|
tableAnalysisTableOptions() {
|
|
if (!this.isRequiredInformationReceived) return [];
|
|
return {
|
|
data: () => this.tableSizeInfo,
|
|
hideControls: true,
|
|
columns: [
|
|
{
|
|
label: '表名',
|
|
fieldname: 'table_name',
|
|
width: 0.5,
|
|
type: 'Component',
|
|
component({ row }) {
|
|
return h(
|
|
'div',
|
|
{
|
|
class: 'truncate text-base cursor-copy',
|
|
onClick() {
|
|
if ('clipboard' in navigator) {
|
|
navigator.clipboard.writeText(row.table_name);
|
|
toast.success('已复制到剪贴板');
|
|
}
|
|
},
|
|
},
|
|
[row.table_name],
|
|
);
|
|
},
|
|
},
|
|
{
|
|
label: '大小 (MB)',
|
|
fieldname: 'total_size_mb',
|
|
width: 0.5,
|
|
},
|
|
{
|
|
label: '列数',
|
|
fieldname: 'no_of_columns',
|
|
width: 0.5,
|
|
},
|
|
],
|
|
};
|
|
},
|
|
databaseSizeBreakup() {
|
|
if (!this.isRequiredInformationReceived) return null;
|
|
let data_size = this.tableSizeInfo.reduce(
|
|
(a, b) => a + parseFloat(b.data_size_mb),
|
|
0,
|
|
);
|
|
data_size = data_size.toFixed(2);
|
|
|
|
let index_size = this.tableSizeInfo.reduce(
|
|
(a, b) => a + parseFloat(b.index_size_mb),
|
|
0,
|
|
);
|
|
index_size = index_size.toFixed(2);
|
|
|
|
let database_size_limit =
|
|
this.site_info.current_plan.max_database_usage.toFixed(2);
|
|
|
|
return {
|
|
data_size,
|
|
index_size,
|
|
database_size_limit,
|
|
free_size: (database_size_limit - data_size - index_size).toFixed(2),
|
|
data_size_percentage: parseInt((data_size / database_size_limit) * 100),
|
|
index_size_percentage: parseInt(
|
|
(index_size / database_size_limit) * 100,
|
|
),
|
|
};
|
|
},
|
|
isPerformanceSchemaEnabled() {
|
|
const result = this.$resources.databasePerformanceReport?.data?.message;
|
|
if (!result) return false;
|
|
return result['is_performance_schema_enabled'];
|
|
},
|
|
queryTabs() {
|
|
if (!this.isRequiredInformationReceived) return [];
|
|
const result = this.$resources.databasePerformanceReport?.data?.message;
|
|
if (!result) return [];
|
|
let prepared_result = [
|
|
{
|
|
label: '慢查询',
|
|
columns: ['检查行数', '发送行数', '调用次数', '持续时间', '查询'],
|
|
data: result['slow_queries'].map((e) => {
|
|
return [e.rows_examined, e.rows_sent, e.count, e.duration, e.query];
|
|
}),
|
|
},
|
|
{
|
|
label: '耗时查询',
|
|
columns: ['百分比', '调用次数', '平均时间', '查询'],
|
|
data: result['top_10_time_consuming_queries'].map((e) => {
|
|
return [
|
|
Math.round(e['percent'], 1),
|
|
e['calls'],
|
|
e['avg_time_ms'],
|
|
e['query'],
|
|
];
|
|
}),
|
|
},
|
|
{
|
|
label: '全表扫描查询',
|
|
columns: ['检查行数', '发送行数', '调用次数', '查询'],
|
|
data: result['top_10_queries_with_full_table_scan'].map((e) => {
|
|
return [e['rows_examined'], e['rows_sent'], e['calls'], e['query']];
|
|
}),
|
|
},
|
|
];
|
|
return prepared_result;
|
|
},
|
|
databaseIndexesTab() {
|
|
if (!this.isRequiredInformationReceived) return [];
|
|
const result = this.$resources.databasePerformanceReport?.data?.message;
|
|
if (!result) return [];
|
|
let prepared_result = [
|
|
{
|
|
label: '建议索引',
|
|
columns: ['表', '列', '索引名称', '示例查询'],
|
|
data: [],
|
|
},
|
|
{
|
|
label: '冗余索引',
|
|
columns: [
|
|
'表名',
|
|
'主导索引',
|
|
'主导索引列',
|
|
'冗余索引',
|
|
'冗余索引列',
|
|
],
|
|
data: result['redundant_indexes'].map((e) => {
|
|
return [
|
|
e['table_name'],
|
|
e['dominant_index_name'],
|
|
e['dominant_index_columns'],
|
|
e['redundant_index_name'],
|
|
e['redundant_index_columns'],
|
|
];
|
|
}),
|
|
},
|
|
{
|
|
label: '未使用索引',
|
|
columns: ['表名', '索引名称'],
|
|
data: result['unused_indexes'].map((e) => {
|
|
return [e['table_name'], e['index_name']];
|
|
}),
|
|
},
|
|
];
|
|
return prepared_result;
|
|
},
|
|
suggestedDatabaseIndexes() {
|
|
if (!this.isRequiredInformationReceived) return [];
|
|
const result =
|
|
this.$resources.suggestDatabaseIndexes?.data?.message?.data ?? [];
|
|
let data = [];
|
|
for (const record of result) {
|
|
for (const index of record.suggested_indexes) {
|
|
data.push([index.table, index.column, record.normalized]);
|
|
}
|
|
}
|
|
return {
|
|
columns: ['表', '列', '慢查询'],
|
|
data: data,
|
|
};
|
|
},
|
|
databaseProcesses() {
|
|
if (!this.isRequiredInformationReceived) return null;
|
|
const result = this.$resources.databaseProcesses.data?.message ?? [];
|
|
return {
|
|
columns: ['ID', '状态', '时间', '用户', '主机', '命令', '查询'],
|
|
data: result.map((e) => {
|
|
return [
|
|
e['id'],
|
|
e['state'],
|
|
e['time'],
|
|
e['db_user'],
|
|
e['db_user_host'],
|
|
e['command'],
|
|
e['query'],
|
|
];
|
|
}),
|
|
};
|
|
},
|
|
cellFormatters() {
|
|
return {
|
|
'检查行数': (v) => formatValue(v, 'commaSeperatedNumber'),
|
|
'发送行数': (v) => formatValue(v, 'commaSeperatedNumber'),
|
|
调用次数: (v) => formatValue(v, 'commaSeperatedNumber'),
|
|
'平均时间': (v) => formatValue(v, 'durationMilliseconds'),
|
|
持续时间: (v) => formatValue(v, 'durationSeconds'),
|
|
时间: (v) => formatValue(v, 'durationSeconds'),
|
|
};
|
|
},
|
|
fullViewFormatters() {
|
|
return {
|
|
查询: (v) => formatValue(v, 'sql'),
|
|
};
|
|
},
|
|
alignColumns() {
|
|
return {
|
|
百分比: 'right',
|
|
'检查行数': 'right',
|
|
'发送行数': 'right',
|
|
调用次数: 'right',
|
|
'平均时间': 'right',
|
|
持续时间: 'right',
|
|
时间: 'right',
|
|
};
|
|
},
|
|
},
|
|
methods: {
|
|
fetchTableSchemas({ site_name = null, reload = false } = {}) {
|
|
if (!site_name) site_name = this.site;
|
|
if (!site_name) return;
|
|
this.$resources.tableSchemas.submit({
|
|
dt: 'Site',
|
|
dn: site_name,
|
|
method: 'fetch_database_table_schema',
|
|
args: {
|
|
reload,
|
|
},
|
|
});
|
|
},
|
|
optimizeTable() {
|
|
this.$resources.optimizeTable.submit({
|
|
dt: 'Site',
|
|
dn: this.site,
|
|
method: 'optimize_tables',
|
|
});
|
|
},
|
|
viewTableSchemaDetails(tableName) {
|
|
this.showTableSchemaSizeDetailsDialog = false;
|
|
this.preSelectedSchemaForSchemaDialog = tableName;
|
|
this.showTableSchemasDialog = true;
|
|
},
|
|
formatSizeInMB(mb) {
|
|
try {
|
|
let floatMB = parseFloat(mb);
|
|
if (floatMB < 1) {
|
|
let kb = floatMB * 1024; // Convert MB to KB
|
|
return `${Math.round(kb)} KB`; // Return KB without decimal
|
|
} else if (floatMB < 1024) {
|
|
return `${Math.round(floatMB)} MB`; // Return MB without decimal
|
|
} else {
|
|
let gb = floatMB / 1024; // Convert MB to GB
|
|
return `${gb.toFixed(1)} GB`; // Return GB with 1 decimal
|
|
}
|
|
} catch (error) {
|
|
return `${mb} MB`; // Return MB without decimal
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script> |