558 lines
15 KiB
Vue
558 lines
15 KiB
Vue
<template>
|
|
<div class="space-y-8">
|
|
<Table
|
|
:columns="[
|
|
{ label: 'Site Name', name: 'name', width: 2 },
|
|
{ label: 'Status', name: 'status' },
|
|
{ label: 'Region', name: 'region' },
|
|
{ label: 'Tags', name: 'tags' },
|
|
{ label: 'Plan', name: 'plan' },
|
|
{ label: '', name: 'actions', width: 0.5 }
|
|
]"
|
|
:rows="versions"
|
|
v-slot="{ rows, columns }"
|
|
>
|
|
<TableHeader v-if="rows.length !== 0" class="mb-4 hidden sm:grid" />
|
|
<div class="flex items-center justify-center">
|
|
<LoadingText class="mt-8" v-if="$resources.versions.loading" />
|
|
<div v-else-if="rows.length === 0" class="mt-8">
|
|
<div class="text-base text-gray-700">No Sites</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-for="(group, i) in rows"
|
|
:key="group.name"
|
|
class="mb-4 rounded border"
|
|
>
|
|
<div
|
|
class="flex w-full items-center justify-between rounded-t bg-gray-50 px-3 py-2 text-base"
|
|
>
|
|
<span
|
|
class="cursor-default font-semibold text-gray-900"
|
|
:title="
|
|
group.deployed_on
|
|
? 'Deployed on ' +
|
|
formatDate(group.deployed_on, 'DATETIME_SHORT', true)
|
|
: ''
|
|
"
|
|
>
|
|
{{ group.name }}
|
|
<Badge :label="group.status" class="ml-2" />
|
|
</span>
|
|
<div class="flex items-center space-x-2">
|
|
<Button
|
|
variant="ghost"
|
|
label="Show Apps"
|
|
@click="
|
|
$resources.versionApps.submit({ name: group.name });
|
|
showAppsDialog = true;
|
|
"
|
|
/>
|
|
<Dropdown :options="benchDropdownItems(i)">
|
|
<template v-slot="{ open }">
|
|
<Button variant="ghost">
|
|
<template #icon>
|
|
<FeatherIcon name="more-horizontal" class="h-4 w-4" />
|
|
</template>
|
|
</Button>
|
|
</template>
|
|
</Dropdown>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="!group.sites?.length"
|
|
class="flex items-center justify-center border-b py-4.5"
|
|
>
|
|
<div class="text-base text-gray-600">No Sites</div>
|
|
</div>
|
|
<TableRow
|
|
v-for="(row, index) in group.sites"
|
|
:key="row.name"
|
|
:row="row"
|
|
:class="index === 0 ? 'rounded-b' : 'rounded'"
|
|
>
|
|
<TableCell v-for="column in columns">
|
|
<Badge v-if="column.name === 'status'" :label="$siteStatus(row)" />
|
|
<div
|
|
v-else-if="column.name === 'tags' && row.tags"
|
|
class="hidden space-x-1 sm:flex"
|
|
>
|
|
<Badge
|
|
v-for="tag in row.tags.slice(0, 1)"
|
|
theme="blue"
|
|
:label="tag"
|
|
/>
|
|
<Tooltip
|
|
v-if="row.tags.length > 1"
|
|
:text="row.tags.slice(1).join(', ')"
|
|
>
|
|
<Badge
|
|
v-if="row.tags.length > 1"
|
|
:label="`+${row.tags.length - 1}`"
|
|
/>
|
|
</Tooltip>
|
|
</div>
|
|
<span v-else-if="column.name === 'plan'" class="hidden sm:block">
|
|
{{
|
|
row.plan
|
|
? `${$planTitle(row.plan)}${
|
|
row.plan.price_usd > 0 ? '/mo' : ''
|
|
}`
|
|
: ''
|
|
}}
|
|
</span>
|
|
<div v-else-if="column.name === 'region'" class="hidden sm:block">
|
|
<img
|
|
v-if="row.server_region_info?.image"
|
|
class="h-4"
|
|
:src="row.server_region_info.image"
|
|
:alt="`Flag of ${row.server_region_info.title}`"
|
|
:title="row.server_region_info.title"
|
|
/>
|
|
<span class="text-base text-gray-700" v-else>
|
|
{{ row.server_region_info?.title }}
|
|
</span>
|
|
</div>
|
|
<div class="w-full text-right" v-else-if="column.name == 'actions'">
|
|
<Dropdown @click.prevent :options="dropdownItems(row)">
|
|
<template v-slot="{ open }">
|
|
<Button
|
|
:variant="open ? 'subtle' : 'ghost'"
|
|
icon="more-horizontal"
|
|
/>
|
|
</template>
|
|
</Dropdown>
|
|
</div>
|
|
<span v-else>{{ row[column.name] || '' }}</span>
|
|
</TableCell>
|
|
</TableRow>
|
|
</div>
|
|
</Table>
|
|
</div>
|
|
|
|
<Dialog :options="{ title: 'Apps', size: 'xl' }" v-model="showAppsDialog">
|
|
<template #body-content>
|
|
<ListItem
|
|
class="mb-3 flex items-center rounded-md border px-4 py-3 shadow ring-1 ring-gray-300"
|
|
v-for="app in $resources.versionApps.data"
|
|
:key="app.app"
|
|
:title="app.app"
|
|
>
|
|
<template #subtitle>
|
|
<div class="mt-1 flex items-center space-x-2 text-gray-600">
|
|
<FeatherIcon name="git-branch" class="h-4 w-4" />
|
|
<div class="truncate text-base hover:text-clip">
|
|
{{ app.repository_owner }}/{{ app.repository }}:{{ app.branch }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #actions>
|
|
<CommitTag
|
|
:tag="app.tag || app.hash.substr(0, 7)"
|
|
class="ml-2"
|
|
:link="`${app.repository_url}/commit/${app.hash}`"
|
|
/>
|
|
</template>
|
|
</ListItem>
|
|
<LoadingText
|
|
class="justify-center"
|
|
v-if="$resources.versionApps.loading"
|
|
/>
|
|
</template>
|
|
</Dialog>
|
|
|
|
<Dialog
|
|
:options="{
|
|
title: 'Login As Administrator',
|
|
actions: [
|
|
{
|
|
label: 'Proceed',
|
|
variant: 'solid',
|
|
onClick: proceedWithLoginAsAdmin
|
|
}
|
|
]
|
|
}"
|
|
v-model="showReasonForAdminLoginDialog"
|
|
>
|
|
<template #body-content>
|
|
<FormControl
|
|
label="Reason for logging in as Administrator"
|
|
type="textarea"
|
|
v-model="reasonForAdminLogin"
|
|
required
|
|
/>
|
|
<ErrorMessage class="mt-3" :message="errorMessage" />
|
|
</template>
|
|
</Dialog>
|
|
|
|
<Dialog :options="{ title: 'SSH Access' }" v-model="showSSHDialog">
|
|
<template v-slot:body-content>
|
|
<div v-if="certificate" class="space-y-4" style="max-width: 29rem">
|
|
<div class="space-y-2">
|
|
<h4 class="text-base font-semibold text-gray-700">Step 1</h4>
|
|
<div class="space-y-1">
|
|
<p class="text-base">
|
|
Execute the following shell command to store the SSH certificate
|
|
locally.
|
|
</p>
|
|
<ClickToCopyField :textContent="certificateCommand" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
<h4 class="text-base font-semibold text-gray-700">Step 2</h4>
|
|
<div class="space-y-1">
|
|
<p class="text-base">
|
|
Execute the following shell command to SSH into your bench
|
|
</p>
|
|
<ClickToCopyField :textContent="sshCommand" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-if="!certificate">
|
|
<p class="mb-4 text-base">
|
|
You will need an SSH certificate to get SSH access to your bench. This
|
|
certificate will work only with your public-private key pair and will
|
|
be valid for 6 hours.
|
|
</p>
|
|
<p class="text-base">
|
|
Please refer to the
|
|
<a href="/docs/benches/ssh" class="underline"
|
|
>SSH Access documentation</a
|
|
>
|
|
for more details.
|
|
</p>
|
|
</div>
|
|
</template>
|
|
<template #actions v-if="!certificate">
|
|
<Button
|
|
:loading="$resources.generateCertificate.loading"
|
|
@click="$resources.generateCertificate.fetch()"
|
|
variant="solid"
|
|
class="w-full"
|
|
>Generate SSH Certificate</Button
|
|
>
|
|
</template>
|
|
<ErrorMessage
|
|
class="mt-3"
|
|
:message="$resources.generateCertificate.error"
|
|
/>
|
|
</Dialog>
|
|
<CodeServer
|
|
:show="showCodeServerDialog"
|
|
@close="showCodeServerDialog = false"
|
|
:version="versions[selectedVersionIndex]?.name"
|
|
/>
|
|
</template>
|
|
<script>
|
|
import { loginAsAdmin } from '@/controllers/loginAsAdmin';
|
|
import Table from '@/components/Table/Table.vue';
|
|
import TableHeader from '@/components/Table/TableHeader.vue';
|
|
import TableRow from '@/components/Table/TableRow.vue';
|
|
import TableCell from '@/components/Table/TableCell.vue';
|
|
import CommitTag from '@/components/utils/CommitTag.vue';
|
|
import CodeServer from '@/views/spaces/CreateCodeServerDialog.vue';
|
|
import ClickToCopyField from '@/components/ClickToCopyField.vue';
|
|
import { notify } from '@/utils/toast';
|
|
|
|
export default {
|
|
name: 'BenchSites',
|
|
props: ['bench', 'benchName'],
|
|
components: {
|
|
Table,
|
|
TableHeader,
|
|
TableRow,
|
|
TableCell,
|
|
ClickToCopyField,
|
|
CommitTag,
|
|
CodeServer
|
|
},
|
|
data() {
|
|
return {
|
|
reasonForAdminLogin: '',
|
|
errorMessage: null,
|
|
selectedVersionIndex: 0,
|
|
showSSHDialog: false,
|
|
showCodeServerDialog: false,
|
|
showAppsDialog: false,
|
|
showReasonForAdminLoginDialog: false,
|
|
siteForLogin: null
|
|
};
|
|
},
|
|
resources: {
|
|
versions() {
|
|
return {
|
|
url: 'jcloud.api.bench.versions',
|
|
params: {
|
|
name: this.benchName
|
|
},
|
|
auto: true
|
|
};
|
|
},
|
|
versionApps() {
|
|
return {
|
|
url: 'jcloud.api.bench.get_installed_apps_in_version'
|
|
};
|
|
},
|
|
loginAsAdmin() {
|
|
return loginAsAdmin('placeholderSite'); // So that RM does not yell at first load
|
|
},
|
|
getCertificate() {
|
|
return {
|
|
url: 'jcloud.api.bench.certificate',
|
|
params: { name: this.benchName },
|
|
auto: true
|
|
};
|
|
},
|
|
generateCertificate() {
|
|
return {
|
|
url: 'jcloud.api.bench.generate_certificate',
|
|
params: { name: this.bench?.name },
|
|
onSuccess() {
|
|
this.$resources.getCertificate.reload();
|
|
}
|
|
};
|
|
},
|
|
restartBench() {
|
|
return {
|
|
url: 'jcloud.api.bench.restart',
|
|
params: {
|
|
name: this.versions[this.selectedVersionIndex]?.name
|
|
}
|
|
};
|
|
},
|
|
rebuildBench() {
|
|
return {
|
|
url: 'jcloud.api.bench.rebuild',
|
|
params: {
|
|
name: this.versions[this.selectedVersionIndex]?.name
|
|
}
|
|
};
|
|
},
|
|
updateAllSites() {
|
|
return {
|
|
url: 'jcloud.api.bench.update',
|
|
onSuccess() {
|
|
notify({
|
|
title: 'Site update scheduled successfully',
|
|
message: `All sites in ${
|
|
this.versions[this.selectedVersionIndex]?.name
|
|
} will be updated to the latest version`,
|
|
icon: 'check',
|
|
color: 'green'
|
|
});
|
|
},
|
|
onError(e) {
|
|
notify({
|
|
title: 'Error',
|
|
message: e.messages.join(', '),
|
|
icon: 'x',
|
|
color: 'red'
|
|
});
|
|
}
|
|
};
|
|
}
|
|
},
|
|
methods: {
|
|
dropdownItems(site) {
|
|
return [
|
|
{
|
|
label: 'Visit Site',
|
|
onClick: () => {
|
|
window.open(`https://${site.name}`, '_blank');
|
|
}
|
|
},
|
|
{
|
|
label: 'Login As Admin',
|
|
onClick: () => {
|
|
if (this.$account.team.name === site.team) {
|
|
return this.$resources.loginAsAdmin.submit({
|
|
name: site.name
|
|
});
|
|
}
|
|
|
|
this.siteForLogin = site.name;
|
|
this.showReasonForAdminLoginDialog = true;
|
|
}
|
|
}
|
|
];
|
|
},
|
|
benchDropdownItems(i) {
|
|
return [
|
|
{
|
|
label: 'View in Desk',
|
|
onClick: () => {
|
|
window.open(
|
|
`${window.location.protocol}//${window.location.host}/app/bench/${this.versions[i].name}`,
|
|
'_blank'
|
|
);
|
|
},
|
|
condition: () => this.$account.user.user_type === 'System User'
|
|
},
|
|
{
|
|
label: 'SSH Access',
|
|
onClick: () => {
|
|
this.selectedVersionIndex = i;
|
|
this.showSSHDialog = true;
|
|
},
|
|
condition: () =>
|
|
this.versions[i].status === 'Active' &&
|
|
this.$account.ssh_key &&
|
|
this.versions[i].is_ssh_proxy_setup &&
|
|
this.permissions.sshAccess
|
|
},
|
|
{
|
|
label: 'View Logs',
|
|
onClick: () => {
|
|
this.$router.push(
|
|
`/groups/${this.bench.name}/logs/${this.versions[i].name}/`
|
|
);
|
|
},
|
|
condition: () => this.versions[i].status === 'Active'
|
|
},
|
|
{
|
|
label: 'Update All Sites',
|
|
onClick: () => {
|
|
this.$resources.updateAllSites.submit({
|
|
name: this.versions[i]?.name
|
|
});
|
|
},
|
|
condition: () =>
|
|
this.versions[i].status === 'Active' &&
|
|
i > 0 &&
|
|
this.versions[i].sites.length > 0
|
|
},
|
|
{
|
|
label: 'Restart Bench',
|
|
onClick: () => {
|
|
this.selectedVersionIndex = i;
|
|
this.confirmRestart();
|
|
},
|
|
condition: () =>
|
|
this.versions[i].status === 'Active' &&
|
|
this.permissions.restartBench
|
|
},
|
|
{
|
|
label: 'Build Assets',
|
|
onClick: () => {
|
|
this.selectedVersionIndex = i;
|
|
this.confirmRebuild();
|
|
},
|
|
condition: () =>
|
|
this.versions[i].status === 'Active' &&
|
|
(Number(this.versions[i].version.split(' ')[1] > 13) ||
|
|
this.versions[i].version === 'v0.1') &&
|
|
this.permissions.rebuildBench
|
|
},
|
|
{
|
|
label: 'Create Code Server',
|
|
onClick: () => {
|
|
this.selectedVersionIndex = i;
|
|
this.showCodeServerDialog = true;
|
|
},
|
|
condition: () => this.$account.team.code_servers_enabled
|
|
}
|
|
].filter(d => (d.condition ? d.condition() : true));
|
|
},
|
|
proceedWithLoginAsAdmin() {
|
|
this.errorMessage = '';
|
|
|
|
if (!this.reasonForAdminLogin.trim()) {
|
|
this.errorMessage = 'Reason is required';
|
|
return;
|
|
}
|
|
|
|
this.$resources.loginAsAdmin.submit({
|
|
name: this.siteForLogin,
|
|
reason: this.reasonForAdminLogin
|
|
});
|
|
|
|
this.showReasonForAdminLoginDialog = false;
|
|
},
|
|
confirmRestart() {
|
|
this.$confirm({
|
|
title: 'Restart Bench',
|
|
message: `
|
|
<b>bench restart</b> command will be executed on your bench. This will temporarily stop all web and backgound workers. Are you sure
|
|
you want to run this command?
|
|
`,
|
|
actionLabel: 'Restart Bench',
|
|
actionColor: 'red',
|
|
action: closeDialog => {
|
|
this.$resources.restartBench.submit();
|
|
closeDialog();
|
|
}
|
|
});
|
|
},
|
|
confirmRebuild() {
|
|
this.$confirm({
|
|
title: 'Build Assets',
|
|
message: `
|
|
<b>bench build</b> command will be executed on your bench. This will regenerate all static assets. Are you sure
|
|
you want to run this command?
|
|
`,
|
|
actionLabel: 'Build Assets',
|
|
actionColor: 'red',
|
|
action: closeDialog => {
|
|
this.$resources.rebuildBench.submit();
|
|
closeDialog();
|
|
}
|
|
});
|
|
}
|
|
},
|
|
computed: {
|
|
permissions() {
|
|
return {
|
|
restartBench: this.$account.hasPermission(
|
|
this.benchName,
|
|
'jcloud.api.bench.restart'
|
|
),
|
|
rebuildBench: this.$account.hasPermission(
|
|
this.benchName,
|
|
'jcloud.api.bench.rebuild'
|
|
),
|
|
sshAccess: this.$account.hasPermission(
|
|
this.benchName,
|
|
'jcloud.api.bench.generate_certificate'
|
|
)
|
|
};
|
|
},
|
|
versions() {
|
|
if (!this.$resources.versions.data) return [];
|
|
|
|
for (let version of this.$resources.versions.data) {
|
|
for (let site of version.sites) {
|
|
site.route = {
|
|
name: 'SiteOverview',
|
|
params: {
|
|
siteName: site.name
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
return this.$resources.versions.data;
|
|
},
|
|
certificate() {
|
|
return this.$resources.getCertificate.data;
|
|
},
|
|
sshCommand() {
|
|
if (this.versions[this.selectedVersionIndex]) {
|
|
return `ssh ${this.versions[this.selectedVersionIndex]?.name}@${
|
|
this.versions[this.selectedVersionIndex]?.proxy_server
|
|
} -p 2222`;
|
|
}
|
|
return null;
|
|
},
|
|
certificateCommand() {
|
|
if (this.certificate) {
|
|
return `echo '${this.certificate.ssh_certificate?.trim()}' > ~/.ssh/id_${
|
|
this.certificate.key_type
|
|
}-cert.pub`;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
};
|
|
</script>
|