504 lines
12 KiB
Vue

<template>
<div>
<header
class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-5 py-2.5"
>
<Breadcrumbs
:items="[
{ label: 'Sites', route: { name: 'Sites' } },
{
label: site?.host_name || site?.name,
route: { name: 'SiteOverview', params: { siteName: site?.name } }
}
]"
>
<template #actions>
<div class="space-x-2">
<Button
v-if="site?.status === 'Active'"
icon-left="external-link"
label="Visit Site"
:link="`https://${site?.name}`"
/>
<Dropdown :options="siteActions">
<template v-slot="{ open }">
<Button variant="ghost" icon="more-horizontal" />
</template>
</Dropdown>
</div>
</template>
</Breadcrumbs>
</header>
<div class="p-5" v-if="site">
<div
class="flex flex-col space-y-3 md:flex-row md:items-baseline md:justify-between md:space-y-0"
>
<div class="flex items-center">
<h1 class="text-2xl font-bold">
{{ site.host_name || site.name }}
</h1>
<Badge class="ml-4" :label="site.status" />
<div
v-if="regionInfo"
class="ml-2 hidden cursor-default flex-row items-center self-end rounded-md bg-yellow-50 px-3 py-1 text-xs font-medium text-yellow-700 md:flex"
>
<img
v-if="regionInfo.image"
class="mr-2 h-4"
:src="regionInfo.image"
:alt="`Flag of ${regionInfo.title}`"
:title="regionInfo.image"
/>
<p>{{ regionInfo.title }}</p>
</div>
</div>
<Button
@click="onActivateClick"
v-if="site.pending_for_long"
:variant="'solid'"
class="mr-1"
>
Activate
</Button>
<div class="mb-10 flex flex-row justify-between md:hidden">
<div class="flex flex-row">
<div
v-if="regionInfo"
class="flex cursor-default flex-row items-center rounded-md bg-yellow-50 px-3 py-1 text-xs font-medium text-yellow-700"
>
<img
v-if="regionInfo.image"
class="mr-2 h-4"
:src="regionInfo.image"
:alt="`Flag of ${regionInfo.title}`"
:title="regionInfo.image"
/>
<p>{{ regionInfo.title }}</p>
</div>
</div>
</div>
</div>
<div class="mb-2 mt-4">
<SiteAlerts
v-if="site && $resources.plan?.data && !$resources.site.loading"
:site="site"
:plan="$resources.plan.data"
@plan-change="handlePlanChange"
/>
</div>
<Tabs :tabs="tabs">
<router-view v-slot="{ Component, route }">
<component
v-if="site"
:is="Component"
:site="site"
:plan="$resources.plan.data"
@plan-change="handlePlanChange"
/>
</router-view>
</Tabs>
</div>
<Dialog
:options="{
title: 'Login As Administrator',
actions: [
{
label: 'Proceed',
variant: 'solid',
onClick: proceedWithLoginAsAdmin
}
]
}"
v-model="showReasonForAdminLoginDialog"
>
<template v-slot:body-content>
<FormControl
label="Reason for logging in as Administrator"
type="textarea"
v-model="reasonForAdminLogin"
required
/>
<ErrorMessage class="mt-3" :message="errorMessage" />
</template>
</Dialog>
<SiteTransferDialog :site="site" v-model="showTransferSiteDialog" />
<SiteChangeGroupDialog
v-if="site"
:site="site"
v-model="showChangeGroupDialog"
/>
<SiteChangeRegionDialog
v-if="site"
:site="site"
v-model="showChangeRegionDialog"
/>
<SiteChangeServerDialog
v-if="site"
:site="site"
v-model="showChangeServerDialog"
/>
<SiteVersionUpgradeDialog
v-if="site"
:site="site"
v-model="showVersionUpgradeDialog"
/>
</div>
</template>
<script>
import Tabs from '@/components/Tabs.vue';
import { loginAsAdmin } from '@/controllers/loginAsAdmin';
import SiteAlerts from './SiteAlerts.vue';
import { notify } from '@/utils/toast';
import SiteTransferDialog from './SiteTransferDialog.vue';
import SiteChangeGroupDialog from './SiteChangeGroupDialog.vue';
import SiteChangeRegionDialog from './SiteChangeRegionDialog.vue';
import SiteVersionUpgradeDialog from './SiteVersionUpgradeDialog.vue';
import SiteChangeServerDialog from './SiteChangeServerDialog.vue';
export default {
name: 'Site',
pageMeta() {
return {
title: `Site - ${this.siteName} - 今果 Jingrow`
};
},
props: ['siteName'],
components: {
SiteAlerts,
Tabs,
SiteTransferDialog,
SiteChangeGroupDialog,
SiteChangeRegionDialog,
SiteChangeServerDialog,
SiteVersionUpgradeDialog
},
data() {
return {
runningJob: false,
reasonForAdminLogin: '',
showReasonForAdminLoginDialog: false,
showTransferSiteDialog: false,
showChangeGroupDialog: false,
showChangeRegionDialog: false,
showChangeServerDialog: false,
showVersionUpgradeDialog: false,
errorMessage: ''
};
},
resources: {
site() {
return {
url: 'jcloud.api.site.get',
params: {
name: this.siteName
},
auto: true,
onSuccess() {
this.routeToGeneral();
if (this.siteName !== this.site.name) {
this.$router.replace({ params: { siteName: this.site.name } });
}
if (this.site.status !== 'Active' || this.site.setup_wizard_complete)
return;
this.$call('jcloud.api.site.setup_wizard_complete', {
name: this.siteName
})
.then(complete => {
this.site.setup_wizard_complete = Boolean(complete);
})
.catch(() => (this.site.setup_wizard_complete = false));
},
onError: this.$routeTo404PageIfNotFound
};
},
loginAsAdmin() {
return loginAsAdmin(this.siteName);
},
plan() {
return {
url: 'jcloud.api.site.current_plan',
params: {
name: this.siteName
},
auto: true
};
}
},
activated() {
this.setupAgentJobUpdate();
if (this.site?.status === 'Active') {
this.$socket.on('list_update', this.onSocketUpdate);
}
},
deactivated() {
this.$socket.off('list_update', this.onSocketUpdate);
},
methods: {
onSocketUpdate({ pagetype, name }) {
if (pagetype === 'Site' && name === this.siteName) {
this.$resources.site.reload();
}
},
setupAgentJobUpdate() {
if (this._agentJobUpdateSet) return;
this._agentJobUpdateSet = true;
this.$socket.on('agent_job_update', data => {
if (data.name === 'New Site' || data.name === 'New Site from Backup') {
if (data.status === 'Success' && data.site === this.siteName) {
setTimeout(() => {
// running reload immediately doesn't work for some reason
this.$router.push(`/sites/${this.siteName}/overview`);
this.$resources.site.reload();
}, 1000);
}
}
this.runningJob =
data.site === this.siteName && data.status !== 'Success';
});
},
routeToGeneral() {
if (this.$route.matched.length === 1) {
let tab = ['Pending', 'Installing'].includes(this.site?.status)
? 'jobs'
: 'overview';
this.$router.replace(`/sites/${this.site?.name}/${tab}`);
}
},
proceedWithLoginAsAdmin() {
this.errorMessage = '';
if (!this.reasonForAdminLogin.trim()) {
// The input is empty
this.errorMessage = 'Reason is required';
return;
}
this.$resources.loginAsAdmin.submit({
name: this.siteName,
reason: this.reasonForAdminLogin
});
this.showReasonForAdminLoginDialog = false;
},
handlePlanChange() {
this.$resources.site.reload();
this.$resources.plan.reload();
},
onActivateClick() {
this.$confirm({
title: 'Activate Site',
message: `Are you sure you want to activate this site?`,
actionLabel: 'Activate',
action: () => this.activate()
});
},
activate() {
this.$call('jcloud.api.site.activate', {
name: this.site.name
});
notify({
title: 'Site activated successfully!',
message: 'You can now access your site',
icon: 'check',
color: 'green'
});
setTimeout(() => window.location.reload(), 1000);
}
},
computed: {
site() {
return this.$resources.site.data;
},
regionInfo() {
if (!this.$resources.site.loading && this.$resources.site.data) {
return this.$resources.site.data.server_region_info;
}
},
siteActions() {
return [
{
label: 'View in Desk',
icon: 'external-link',
condition: () => this.$account.user.user_type === 'System User',
onClick: () => {
window.open(
`${window.location.protocol}//${window.location.host}/app/site/${this.site?.name}`,
'_blank'
);
}
},
{
label: 'Manage Bench',
icon: 'tool',
route: `/groups/${this.site?.group}`,
condition: () => this.site?.group,
onClick: () => {
this.$router.push(`/groups/${this.site?.group}`);
}
},
{
label: 'Login As Administrator',
icon: 'external-link',
loading: this.$resources.loginAsAdmin.loading,
condition: () => this.site?.status === 'Active',
onClick: () => {
if (this.$account.team.name == this.site?.notify_email) {
return this.$resources.loginAsAdmin.submit({
name: this.siteName
});
}
this.showReasonForAdminLoginDialog = true;
}
},
{
label: 'Impersonate Team',
icon: 'tool',
condition: () => this.$account.user.user_type === 'System User',
onClick: async () => {
await this.$account.switchTeam(this.site?.team);
notify({
title: 'Switched Team',
message: `Switched to ${this.site?.team}`,
icon: 'check',
color: 'green'
});
}
},
{
label: 'Transfer Site',
icon: 'tool',
condition: () =>
this.site?.status === 'Active' && !this.$account.parent_team,
onClick: () => {
this.showTransferSiteDialog = true;
}
},
{
label: 'Change Bench',
icon: 'package',
condition: () => this.site?.status === 'Active',
onClick: () => (this.showChangeGroupDialog = true)
},
{
label: 'Change Region',
icon: 'globe',
condition: () => this.site?.status === 'Active',
onClick: () => (this.showChangeRegionDialog = true)
},
{
label: 'Upgrade Version',
icon: 'arrow-up',
condition: () => this.site?.status === 'Active',
onClick: () => (this.showVersionUpgradeDialog = true)
},
{
label: 'Change Server',
icon: 'server',
condition: () =>
this.site?.status === 'Active' && !this.site?.is_public,
onClick: () => (this.showChangeServerDialog = true)
}
];
},
hasMonitorAccess() {
return this.$resources.plan.data?.monitor_access;
},
tabs() {
let siteConfig = '';
let siteMonitorTab = '';
let tabRoute = subRoute => `/sites/${this.siteName}/${subRoute}`;
let tabs = [
{ label: 'Overview', route: 'overview' },
{ label: 'Apps', route: 'apps' },
{ label: 'Analytics', route: 'analytics' },
{ label: 'Monitor', route: 'monitor' },
{ label: 'Database', route: 'database' },
{ label: 'Config', route: 'site-config' },
{ label: 'Jobs', route: 'jobs', showRedDot: this.runningJob },
{ label: 'Logs', route: 'logs' },
{ label: 'Settings', route: 'settings' }
];
if (this.site && this.site?.hide_config !== 1) {
siteConfig = 'Config';
}
if (this.site && this.hasMonitorAccess) {
siteMonitorTab = 'Monitor';
}
let tabsByStatus = {
Active: [
'Overview',
'Apps',
'Analytics',
'Database',
siteConfig,
'Jobs',
'Logs',
'Request Logs',
'Settings',
siteMonitorTab
],
Inactive: [
'Overview',
'Apps',
'Database',
siteConfig,
'Jobs',
'Logs',
'Settings'
],
Installing: ['Jobs'],
Pending: ['Jobs'],
Broken: [
'Overview',
'Apps',
siteConfig,
'Database',
'Jobs',
'Logs',
'Settings',
siteMonitorTab
],
Suspended: [
'Overview',
'Apps',
'Database',
'Jobs',
'Plan',
'Logs',
'Settings'
]
};
if (this.site) {
let tabsToShow = tabsByStatus[this.site?.status];
if (tabsToShow?.length) {
tabs = tabs.filter(tab => tabsToShow.includes(tab.label));
}
return tabs.map(tab => {
return {
...tab,
route: tabRoute(tab.route)
};
});
}
return [];
}
}
};
</script>