1351 lines
36 KiB
JavaScript

import {
createListResource,
createResource,
LoadingIndicator,
} from 'jingrow-ui';
import { defineAsyncComponent, h } from 'vue';
import { unparse } from 'papaparse';
import { toast } from 'vue-sonner';
import AddDomainDialog from '../components/AddDomainDialog.vue';
import GenericDialog from '../components/GenericDialog.vue';
import ObjectList from '../components/ObjectList.vue';
import SiteActions from '../components/SiteActions.vue';
import { getTeam, switchToTeam } from '../data/team';
import router from '../router';
import { getRunningJobs } from '../utils/agentJob';
import { confirmDialog, icon, renderDialog } from '../utils/components';
import dayjs from '../utils/dayjs';
import { bytes, date, userCurrency, getStatusLabel, getDeployTypeLabel } from '../utils/format';
import { getToastErrorMessage } from '../utils/toast';
import { getDocResource } from '../utils/resource';
import { trialDays } from '../utils/site';
import { clusterOptions, getUpsellBanner } from './common';
import { getAppsTab } from './common/apps';
import { isMobile } from '../utils/device';
import { getJobsTab } from './common/jobs';
export default {
pagetype: 'Site',
whitelistedMethods: {
activate: 'activate',
addDomain: 'add_domain',
archive: 'archive',
backup: 'backup',
clearSiteCache: 'clear_site_cache',
deactivate: 'deactivate',
disableReadWrite: 'disable_read_write',
enableReadWrite: 'enable_read_write',
installApp: 'install_app',
uninstallApp: 'uninstall_app',
migrate: 'migrate',
moveToBench: 'move_to_bench',
moveToGroup: 'move_to_group',
loginAsAdmin: 'login_as_admin',
loginAsTeam: 'login_as_team',
isSetupWizardComplete: 'is_setup_wizard_complete',
reinstall: 'reinstall',
removeDomain: 'remove_domain',
redirectToPrimary: 'set_redirect',
removeRedirect: 'unset_redirect',
setPrimaryDomain: 'set_host_name',
restoreSite: 'restore_site',
restoreSiteFromFiles: 'restore_site_from_files',
scheduleUpdate: 'schedule_update',
editScheduledUpdate: 'edit_scheduled_update',
cancelUpdate: 'cancel_scheduled_update',
setPlan: 'set_plan',
updateConfig: 'update_config',
deleteConfig: 'delete_config',
sendTransferRequest: 'send_change_team_request',
addTag: 'add_resource_tag',
removeTag: 'remove_resource_tag',
getBackupDownloadLink: 'get_backup_download_link',
fetchDatabaseTableSchemas: 'fetch_database_table_schemas',
},
list: {
route: '/sites',
title: '站点',
fields: [
'plan.plan_title as plan_title',
'plan.price_usd as price_usd',
'plan.price_cny as price_cny',
'group.title as group_title',
'group.public as group_public',
'group.team as group_team',
'group.version as version',
'cluster.image as cluster_image',
'cluster.title as cluster_title',
'trial_end_date',
'site_end_date',
],
orderBy: 'creation desc',
searchField: 'host_name',
filterControls() {
return [
{
type: 'select',
label: '状态',
fieldname: 'status',
options: [
{ label: '', value: '' },
{ label: '激活', value: 'Active' },
{ label: '未激活', value: 'Inactive' },
{ label: '已暂停', value: 'Suspended' },
{ label: '损坏', value: 'Broken' },
{ label: '已归档', value: 'Archived' },
],
},
{
type: 'link',
label: '版本',
fieldname: 'group.version',
options: {
pagetype: 'Jingrow Version',
},
},
{
type: 'link',
label: '站点分组',
fieldname: 'group',
options: {
pagetype: 'Release Group',
},
},
{
type: 'select',
label: '区域',
fieldname: 'cluster',
options: clusterOptions,
},
{
type: 'link',
label: '标签',
fieldname: 'tags.tag',
options: {
pagetype: 'Jcloud Tag',
filters: {
pagetype_name: 'Site',
},
},
},
];
},
columns: [
{
label: '站点',
fieldname: 'host_name',
width: 1.5,
class: 'font-medium',
format(value, row) {
return value || row.name;
},
},
{
label: '状态',
fieldname: 'status',
type: 'Badge',
width: '140px',
format(value) {
const statusMap = {
'Active': '激活',
'Inactive': '未激活',
'Suspended': '已暂停',
'Broken': '损坏',
'Archived': '已归档',
'Pending': '待处理',
'Installing': '安装中',
'Update Available': '可更新',
'Running': '运行中',
'Success': '成功',
'Failure': '失败'
};
return statusMap[value] || value;
}
},
{
label: '计划',
fieldname: 'plan',
width: 0.85,
format(value, row) {
if (row.trial_end_date) {
return trialDays(row.trial_end_date);
}
const $team = getTeam();
if (row.price_usd > 0) {
const china = $team.pg?.currency === 'CNY';
const formattedValue = userCurrency(
china ? row.price_cny : row.price_usd,
0,
);
return `${formattedValue}/月`;
}
return row.plan_title;
},
},
{
label: '区域',
fieldname: 'cluster',
width: 1,
format(value, row) {
return row.cluster_title || value;
},
prefix(row) {
return h('img', {
src: row.cluster_image,
class: 'w-4 h-4',
alt: row.cluster_title,
});
},
},
{
label: '站点分组',
fieldname: 'group',
width: '15rem',
format(value, row) {
return row.group_public ? '公域' : row.group_title || value;
},
},
{
label: '版本',
fieldname: 'version',
width: 0.5,
},
{
label: '到期时间',
fieldname: 'site_end_date',
width: 1,
format(value) {
return value ? date(value, 'YYYY-MM-DD') : '';
},
},
],
primaryAction({ listResource: sites }) {
return {
label: '新建站点',
variant: 'solid',
slots: {
prefix: icon('plus'),
},
onClick() {
router.push({ name: 'New Site' });
},
};
},
moreActions({ listResource: sites }) {
return [
{
label: '导出为CSV',
icon: 'download',
onClick() {
const fields = [
'host_name',
'plan_title',
'cluster_title',
'group_title',
'version',
];
const data = sites.data.map((site) => {
const row = {};
fields.forEach((field) => {
row[field] = site[field];
});
return row;
});
let csv = unparse({
fields,
data,
});
csv = '\uFEFF' + csv; // for utf-8
// create a blob and trigger a download
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
const today = new Date().toISOString().split('T')[0];
const filename = `sites-${today}.csv`;
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
},
},
];
},
},
detail: {
titleField: 'name',
route: '/sites/:name',
statusBadge({ documentResource: site }) {
const statusMap = {
'Active': '激活',
'Inactive': '未激活',
'Suspended': '已暂停',
'Broken': '损坏',
'Archived': '已归档',
'Pending': '待处理',
'Installing': '安装中',
'Running': '运行中',
'Success': '成功',
'Failure': '失败'
};
return { label: statusMap[site.pg.status] || site.pg.status };
},
breadcrumbs({ items, documentResource: site }) {
let breadcrumbs = [];
let $team = getTeam();
let siteCrumb = {
label: site.pg.host_name || site.pg?.name,
route: `/sites/${site.pg?.name}`,
};
if (
(site.pg.server_team == $team.pg?.name &&
site.pg.group_team == $team.pg?.name) ||
$team.pg?.is_desk_user
) {
breadcrumbs.push({
label: site.pg?.server_title || site.pg?.server,
route: `/servers/${site.pg?.server}`,
});
}
if (
site.pg.group_team == $team.pg?.name ||
$team.pg?.is_desk_user ||
$team.pg?.is_support_agent
) {
breadcrumbs.push(
{
label: site.pg?.group_title,
route: `/groups/${site.pg?.group}`,
},
siteCrumb,
);
} else {
breadcrumbs.push(...items.slice(0, -1), siteCrumb);
}
return breadcrumbs;
},
tabs: [
{
label: '概览',
icon: icon('home'),
route: 'overview',
type: 'Component',
condition: (site) => site.pg?.status !== 'Archived',
component: defineAsyncComponent(
() => import('../components/SiteOverview.vue'),
),
props: (site) => {
return { site: site.pg?.name };
},
},
getAppsTab(true),
{
label: '域名',
icon: icon('external-link'),
route: 'domains',
type: 'list',
condition: (site) => site.pg?.status !== 'Archived',
list: {
pagetype: 'Site Domain',
fields: ['redirect_to_primary'],
filters: (site) => {
return { site: site.pg?.name };
},
columns: [
{
label: '域名',
fieldname: 'domain',
},
{
label: '状态',
fieldname: 'status',
type: 'Badge',
format(value) {
const statusMap = {
'Active': '激活',
'Inactive': '未激活',
'Suspended': '已暂停',
'Broken': '损坏',
'Archived': '已归档',
'Pending': '待处理',
'Installing': '安装中',
'Running': '运行中',
'Success': '成功',
'Failure': '失败'
};
return statusMap[value] || value;
}
},
{
label: '主域名',
fieldname: 'primary',
type: 'Icon',
Icon(value) {
return value ? 'check' : '';
},
},
{
label: 'DNS 类型',
fieldname: 'dns_type',
type: 'Badge',
},
],
banner({ documentResource: site }) {
if (site.pg.broken_domain_error) {
return {
title:
'获取您的域名的 HTTPS 证书时出错。',
type: 'error',
button: {
label: '查看错误',
variant: 'outline',
onClick() {
renderDialog(
h(
GenericDialog,
{
options: {
title: '获取证书时出错',
size: 'xl',
},
},
{
default: () => {
return h('pre', {
class:
'whitespace-pre-wrap text-sm rounded border-2 p-3 border-gray-200 bg-gray-100',
innerHTML: site.pg.broken_domain_error,
});
},
},
),
);
},
},
};
} else {
return null;
}
},
primaryAction({ listResource: domains, documentResource: site }) {
return {
label: '添加域名',
slots: {
prefix: icon('plus'),
},
onClick() {
renderDialog(
h(AddDomainDialog, {
site: site.pg,
onDomainAdded() {
domains.reload();
},
}),
);
},
};
},
rowActions({ row, listResource: domains, documentResource: site }) {
return [
{
label: '移除',
condition: () => row.domain !== site.pg?.name,
onClick() {
confirmDialog({
title: `移除域名`,
message: `您确定要从站点 <b>${site.pg?.name}</b> 中移除域名 <b>${row.domain}</b> 吗?`,
onSuccess({ hide }) {
if (site.removeDomain.loading) return;
toast.promise(
site.removeDomain.submit({
domain: row.domain,
}),
{
loading: '正在移除域名...',
success: () => {
hide();
return '域名已移除';
},
error: (e) => getToastErrorMessage(e),
},
);
},
});
},
},
{
label: '设为主域名',
condition: () => !row.primary && row.status === 'Active',
onClick() {
confirmDialog({
title: `设为主域名`,
message: `您确定要将域名 <b>${row.domain}</b> 设为站点 <b>${site.pg?.name}</b> 的主域名吗?`,
onSuccess({ hide }) {
if (site.setPrimaryDomain.loading) return;
toast.promise(
site.setPrimaryDomain.submit({
domain: row.domain,
}),
{
loading: '正在设置主域名...',
success: () => {
hide();
return '主域名已设置';
},
error: (e) => getToastErrorMessage(e),
},
);
},
});
},
},
{
label: '重定向到主域名',
condition: () =>
!row.primary &&
!row.redirect_to_primary &&
row.status === 'Active',
onClick() {
confirmDialog({
title: `重定向域名`,
message: `您确定要将域名 <b>${row.domain}</b> 重定向到站点 <b>${site.pg?.name}</b> 的主域名吗?`,
onSuccess({ hide }) {
if (site.redirectToPrimary.loading) return;
toast.promise(
site.redirectToPrimary.submit({
domain: row.domain,
}),
{
loading: '正在重定向域名...',
success: () => {
hide();
return '域名已重定向';
},
error: (e) => getToastErrorMessage(e),
},
);
},
});
},
},
{
label: '移除重定向',
condition: () =>
!row.primary &&
row.redirect_to_primary &&
row.status === 'Active',
onClick() {
confirmDialog({
title: `移除重定向`,
message: `您确定要移除从域名 <b>${row.domain}</b> 到站点 <b>${site.pg?.name}</b> 主域名的重定向吗?`,
onSuccess({ hide }) {
if (site.removeRedirect.loading) return;
toast.promise(
site.removeRedirect.submit({
domain: row.domain,
}),
{
loading: '正在移除重定向...',
success: () => {
hide();
return '重定向已移除';
},
error: (e) => getToastErrorMessage(e),
},
);
},
});
},
},
];
},
},
},
{
label: '备份',
icon: icon('archive'),
route: 'backups',
type: 'list',
list: {
pagetype: 'Site Backup',
filters: (site) => {
return {
site: site.pg?.name,
files_availability: 'Available',
status: ['in', ['Pending', 'Running', 'Success']],
};
},
orderBy: 'creation desc',
fields: [
'job',
'status',
'database_url',
'public_url',
'private_url',
'config_file_url',
'site',
'remote_database_file',
'remote_public_file',
'remote_private_file',
'remote_config_file',
'physical',
],
columns: [
{
label: '时间戳',
fieldname: 'creation',
width: 1,
format(value) {
return `备份于 ${date(value, 'llll')}`;
},
},
{
label: '数据库',
fieldname: 'database_size',
width: 0.5,
format(value) {
return value ? bytes(value) : '';
},
},
{
label: '公共文件',
fieldname: 'public_size',
width: 0.5,
format(value) {
return value ? bytes(value) : '';
},
},
{
label: '私有文件',
fieldname: 'private_size',
width: 0.5,
format(value) {
return value ? bytes(value) : '';
},
},
{
label: '包含文件的备份',
fieldname: 'with_files',
type: 'Icon',
width: 0.5,
Icon(value) {
return value ? 'check' : '';
},
},
{
label: '异地备份',
fieldname: 'offsite',
width: 0.5,
type: 'Icon',
Icon(value) {
return value ? 'check' : '';
},
},
],
filterControls() {
return [
{
type: 'checkbox',
label: '异地备份',
fieldname: 'offsite',
},
];
},
rowActions({ row, documentResource: site }) {
if (row.status != 'Success') return;
function getFileName(file) {
if (file == 'database') return 'database';
if (file == 'public') return 'public files';
if (file == 'private') return 'private files';
if (file == 'config') return 'config file';
}
function confirmDownload(backup, file) {
confirmDialog({
title: '下载备份',
message: `您将下载站点 <b>${
site.pg?.host_name || site.pg?.name
}</b> 的 ${getFileName(file)} 备份,该备份创建于 ${date(backup.creation, 'llll')}${
!backup.offsite
? '<br><br><div class="p-2 bg-gray-100 border-gray-200 rounded">您需要以 <b>系统管理员</b> 身份登录 <em>您的站点</em> 才能下载备份。<div>'
: ''
}`,
onSuccess() {
downloadBackup(backup, file);
},
});
}
async function downloadBackup(backup, file) {
// file: database, public, or private
if (backup.offsite) {
site.getBackupDownloadLink.submit(
{ backup: backup.name, file },
{
onSuccess(r) {
// TODO: fix this in documentResource, it should return message directly
if (r.message) {
window.open(r.message);
}
},
},
);
} else {
const url =
file == 'config'
? backup.config_file_url
: backup[file + '_url'];
const domainRegex = /^(https?:\/\/)?([^/]+)\/?/;
const newUrl = url.replace(
domainRegex,
`$1${site.pg.host_name}/`,
);
window.open(newUrl);
}
}
return [
{
group: '详情',
items: [
{
label: '查看任务',
onClick() {
router.push({
name: 'Site Job',
params: { name: site.name, id: row.job },
});
},
},
],
},
{
group: '下载',
items: [
{
label: '下载数据库',
onClick() {
return confirmDownload(row, 'database');
},
},
{
label: '下载公共文件',
onClick() {
return confirmDownload(row, 'public');
},
condition: () => row.public_url,
},
{
label: '下载私有文件',
onClick() {
return confirmDownload(row, 'private');
},
condition: () => row.private_url,
},
{
label: '下载配置文件',
onClick() {
return confirmDownload(row, 'config');
},
condition: () => row.config_file_url,
},
],
},
{
group: '恢复',
condition: () => row.offsite,
items: [
{
label: '恢复备份',
condition: () => site.pg.status !== 'Archived',
onClick() {
confirmDialog({
title: '恢复备份',
message: `您确定要将您的站点恢复到<b>${dayjs(
row.creation,
).format('lll')}</b>的异地备份吗?`,
onSuccess({ hide }) {
toast.promise(
site.restoreSiteFromFiles.submit({
files: {
database: row.remote_database_file,
public: row.remote_public_file,
private: row.remote_private_file,
config: row.remote_config_file,
},
}),
{
loading: '正在安排备份恢复...',
success: (jobId) => {
hide();
router.push({
name: 'Site Job',
params: {
name: site.name,
id: jobId,
},
});
return '备份恢复已成功安排。';
},
error: (e) => getToastErrorMessage(e),
},
);
},
});
},
},
{
label: '在另一个站点上恢复备份',
onClick() {
let SelectSiteForRestore = defineAsyncComponent(
() =>
import('../components/site/SelectSiteForRestore.vue'),
);
renderDialog(
h(SelectSiteForRestore, {
site: site.name,
onRestore(siteName) {
const restoreSite = createResource({
url: 'jcloud.api.site.restore',
});
return toast.promise(
restoreSite.submit({
name: siteName,
files: {
database: row.remote_database_file,
public: row.remote_public_file,
private: row.remote_private_file,
config: row.remote_config_file,
},
}),
{
loading: '正在安排备份恢复...',
success: (jobId) => {
router.push({
name: 'Site Job',
params: { name: siteName, id: jobId },
});
return '备份恢复已成功安排。';
},
error: (e) => getToastErrorMessage(e),
},
);
},
}),
);
},
},
],
},
].filter((d) => (d.condition ? d.condition() : true));
},
primaryAction({ listResource: backups, documentResource: site }) {
return {
label: '安排备份',
slots: {
prefix: icon('upload-cloud'),
},
loading: site.backup.loading,
onClick() {
confirmDialog({
title: '安排备份',
message:
'您确定要安排备份吗?这将创建一个本地备份。',
onSuccess({ hide }) {
toast.promise(
site.backup.submit({
with_files: true,
}),
{
loading: '正在安排备份...',
success: () => {
hide();
router.push({
name: 'Site Jobs',
params: { name: site.name },
});
return '备份已成功安排。';
},
error: (e) => getToastErrorMessage(e),
},
);
},
});
},
};
},
},
},
getJobsTab('Site'),
{
label: '操作',
icon: icon('sliders'),
route: 'actions',
type: 'Component',
condition: (site) => site.pg?.status !== 'Archived',
component: SiteActions,
props: (site) => {
return { site: site.pg?.name };
},
},
{
label: '更新',
icon: icon('arrow-up-circle'),
route: 'updates',
type: 'list',
condition: (site) => site.pg?.status !== 'Archived',
childrenRoutes: ['Site Update'],
list: {
pagetype: 'Site Update',
filters: (site) => {
return { site: site.pg?.name };
},
orderBy: 'creation',
fields: [
'difference',
'update_job.end as updated_on',
'update_job',
'backup_type',
'recover_job',
],
columns: [
{
label: '类型',
fieldname: 'deploy_type',
width: 0.3,
format(value) {
return getDeployTypeLabel(value);
}
},
{
label: '状态',
fieldname: 'status',
type: 'Badge',
width: 0.5,
format(value) {
return getStatusLabel(value);
}
},
// {
// label: '备份',
// width: 0.4,
// type: 'Component',
// component({ row }) {
// return h(
// 'div',
// {
// class: 'truncate text-base',
// },
// row.skipped_backups
// ? '跳过'
// : row.backup_type || '逻辑',
// );
// },
// },
{
label: '创建者',
fieldname: 'owner',
},
{
label: '计划时间',
fieldname: 'scheduled_time',
format(value) {
return date(value, 'lll');
},
},
{
label: '更新时间',
fieldname: 'updated_on',
format(value) {
return date(value, 'lll');
},
},
],
rowActions({ row, documentResource: site }) {
return [
{
label: '编辑',
condition: () => row.status === 'Scheduled',
onClick() {
let SiteUpdateDialog = defineAsyncComponent(
() => import('../components/SiteUpdateDialog.vue'),
);
renderDialog(
h(SiteUpdateDialog, {
site: site.pg?.name,
existingUpdate: row.name,
}),
);
},
},
{
label: '取消',
condition: () => row.status === 'Scheduled',
onClick() {
confirmDialog({
title: '取消更新',
message: `您确定要取消计划的更新吗?`,
onSuccess({ hide }) {
if (site.cancelUpdate.loading) return;
toast.promise(
site.cancelUpdate.submit({ site_update: row.name }),
{
loading: '正在取消更新...',
success: () => {
hide();
site.reload();
return '更新已取消';
},
error: (e) => getToastErrorMessage(e),
},
);
},
});
},
},
{
label: '查看任务',
condition: () => row.status !== 'Scheduled',
onClick() {
router.push({
name: 'Site Update',
params: { id: row.name },
});
},
},
{
label: '立即更新',
condition: () => row.status === 'Scheduled',
onClick() {
let siteUpdate = getDocResource({
pagetype: 'Site Update',
name: row.name,
whitelistedMethods: {
updateNow: 'start',
},
});
toast.promise(siteUpdate.updateNow.submit(), {
loading: '正在更新站点...',
success: () => {
router.push({
name: 'Site Update',
params: { id: row.name },
});
return '站点更新已启动';
},
error: '更新站点失败',
});
},
},
{
label: '查看应用更改',
onClick() {
createListResource({
pagetype: 'Deploy Candidate Difference App',
fields: [
'difference.github_diff_url as diff_url',
'difference.source_hash as source_hash',
'difference.destination_hash as destination_hash',
'app.title as app',
],
filters: {
parenttype: 'Deploy Candidate Difference',
parent: row.difference,
},
auto: true,
pageLength: 99,
onSuccess(data) {
if (data?.length) {
renderDialog(
h(
GenericDialog,
{
options: {
title: '应用更改',
size: '2xl',
},
},
{
default: () =>
h(ObjectList, {
options: {
data: () => data,
columns: [
{
label: '应用',
fieldname: 'app',
},
{
label: '从',
fieldname: 'source_hash',
type: 'Button',
Button({ row }) {
return {
label:
row.source_tag ||
row.source_hash.slice(0, 7),
variant: 'ghost',
class: 'font-mono',
link: `${
row.diff_url.split('/compare')[0]
}/commit/${row.source_hash}`,
};
},
},
{
label: '到',
fieldname: 'destination_hash',
type: 'Button',
Button({ row }) {
return {
label:
row.destination_tag ||
row.destination_hash.slice(0, 7),
variant: 'ghost',
class: 'font-mono',
link: `${
row.diff_url.split('/compare')[0]
}/commit/${row.destination_hash}`,
};
},
},
{
label: '应用变更',
fieldname: 'diff_url',
align: 'right',
type: 'Button',
Button({ row }) {
return {
label: '查看',
variant: 'ghost',
slots: {
prefix: icon('external-link'),
},
link: row.diff_url,
};
},
},
],
},
}),
},
),
);
} else toast.error('未找到应用变更');
},
});
},
},
];
},
actions({ documentResource: site }) {
if (site.pg.group_public) return [];
return [
{
label: '配置',
slots: {
prefix: icon('settings'),
},
onClick() {
let ConfigureAutoUpdateDialog = defineAsyncComponent(
() =>
import(
'../components/site/ConfigureAutoUpdateDialog.vue'
),
);
renderDialog(
h(ConfigureAutoUpdateDialog, {
site: site.pg?.name,
}),
);
},
},
];
},
},
},
],
actions(context) {
let { documentResource: site } = context;
let $team = getTeam();
let runningJobs = getRunningJobs({ site: site.pg?.name });
return [
{
label: '进行中的任务',
slots: {
prefix: () => h(LoadingIndicator, { class: 'w-4 h-4' }),
},
condition() {
return (
runningJobs.filter((job) =>
['Pending', 'Running'].includes(job.status),
).length > 0
);
},
onClick() {
router.push({
name: 'Site Jobs',
params: { name: site.name },
});
},
},
{
label: '有可用更新',
variant: site.pg?.setup_wizard_complete ? 'solid' : 'subtle',
slots: {
prefix: icon('alert-circle'),
},
condition() {
return (
!site.pg?.has_scheduled_updates &&
site.pg.update_information?.update_available &&
['Active', 'Inactive', 'Suspended', 'Broken'].includes(
site.pg.status,
)
);
},
onClick() {
let SiteUpdateDialog = defineAsyncComponent(
() => import('../components/SiteUpdateDialog.vue'),
);
renderDialog(h(SiteUpdateDialog, { site: site.pg?.name }));
},
},
{
label: '已安排更新',
slots: {
prefix: icon('calendar'),
},
condition: () => site.pg?.has_scheduled_updates,
onClick() {
router.push({
name: 'Site Detail Updates',
params: { name: site.name },
});
},
},
{
label: '模拟站点所有者',
title: '模拟站点所有者', // for label to pop-up on hover
slots: {
icon: defineAsyncComponent(
() => import('~icons/lucide/venetian-mask'),
),
},
condition: () =>
$team.pg?.is_desk_user && site.pg.team !== $team.name,
onClick() {
switchToTeam(site.pg.team);
},
},
{
label: '访问站点',
slots: {
prefix: icon('external-link'),
},
condition: () =>
site.pg.status !== 'Archived' && site.pg?.setup_wizard_complete,
onClick() {
window.open(`https://${site.name}/app`, '_blank');
},
},
{
label: '设置站点',
slots: {
prefix: icon('external-link'),
},
variant: 'solid',
condition: () =>
site.pg.status === 'Active' && !site.pg?.setup_wizard_complete,
onClick() {
if (site.pg.additional_system_user_created) {
site.loginAsTeam
.submit({ reason: '' })
.then((url) => window.open(url, '_blank'));
} else {
site.loginAsAdmin
.submit({ reason: '' })
.then((url) => window.open(url, '_blank'));
}
},
},
{
label: '选项',
context,
options: [
{
label: '在 Desk 中查看',
icon: 'external-link',
condition: () => $team.pg?.is_desk_user,
onClick: () => {
window.open(
`${window.location.protocol}//${window.location.host}/app/site/${site.name}`,
'_blank',
);
},
},
{
label: '以管理员身份登录',
icon: 'external-link',
condition: () => ['Active', 'Broken'].includes(site.pg.status),
onClick: () => {
confirmDialog({
title: '以管理员身份登录',
message: `您确定要以管理员身份登录站点 <b>${site.pg?.name}</b> 吗?`,
fields:
$team.name !== site.pg.team
? [
{
label: '原因',
type: 'textarea',
fieldname: 'reason',
},
]
: [],
onSuccess: ({ hide, values }) => {
if (!values.reason && $team.name !== site.pg.team) {
throw new Error('原因必填');
}
return site.loginAsAdmin
.submit({ reason: values.reason })
.then((result) => {
let url = result;
window.open(url, '_blank');
hide();
});
},
});
},
},
],
},
];
},
},
routes: [
{
name: '站点更新',
path: 'updates/:id',
component: () => import('../pages/SiteUpdate.vue'),
},
{
name: 'Site Job',
path: 'jobs/:id',
component: () => import('../pages/JobPage.vue')
}
],
};