jcloud/dashboard/src2/views/site/SiteAppsAndSubscriptions.vue

449 lines
11 KiB
Vue

<template>
<Card title="Apps" subtitle="Apps installed on your site">
<template #actions>
<Button
@click="
() => {
showInstallAppsDialog = true;
$resources.availableApps.fetch();
}
"
:disabled="site?.status === 'Suspended'"
>
Add App
</Button>
</template>
<div class="flex text-base text-gray-600">
<span class="w-2/6">App</span>
<span class="hidden w-1/6 md:inline">Plan</span>
<span class="w-1/6">Status</span>
<span class="hidden w-1/6 md:inline">Price</span>
<span></span>
</div>
<LoadingText class="m-2 mt-4" v-if="$resources.installedApps.loading" />
<div v-else class="divide-y">
<div
class="flex items-center py-4 text-base text-gray-600"
v-for="app in $resources.installedApps.data"
:key="app.name"
>
<div class="w-2/6">
<div class="flex flex-row items-center">
<div class="font-medium text-gray-900">
{{ app.title }}
</div>
<Tooltip :text="app.commit_message">
<CommitTag
class="ml-2"
:tag="app.tag || app.hash.substr(0, 7)"
:link="`${app.repository_url}/commit/${app.hash}`"
/>
</Tooltip>
</div>
<div class="mt-1 flex items-center space-x-2">
<FeatherIcon name="git-branch" class="h-4 w-4" />
<div class="truncate text-base text-gray-600 hover:text-clip">
{{ app.repository_owner }}/{{ app.repository }}:{{ app.branch }}
</div>
</div>
</div>
<div class="w-1/6">
<span v-if="app.subscription.plan">
{{ app.plan_info.plan }}
</span>
<span v-else>-</span>
</div>
<div class="w-1/6">
<span v-if="app.subscription.enabled"
><Badge label="Enabled" />
</span>
<span v-else>-</span>
</div>
<div class="w-1/6">
<span v-if="app.plan_info">
{{ app.is_free ? 'Free' : $planTitle(app.plan_info) }}
</span>
<span v-else>-</span>
</div>
<div class="ml-auto flex items-center space-x-2">
<Button v-if="app.plan_info" @click="changeAppPlan(app)"
>Change Plan</Button
>
<Button
v-if="!app.plan_info && app.subscription_available"
@click="
() => {
showPlanSelectionDialog = true;
appToInstall = app;
}
"
>Subscribe</Button
>
<Dropdown :options="dropdownItems(app)" right>
<template v-slot="{ open }">
<Button icon="more-horizontal" />
</template>
</Dropdown>
</div>
</div>
</div>
<Dialog
:options="{
title: 'Install an app on your site',
position: 'top',
size: 'lg'
}"
v-model="showInstallAppsDialog"
>
<template v-slot:body-content>
<FormControl
class="mb-2"
placeholder="Search for Apps"
v-on:input="e => updateSearchTerm(e.target.value)"
/>
<div
v-if="availableApps.data && availableApps.data.length"
class="max-h-96 space-y-3 divide-y overflow-auto p-1"
:class="filteredOptions.length > 7 ? 'pr-2' : ''"
>
<div
class="flex items-center rounded-md border px-4 py-3 shadow ring-1 ring-gray-300"
v-for="app in filteredOptions"
:key="app.name"
>
<div class="flex flex-col">
<div class="w-2/4 text-base font-semibold">
{{ app.title }}
</div>
<div class="w-1/4 text-base text-gray-700">
{{ app.repository_owner }}:{{ app.branch }}
</div>
</div>
<Button
class="ml-auto"
@click="installApp(app)"
:loading="
$resources.installApp.loading && appToInstall.name == app.name
"
>
Install
</Button>
</div>
</div>
<div class="text-base text-gray-600" v-else>
No apps available to install
</div>
<div v-if="site?.group">
<p class="mt-4 text-sm text-gray-700">
<Link :to="`/groups/${site.group}/apps`" class="font-medium">
Add more apps to your bench
</Link>
</p>
</div>
</template>
</Dialog>
<!-- New App Install -->
<Dialog
v-model="showPlanSelectionDialog"
:options="{
title: 'Select app plan',
size: '2xl',
actions: [
{
label: 'Proceed',
variant: 'solid',
onClick: $resources.installApp.submit
}
]
}"
>
<template v-slot:body-content>
<ChangeAppPlanSelector
v-if="appToInstall?.app"
:app="appToInstall.app"
:jingrowVersion="site?.jingrow_version"
class="mb-9"
@change="
plan => {
selectedPlan = plan.name;
selectedPlanIsFree = plan.price_usd === 0;
}
"
/>
<ErrorMessage :message="$resources.installApp.error" />
</template>
</Dialog>
<!-- Plan Change Dialog -->
<Dialog
:options="{
title: 'Select Plan',
size: '2xl',
actions: [
{
label: 'Change Plan',
variant: 'solid',
onClick: switchToNewPlan,
loading: $resources.changePlan.loading
}
]
}"
v-model="showAppPlanChangeDialog"
>
<template v-slot:body-content>
<ChangeAppPlanSelector
@change="
plan => {
newAppPlan = plan.name;
newAppPlanIsFree = plan.is_free;
}
"
v-if="appToChangePlan"
:app="appToChangePlan.name"
:currentPlan="appToChangePlan.plan"
:jingrowVersion="site.jingrow_version"
/>
</template>
</Dialog>
</Card>
</template>
<script>
import CommitTag from '@/components/utils/CommitTag.vue';
import ChangeAppPlanSelector from '@/components/ChangeAppPlanSelector.vue';
import Fuse from 'fuse.js/dist/fuse.basic.esm';
import { notify } from '@/utils/toast';
export default {
name: 'SiteAppsAndSubscriptions',
props: ['site', 'siteName'],
data() {
return {
showInstallAppsDialog: false,
showPlanSelectionDialog: false,
showAppPlanChangeDialog: false,
appToChangePlan: null,
newAppPlan: '',
appToInstall: null,
selectedPlan: null,
selectedPlanIsFree: null,
searchTerm: '',
filteredOptions: []
};
},
components: {
ChangeAppPlanSelector,
CommitTag
},
resources: {
marketplaceSubscriptions() {
return {
url: 'jcloud.api.marketplace.get_marketplace_subscriptions_for_site',
params: {
site: this.siteName
},
auto: true
};
},
changePlan() {
return {
url: 'jcloud.api.marketplace.change_app_plan',
onSuccess() {
this.showAppPlanChangeDialog = false;
this.$resources.marketplaceSubscriptions.fetch();
},
onError(e) {
this.showAppPlanChangeDialog = false;
notify({
title: e,
color: 'red',
icon: 'x'
});
}
};
},
installedApps() {
return {
url: 'jcloud.api.site.installed_apps',
params: { name: this.siteName },
auto: true
};
},
availableApps() {
return {
url: 'jcloud.api.site.available_apps',
params: { name: this.siteName }
};
},
installApp() {
return {
url: 'jcloud.api.site.install_app',
makeParams() {
return {
name: this.siteName,
app: this.appToInstall?.app,
plan: this.selectedPlan
};
},
validate() {
if (this.showPlanSelectionDialog && !this.selectedPlan) {
return 'Please aaa select a plan to continue';
}
},
onSuccess() {
this.showPlanSelectionDialog = false;
this.showInstallAppsDialog = false;
this.$emit('app-installed');
}
};
},
uninstallApp: {
url: 'jcloud.api.site.uninstall_app',
onSuccess() {
this.$emit('app-uninstalled');
}
}
},
computed: {
availableApps() {
if (this.$resources.availableApps.data) {
this.fuse = new Fuse(this.$resources.availableApps.data, {
limit: 20,
keys: ['title']
});
this.filteredOptions = this.$resources.availableApps.data;
}
return this.$resources.availableApps;
},
marketplaceSubscriptions() {
if (
this.$resources.marketplaceSubscriptions.data &&
!this.$resources.marketplaceSubscriptions.loading
) {
return this.$resources.marketplaceSubscriptions.data;
}
return [];
}
},
methods: {
updateSearchTerm(value) {
if (value) {
this.filteredOptions = this.fuse
.search(value)
.map(result => result.item);
} else {
this.filteredOptions = this.$resources.availableApps.data;
}
},
subscribe(app) {
this.showPlanSelectionDialog = true;
},
changeAppPlan(app) {
this.currentSubscription = app.subscription.name;
this.currentAppPlan = app.subscription.marketplace_app_plan;
this.newAppPlan = this.currentAppPlan;
this.appToChangePlan = {
name: app.subscription.document_name,
title: app.app_title,
image: app.app_image,
plan: app.subscription.plan,
subscription: app.subscription.name,
billing_type: app.billing_type
};
this.showAppPlanChangeDialog = true;
},
switchToNewPlan() {
if (!this.$account.hasBillingInfo) {
window.location = '/dashboard-old/billing';
}
if (this.currentAppPlan !== this.newAppPlan) {
this.$resources.changePlan.submit({
subscription: this.appToChangePlan.subscription,
new_plan: this.newAppPlan
});
} else {
this.showAppPlanChangeDialog = false;
}
},
installApp(app) {
this.appToInstall = app;
// If paid app, show plan selection dialog
if (app.has_plans_available) {
this.showInstallAppsDialog = false;
this.showPlanSelectionDialog = true;
return;
}
this.$resources.installApp.submit({
name: this.siteName,
app: this.appToInstall?.app,
plan: this.selectedPlan
});
},
handlePlanSelection() {
this.$resources.installApp.submit();
},
dropdownItems(app) {
return [
{
label: 'View in Desk',
onClick: () =>
window.open(
`${window.location.protocol}//${window.location.host}/app/app/${app.name}`,
'_blank'
),
condition: () => this.$account.user.user_type == 'System User'
},
{
label: 'Remove App',
onClick: () => this.confirmRemoveApp(app),
condition: () => app.app != 'jingrow'
},
{
label: 'Visit Repo',
onClick: () =>
window.open(`${app.repository_url}/tree/${app.branch}`, '_blank')
}
].filter(Boolean);
},
confirmRemoveApp(app) {
this.$confirm({
title: 'Remove App',
message: `Are you sure you want to uninstall app ${app.title} from <b>site</b>?<br><br>
<b>All pagetypes and modules pertaining to this app will be removed.</b>`,
actionLabel: 'Remove App',
actionColor: 'red',
action: closeDialog => {
closeDialog();
this.$resources.uninstallApp.submit({
name: this.site.name,
app: app.app
});
}
});
}
}
};
</script>