1Panel/frontend/src/views/app-store/installed/index.vue
2023-02-08 18:08:11 +08:00

374 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<LayoutContent v-loading="loading" :title="activeName" :divider="true">
<template #toolbar>
<el-row :gutter="5">
<el-col :span="20">
<div v-if="data != null">
<el-button
class="tag-button"
:class="activeTag === 'all' ? '' : 'no-active'"
@click="changeTag('all')"
:type="activeTag === 'all' ? 'primary' : ''"
:plain="activeTag !== 'all'"
>
{{ $t('app.all') }}
</el-button>
<div v-for="item in tags" :key="item.key" style="display: inline">
<el-button
class="tag-button"
:class="activeTag === item.key ? '' : 'no-active'"
@click="changeTag(item.key)"
:type="activeTag === item.key ? 'primary' : ''"
:plain="activeTag !== item.key"
>
{{ item.name }}
</el-button>
</div>
</div>
</el-col>
<el-col :span="4">
<div class="search-button">
<el-input
class="table-button"
v-model="searchReq.name"
clearable
@clear="search()"
suffix-icon="Search"
@keyup.enter="search()"
@blur="search()"
:placeholder="$t('commons.button.search')"
></el-input>
</div>
</el-col>
</el-row>
</template>
<template #rightButton>
<el-button @click="sync" type="primary" link v-if="mode === 'installed' && data != null">
{{ $t('app.sync') }}
</el-button>
</template>
<template #main>
<div class="update-prompt" v-if="data == null">
<el-empty
:description="mode === 'update' ? $t('app.updatePrompt') : $t('app.installPrompt')"
image="/src/assets/images/no_update_app.svg"
:image-size="200"
></el-empty>
</div>
<el-row :gutter="5">
<el-col v-for="(installed, index) in data" :key="index" :span="12">
<div class="app-card">
<el-row :gutter="24">
<el-col :span="4">
<div class="icon">
<el-avatar
shape="square"
:size="66"
:src="'data:image/png;base64,' + installed.app.icon"
/>
</div>
</el-col>
<el-col :span="20">
<div class="a-detail">
<div class="d-name">
<span class="name">{{ installed.name }}</span>
<span class="status">
<el-popover
v-if="installed.status === 'Error'"
placement="bottom"
:width="400"
trigger="hover"
:content="installed.message"
>
<template #reference>
<Status :key="installed.status" :status="installed.status"></Status>
</template>
</el-popover>
<span v-else>
<Status :key="installed.status" :status="installed.status"></Status>
</span>
</span>
<el-button
class="h-button"
type="primary"
plain
round
size="small"
@click="openBackups(installed.id, installed.name)"
v-if="mode === 'installed'"
>
{{ $t('app.backup') }}
</el-button>
<el-button
class="h-button"
type="primary"
plain
round
size="small"
@click="openOperate(installed, 'update')"
v-if="mode === 'update'"
>
{{ $t('app.update') }}
</el-button>
</div>
<div class="d-description">
<el-tag>{{ $t('app.version') }}{{ installed.version }}</el-tag>
<el-tag>HTTP{{ $t('app.port') }}{{ installed.httpPort }}</el-tag>
<el-tag v-if="installed.httpsPort > 0">
HTTPS{{ $t('app.port') }}{{ installed.httpsPort }}
</el-tag>
<div class="description">
<span>{{ $t('app.areadyRun') }} {{ getAge(installed.createdAt) }}</span>
</div>
</div>
<div class="app-divider" />
<div class="d-button" v-if="mode === 'installed'">
<el-button
v-for="(button, key) in buttons"
:key="key"
type="primary"
plain
round
size="small"
@click="button.click(installed)"
:disabled="button.disabled && button.disabled(installed)"
>
{{ button.label }}
</el-button>
</div>
</div>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</template>
</LayoutContent>
<Backups ref="backupRef" @close="search" />
<AppResources ref="checkRef" />
<AppDelete ref="deleteRef" @close="search" />
<AppParams ref="appParamRef" />
<AppUpdate ref="updateRef" @close="search" />
</template>
<script lang="ts" setup>
import {
SearchAppInstalled,
InstalledOp,
SyncInstalledApp,
AppInstalledDeleteCheck,
GetAppTags,
} from '@/api/modules/app';
import LayoutContent from '@/layout/layout-content.vue';
import { onMounted, onUnmounted, reactive, ref } from 'vue';
import i18n from '@/lang';
import { ElMessage, ElMessageBox } from 'element-plus';
import Backups from './backup/index.vue';
import AppResources from './check/index.vue';
import AppDelete from './delete/index.vue';
import AppParams from './detail/index.vue';
import AppUpdate from './update/index.vue';
import { App } from '@/api/interface/app';
import Status from '@/components/status/index.vue';
import { getAge } from '@/utils/util';
import { useRouter } from 'vue-router';
let data = ref<any>();
let loading = ref(false);
let timer: NodeJS.Timer | null = null;
const paginationConfig = reactive({
currentPage: 1,
pageSize: 20,
total: 0,
});
let open = ref(false);
let operateReq = reactive({
installId: 0,
operate: '',
detailId: 0,
});
const backupRef = ref();
const checkRef = ref();
const deleteRef = ref();
const appParamRef = ref();
const updateRef = ref();
let tags = ref<App.Tag[]>([]);
let activeTag = ref('all');
let searchReq = reactive({
page: 1,
pageSize: 15,
name: '',
tags: [],
update: false,
});
const router = useRouter();
let activeName = ref(i18n.global.t('app.installed'));
let mode = ref('installed');
const sync = () => {
loading.value = true;
SyncInstalledApp()
.then(() => {
ElMessage.success(i18n.global.t('app.syncSuccess'));
search();
})
.finally(() => {
loading.value = false;
});
};
const changeTag = (key: string) => {
searchReq.tags = [];
activeTag.value = key;
if (key !== 'all') {
searchReq.tags = [key];
}
search();
};
const search = () => {
searchReq.page = paginationConfig.currentPage;
searchReq.pageSize = paginationConfig.pageSize;
SearchAppInstalled(searchReq).then((res) => {
data.value = res.data.items;
paginationConfig.total = res.data.total;
});
GetAppTags().then((res) => {
tags.value = res.data;
});
};
const openOperate = (row: any, op: string) => {
operateReq.installId = row.id;
operateReq.operate = op;
if (op == 'update') {
updateRef.value.acceptParams(row.id, row.name);
} else if (op == 'delete') {
AppInstalledDeleteCheck(row.id).then(async (res) => {
const items = res.data;
if (res.data && res.data.length > 0) {
checkRef.value.acceptParams({ items: items });
} else {
deleteRef.value.acceptParams(row);
}
});
} else {
onOperate(op);
}
};
const operate = async () => {
open.value = false;
loading.value = true;
await InstalledOp(operateReq)
.then(() => {
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
search();
})
.finally(() => {
loading.value = false;
});
};
const onOperate = async (operation: string) => {
ElMessageBox.confirm(
i18n.global.t('app.operatorHelper', [i18n.global.t('app.' + operation)]),
i18n.global.t('app.' + operation),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
},
).then(() => {
operate();
});
};
const buttons = [
{
label: i18n.global.t('app.sync'),
click: (row: any) => {
openOperate(row, 'sync');
},
},
{
label: i18n.global.t('app.update'),
click: (row: any) => {
openOperate(row, 'update');
},
disabled: (row: any) => {
return !row.canUpdate;
},
},
{
label: i18n.global.t('app.restart'),
click: (row: any) => {
openOperate(row, 'restart');
},
},
{
label: i18n.global.t('app.up'),
click: (row: any) => {
openOperate(row, 'up');
},
disabled: (row: any) => {
return row.status === 'Running';
},
},
{
label: i18n.global.t('app.down'),
click: (row: any) => {
openOperate(row, 'down');
},
},
{
label: i18n.global.t('app.delete'),
click: (row: any) => {
openOperate(row, 'delete');
},
},
{
label: i18n.global.t('app.detail'),
click: (row: any) => {
openParam(row.id);
},
},
];
const openBackups = (installId: number, installName: string) => {
let params = {
appInstallId: installId,
appInstallName: installName,
};
backupRef.value.acceptParams(params);
};
const openParam = (installId: number) => {
appParamRef.value.acceptParams({ id: installId });
};
onMounted(() => {
const path = router.currentRoute.value.path;
if (path == '/apps/update') {
activeName.value = i18n.global.t('app.canUpdate');
mode.value = 'update';
searchReq.update = true;
}
search();
timer = setInterval(() => {
search();
}, 1000 * 8);
});
onUnmounted(() => {
clearInterval(Number(timer));
timer = null;
});
</script>
<style scoped lang="scss">
@import '../index.scss';
</style>