mirror of
https://github.com/hanzo-io/press.git
synced 2026-10-30 18:56:50 +00:00
479 lines
14 KiB
Vue
479 lines
14 KiB
Vue
<template>
|
|
<div
|
|
v-if="$appServer?.doc"
|
|
class="grid grid-cols-1 items-start gap-5 sm:grid-cols-2"
|
|
>
|
|
<div
|
|
v-for="server in !!$dbReplicaServer?.doc
|
|
? ['Server', 'Database Server', 'Replication Server']
|
|
: ['Server', 'Database Server']"
|
|
class="col-span-1 rounded-md border lg:col-span-2"
|
|
>
|
|
<div class="grid grid-cols-2 lg:grid-cols-4">
|
|
<template v-for="(d, i) in currentUsage(server)" :key="d.value">
|
|
<div
|
|
class="border-b p-5 lg:border-b-0"
|
|
:class="{ 'border-r': i + 1 != currentUsage(server).length }"
|
|
>
|
|
<div
|
|
v-if="d.type === 'header'"
|
|
class="m-auto flex h-full items-center justify-between"
|
|
>
|
|
<div
|
|
v-if="d.type === 'header'"
|
|
class="mt-2 flex flex-col space-y-2"
|
|
>
|
|
<div class="text-base text-gray-700">{{ d.label }}</div>
|
|
<div class="space-y-1">
|
|
<div class="flex items-center text-base text-gray-900">
|
|
{{ d.value }}
|
|
<Tooltip v-if="d.isPremium" text="Premium Server">
|
|
<!-- this icon isn't available in unplugin package yet -->
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="24"
|
|
height="24"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
class="lucide lucide-circle-parking ml-2 h-4 w-4 text-gray-600"
|
|
>
|
|
<circle cx="12" cy="12" r="10" />
|
|
<path d="M9 17V7h4a3 3 0 0 1 0 6H9" />
|
|
</svg>
|
|
</Tooltip>
|
|
</div>
|
|
<div class="flex space-x-1">
|
|
<div class="text-sm text-gray-600" v-html="d.subValue" />
|
|
<Tooltip v-if="d.help" :text="d.help">
|
|
<i-lucide-info class="h-3.5 w-3.5 text-gray-500" />
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
v-if="d.type === 'header' && !$appServer.doc.is_self_hosted"
|
|
@click="showPlanChangeDialog(server)"
|
|
label="Change"
|
|
/>
|
|
</div>
|
|
<div v-else-if="d.type === 'progress'">
|
|
<div class="flex items-center justify-between space-x-2">
|
|
<div class="text-base text-gray-700">{{ d.label }}</div>
|
|
<div v-if="d.actions" class="flex space-x-2">
|
|
<Button v-for="action in d.actions || []" v-bind="action" />
|
|
</div>
|
|
<div v-else class="h-8" />
|
|
</div>
|
|
<div class="mt-2">
|
|
<Progress size="md" :value="d.progress_value || 0" />
|
|
<div class="flex space-x-2">
|
|
<div class="mt-2 flex justify-between">
|
|
<div class="text-sm text-gray-600">
|
|
{{ d.value }}
|
|
</div>
|
|
</div>
|
|
<Tooltip v-if="d.help" :text="d.help">
|
|
<i-lucide-info class="mt-2 h-4 w-4 text-gray-500" />
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="rounded-md border">
|
|
<div class="h-12 border-b px-5 py-4">
|
|
<h2 class="text-lg font-medium text-gray-900">Server Information</h2>
|
|
</div>
|
|
<div>
|
|
<div
|
|
v-for="d in serverInformation"
|
|
:key="d.label"
|
|
class="flex items-center px-5 py-3 last:pb-5 even:bg-gray-50/70"
|
|
>
|
|
<div class="w-1/3 text-base text-gray-700">{{ d.label }}</div>
|
|
<div class="w-2/3 text-base font-medium">{{ d.value }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<ServerLoadAverage :server="server" />
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { toast } from 'vue-sonner';
|
|
import { h, defineAsyncComponent } from 'vue';
|
|
import { getCachedDocumentResource } from 'frappe-ui';
|
|
import { confirmDialog, renderDialog } from '../../utils/components';
|
|
import { getToastErrorMessage } from '../../utils/toast';
|
|
import ServerPlansDialog from './ServerPlansDialog.vue';
|
|
import ServerLoadAverage from './ServerLoadAverage.vue';
|
|
import StorageBreakdownDialog from './StorageBreakdownDialog.vue';
|
|
import { getDocResource } from '../../utils/resource';
|
|
|
|
export default {
|
|
props: ['server'],
|
|
components: {
|
|
ServerLoadAverage,
|
|
ServerPlansDialog,
|
|
StorageBreakdownDialog,
|
|
},
|
|
methods: {
|
|
showPlanChangeDialog(serverType) {
|
|
let ServerPlansDialog = defineAsyncComponent(
|
|
() => import('./ServerPlansDialog.vue'),
|
|
);
|
|
renderDialog(
|
|
h(ServerPlansDialog, {
|
|
server:
|
|
serverType === 'Server'
|
|
? this.$appServer.name
|
|
: this.$dbServer.name,
|
|
serverType,
|
|
}),
|
|
);
|
|
},
|
|
showStorageBreakdownDialog(serverType) {
|
|
let StorageBreakdownDialog = defineAsyncComponent(
|
|
() => import('./StorageBreakdownDialog.vue'),
|
|
);
|
|
renderDialog(
|
|
h(StorageBreakdownDialog, {
|
|
server:
|
|
serverType === 'Server'
|
|
? this.$appServer.name
|
|
: this.$dbServer.name,
|
|
serverType,
|
|
}),
|
|
);
|
|
},
|
|
currentUsage(serverType) {
|
|
if (!this.$appServer?.doc) return [];
|
|
if (!this.$dbServer?.doc) return [];
|
|
|
|
let formatBytes = (v) => this.$format.bytes(v, 0, 2);
|
|
|
|
let doc =
|
|
serverType === 'Server'
|
|
? this.$appServer.doc
|
|
: serverType === 'Database Server'
|
|
? this.$dbServer.doc
|
|
: serverType === 'Replication Server'
|
|
? this.$dbReplicaServer?.doc
|
|
: null;
|
|
|
|
if (!doc) return [];
|
|
|
|
let currentPlan = doc.current_plan;
|
|
let currentUsage = doc.usage;
|
|
let diskSize = doc.disk_size;
|
|
let additionalStorage = diskSize - (currentPlan?.disk || 0);
|
|
let price = 0;
|
|
// not using $format.planTitle cuz of manual calculation of add-on storage plan
|
|
let priceField =
|
|
this.$team.doc.currency === 'INR' ? 'price_inr' : 'price_usd';
|
|
|
|
let planDescription = '';
|
|
if (!currentPlan?.name) {
|
|
planDescription = 'No plan selected';
|
|
} else if (currentPlan.price_usd > 0) {
|
|
price = currentPlan[priceField];
|
|
planDescription = `${this.$format.userCurrency(price, 0)}/mo`;
|
|
} else {
|
|
planDescription = currentPlan.plan_title;
|
|
}
|
|
|
|
return [
|
|
{
|
|
label:
|
|
serverType === 'Server'
|
|
? 'Application Server Plan'
|
|
: serverType === 'Database Server'
|
|
? 'Database Server Plan'
|
|
: 'Replication Server Plan',
|
|
value: planDescription,
|
|
subValue:
|
|
additionalStorage > 0
|
|
? `${this.$format.userCurrency(
|
|
doc.storage_plan[priceField] * additionalStorage,
|
|
0,
|
|
)}/mo`
|
|
: '',
|
|
type: 'header',
|
|
isPremium: !!currentPlan?.premium,
|
|
help:
|
|
additionalStorage > 0
|
|
? `Server Plan: ${this.$format.userCurrency(
|
|
currentPlan[priceField],
|
|
)}/mo & Add-on Storage Plan: ${this.$format.userCurrency(
|
|
doc.storage_plan[priceField] * additionalStorage,
|
|
)}/mo`
|
|
: '',
|
|
},
|
|
{
|
|
label: 'CPU',
|
|
type: 'progress',
|
|
progress_value: currentPlan
|
|
? (currentUsage.vcpu / currentPlan.vcpu) * 100
|
|
: 0,
|
|
value: currentPlan
|
|
? `${(((currentUsage.vcpu || 0) / currentPlan.vcpu) * 100).toFixed(
|
|
2,
|
|
)}% of ${currentPlan.vcpu} ${this.$format.plural(
|
|
currentPlan.vcpu,
|
|
'vCPU',
|
|
'vCPUs',
|
|
)}`
|
|
: '0% vCPU',
|
|
},
|
|
{
|
|
label: 'Memory',
|
|
type: 'progress',
|
|
progress_value: currentPlan
|
|
? (currentUsage.memory / currentPlan.memory) * 100
|
|
: 0,
|
|
value: currentPlan
|
|
? `${formatBytes(currentUsage.memory || 0)} of ${formatBytes(
|
|
currentPlan.memory,
|
|
)}`
|
|
: formatBytes(currentUsage.memory || 0),
|
|
},
|
|
{
|
|
label: 'Storage',
|
|
type: 'progress',
|
|
progress_value: currentPlan
|
|
? (currentUsage.disk / (diskSize ? diskSize : currentPlan.disk)) *
|
|
100
|
|
: 0,
|
|
value: currentPlan
|
|
? `${currentUsage.disk || 0} GB of ${
|
|
diskSize ? diskSize : currentPlan.disk
|
|
} GB`
|
|
: `${currentUsage.disk || 0} GB`,
|
|
help:
|
|
diskSize - (currentPlan?.disk || 0) > 0
|
|
? `Add-on storage: ${diskSize - (currentPlan?.disk || 0)} GB`
|
|
: '',
|
|
actions: [
|
|
{
|
|
label: 'Increase Storage',
|
|
icon: 'plus',
|
|
variant: 'ghost',
|
|
onClick: () => {
|
|
confirmDialog({
|
|
title: 'Increase Storage',
|
|
message: `Enter the disk size you want to increase to the server <b>${
|
|
doc.title || doc.name
|
|
}</b><div class="rounded mt-4 p-2 text-sm text-gray-700 bg-gray-100 border">You will be charged at the rate of <b>${this.$format.userCurrency(
|
|
doc.storage_plan[priceField],
|
|
)}/mo</b> for each additional GB of storage.</div><p class="mt-4 text-sm text-gray-700"><strong>Note</strong>: You can increase the storage size of the server only once in 6 hours.</div>`,
|
|
fields: [
|
|
{
|
|
fieldname: 'storage',
|
|
type: 'select',
|
|
default: 50,
|
|
label: 'Storage (GB)',
|
|
variant: 'outline',
|
|
// options from 5 GB to 500 GB in steps of 5 GB
|
|
options: Array.from({ length: 100 }, (_, i) => ({
|
|
label: `${(i + 1) * 5} GB`,
|
|
value: (i + 1) * 5,
|
|
})),
|
|
},
|
|
],
|
|
onSuccess: ({ hide, values }) => {
|
|
toast.promise(
|
|
this.$appServer.increaseDiskSize.submit(
|
|
{
|
|
server: doc.name,
|
|
increment: Number(values.storage),
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
hide();
|
|
this.$router.push({
|
|
name: 'Server Detail Plays',
|
|
params: { name: this.$appServer.name },
|
|
});
|
|
},
|
|
onError(e) {
|
|
console.error(e);
|
|
},
|
|
},
|
|
),
|
|
{
|
|
loading: 'Increasing disk size...',
|
|
success: 'Disk size is scheduled to increase',
|
|
error: (e) =>
|
|
getToastErrorMessage(
|
|
e,
|
|
'Failed to increase disk size',
|
|
),
|
|
},
|
|
);
|
|
},
|
|
});
|
|
},
|
|
},
|
|
{
|
|
label: 'Configure Auto Increase Storage',
|
|
icon: 'tool',
|
|
variant: 'ghost',
|
|
onClick: () => {
|
|
confirmDialog({
|
|
title: 'Configure Auto Increase Storage',
|
|
message: `<div class="rounded my-4 p-2 text-sm text-gray-700 bg-gray-100 border">
|
|
|
|
This feature automatically increases available storage as it reaches over <b>90%</b> of its capacity. It is enabled by default to avoid server/site downtime.
|
|
|
|
<br><br>
|
|
You can disable this entirely by setting minimum and maximum below to <b>0 GB</b>. But if you do, <strong>we may not be notified of incidents from this server</strong>.
|
|
|
|
<br><br>
|
|
<strong>Note</strong>: Storage can auto increase only once in 6 hours.
|
|
|
|
|
|
</div>Enter the maximum and minimum storage to increase for the server <b>${
|
|
doc.title || doc.name
|
|
}</b>.`,
|
|
fields: [
|
|
{
|
|
fieldname: 'min',
|
|
type: 'select',
|
|
default: String(doc.auto_add_storage_min),
|
|
label: 'Minimum Storage Increase (GB)',
|
|
variant: 'outline',
|
|
// options from 5 GB to 250 GB in steps of 5 GB
|
|
options: Array.from({ length: 51 }, (_, i) => ({
|
|
label: `${i * 5} GB`,
|
|
value: i * 5,
|
|
})),
|
|
},
|
|
{
|
|
fieldname: 'max',
|
|
type: 'select',
|
|
default: String(doc.auto_add_storage_max),
|
|
label: 'Maximum Storage Increase (GB)',
|
|
variant: 'outline',
|
|
// options from 5 GB to 250 GB in steps of 5 GB
|
|
options: Array.from({ length: 51 }, (_, i) => ({
|
|
label: `${i * 5} GB`,
|
|
value: i * 5,
|
|
})),
|
|
},
|
|
],
|
|
onSuccess: ({ hide, values }) => {
|
|
toast.promise(
|
|
this.$appServer.configureAutoAddStorage.submit(
|
|
{
|
|
server: doc.name,
|
|
min: Number(values.min),
|
|
max: Number(values.max),
|
|
},
|
|
{
|
|
onSuccess: () => {
|
|
hide();
|
|
|
|
if (doc.name === this.$appServer.name)
|
|
this.$appServer.reload();
|
|
else if (doc.name === this.$dbServer.name)
|
|
this.$dbServer.reload();
|
|
else if (doc.name === this.$replicationServer.name)
|
|
this.$replicationServer.reload();
|
|
},
|
|
},
|
|
),
|
|
{
|
|
loading: 'Configuring auto increase storage...',
|
|
success: 'Auto increase storage is configured',
|
|
error: (err) => {
|
|
return err.messages.length
|
|
? err.messages.join('/n')
|
|
: err.message ||
|
|
'Failed to configure auto increase storage';
|
|
},
|
|
},
|
|
);
|
|
},
|
|
});
|
|
},
|
|
},
|
|
{
|
|
label: 'Storage Breakdown',
|
|
icon: 'pie-chart',
|
|
variant: 'ghost',
|
|
hidden: serverType !== 'Database Server',
|
|
onClick: () => {
|
|
this.showStorageBreakdownDialog(serverType);
|
|
},
|
|
},
|
|
].filter((e) => e.hidden !== true),
|
|
},
|
|
];
|
|
},
|
|
},
|
|
computed: {
|
|
serverInformation() {
|
|
return [
|
|
{
|
|
label: 'Application server',
|
|
value: this.$appServer.doc.name,
|
|
},
|
|
{
|
|
label: 'Database server',
|
|
value: this.$appServer.doc.database_server,
|
|
},
|
|
{
|
|
label: 'Replication server',
|
|
value: this.$appServer.doc.replication_server,
|
|
},
|
|
{
|
|
label: 'Owned by',
|
|
value: this.$appServer.doc.owner_email || this.$appServer.doc.team,
|
|
},
|
|
{
|
|
label: 'Created by',
|
|
value: this.$appServer.doc.owner,
|
|
},
|
|
{
|
|
label: 'Created on',
|
|
value: this.$format.date(this.$appServer.doc.creation),
|
|
},
|
|
].filter((d) => d.value);
|
|
},
|
|
$appServer() {
|
|
return getCachedDocumentResource('Server', this.server);
|
|
},
|
|
$dbServer() {
|
|
return getDocResource({
|
|
doctype: 'Database Server',
|
|
name: this.$appServer.doc.database_server,
|
|
whitelistedMethods: {
|
|
changePlan: 'change_plan',
|
|
reboot: 'reboot',
|
|
rename: 'rename',
|
|
},
|
|
});
|
|
},
|
|
$dbReplicaServer() {
|
|
return getDocResource({
|
|
doctype: 'Database Server',
|
|
name: this.$appServer.doc.replication_server,
|
|
whitelistedMethods: {
|
|
changePlan: 'change_plan',
|
|
reboot: 'reboot',
|
|
rename: 'rename',
|
|
},
|
|
});
|
|
},
|
|
},
|
|
};
|
|
</script>
|