449 lines
11 KiB
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>
|