506 lines
16 KiB
HTML
506 lines
16 KiB
HTML
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
|
%} {{ window_vars(user, extensions) }}{% block page %}
|
|
<div class="row q-col-gutter-md q-mb-md">
|
|
<div class="col-sm-9 col-xs-12">
|
|
<p class="text-h4 gt-sm">Manage Extensions</p>
|
|
</div>
|
|
|
|
<div class="col-sm-3 col-xs-12 q-ml-auto">
|
|
<q-input v-model="searchTerm" label="Search extensions">
|
|
<q-icon
|
|
v-if="searchTerm !== ''"
|
|
name="close"
|
|
@click="searchTerm = ''"
|
|
class="cursor-pointer"
|
|
/>
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row q-col-gutter-md q-mb-md">
|
|
<div class="col-12">
|
|
<q-card>
|
|
<div class="q-pa-xs">
|
|
<div class="q-gutter-y-md">
|
|
<q-tabs
|
|
v-model="tab"
|
|
@input="handleTabChanged"
|
|
active-color="primary"
|
|
align="left"
|
|
>
|
|
<q-tab
|
|
name="installed"
|
|
label="Installed"
|
|
@update="val => tab = val.name"
|
|
></q-tab>
|
|
<q-tab
|
|
name="all"
|
|
label="All"
|
|
@update="val => tab = val.name"
|
|
></q-tab>
|
|
<q-tab
|
|
name="featured"
|
|
label="Featured"
|
|
@update="val => tab = val.name"
|
|
></q-tab>
|
|
</q-tabs>
|
|
</div>
|
|
</div>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
<div class="row q-col-gutter-md">
|
|
<div
|
|
class="col-6 col-md-4 col-lg-3"
|
|
v-for="extension in filteredExtensions"
|
|
:key="extension.id + extension.hash"
|
|
>
|
|
<q-card>
|
|
<q-card-section style="min-height: 140px">
|
|
<div class="row">
|
|
<div class="col-3">
|
|
<q-img
|
|
v-if="extension.icon"
|
|
:src="extension.icon"
|
|
spinner-color="white"
|
|
style="max-width: 100%"
|
|
></q-img>
|
|
<div v-else>
|
|
<q-icon
|
|
class="gt-sm"
|
|
name="extension"
|
|
color="primary"
|
|
size="70px"
|
|
></q-icon>
|
|
<q-icon
|
|
class="lt-md"
|
|
name="extension"
|
|
color="primary"
|
|
size="35px"
|
|
></q-icon>
|
|
</div>
|
|
</div>
|
|
<div class="col-9 q-pl-sm">
|
|
<q-badge
|
|
v-if="hasNewVersion(extension)"
|
|
color="green"
|
|
class="float-right"
|
|
>
|
|
<small>New Version</small>
|
|
</q-badge>
|
|
{% raw %}
|
|
<div class="text-h5 gt-sm q-mt-sm q-mb-xs gt-sm">
|
|
{{ extension.name }}
|
|
</div>
|
|
<div
|
|
class="text-h5 gt-sm q-mt-sm q-mb-xs lt-md"
|
|
style="min-height: 60px"
|
|
>
|
|
{{ extension.name }}
|
|
</div>
|
|
<div
|
|
class="text-subtitle2 gt-sm"
|
|
style="font-size: 11px; height: 34px"
|
|
>
|
|
{{ extension.shortDescription }}
|
|
</div>
|
|
<div class="text-subtitle1 lt-md q-mt-sm q-mb-xs">
|
|
{{ extension.name }}
|
|
</div>
|
|
<div
|
|
class="text-subtitle2 lt-md"
|
|
style="font-size: 9px; height: 34px"
|
|
>
|
|
{{ extension.shortDescription }}
|
|
</div>
|
|
{% endraw %}
|
|
</div>
|
|
</div>
|
|
<div class="row q-pt-sm">
|
|
<div class="col">
|
|
<small v-if="extension.dependencies?.length">Depends on:</small>
|
|
<small v-else> </small>
|
|
<q-badge
|
|
v-for="dep in extension.dependencies"
|
|
:key="dep"
|
|
color="orange"
|
|
>
|
|
<small v-text="dep"></small>
|
|
</q-badge>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
<q-card-section>
|
|
<div>
|
|
<q-rating
|
|
class="gt-sm"
|
|
v-model="maxStars"
|
|
disable
|
|
size="1.5em"
|
|
:max="5"
|
|
color="primary"
|
|
></q-rating>
|
|
<q-rating
|
|
v-model="maxStars"
|
|
class="lt-md"
|
|
size="1.5em"
|
|
:max="5"
|
|
color="primary"
|
|
></q-rating
|
|
><q-tooltip>Ratings coming soon</q-tooltip>
|
|
</div>
|
|
</q-card-section>
|
|
<q-separator v-if="g.user.admin"></q-separator>
|
|
<q-card-actions>
|
|
<div class="col-10">
|
|
<div v-if="g.user.admin">
|
|
<div v-if="!extension.inProgress">
|
|
<q-btn @click="showUpgrade(extension)" flat color="primary">
|
|
Manage</q-btn
|
|
>
|
|
<q-toggle
|
|
v-if="extension.isAvailable && extension.isInstalled"
|
|
:label="extension.isActive ? 'Activated': 'Deactivated' "
|
|
color="secodary"
|
|
v-model="extension.isActive"
|
|
@input="toggleExtension(extension)"
|
|
></q-toggle>
|
|
</div>
|
|
<div v-else>
|
|
<q-spinner color="primary" size="2.55em"></q-spinner>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-2">
|
|
<div class="float-right"></div>
|
|
</div>
|
|
</q-card-actions>
|
|
</q-card>
|
|
</div>
|
|
<q-dialog v-model="showUninstallDialog">
|
|
<q-card class="q-pa-lg">
|
|
<h6 class="q-my-md text-primary">Warning</h6>
|
|
<p>
|
|
You are about to remove the extension for all users. <br />
|
|
Are you sure you want to continue?
|
|
</p>
|
|
|
|
<div class="row q-mt-lg">
|
|
<q-btn outline color="grey" @click="uninstallExtension()"
|
|
>Yes, Uninstall</q-btn
|
|
>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
|
</div>
|
|
</q-card>
|
|
</q-dialog>
|
|
|
|
<q-dialog v-model="showUpgradeDialog">
|
|
<q-card class="q-pa-lg lnbits__dialog-card">
|
|
<q-card-section>
|
|
<div class="text-h6" v-text="selectedExtension?.name"></div>
|
|
</q-card-section>
|
|
<div class="col-12 col-md-5 q-gutter-y-md" v-if="selectedExtensionRepos">
|
|
<q-card
|
|
flat
|
|
bordered
|
|
class="my-card"
|
|
v-for="repoName of Object.keys(selectedExtensionRepos)"
|
|
:key="repoName"
|
|
>
|
|
<q-expansion-item
|
|
:key="repoName"
|
|
group="repos"
|
|
:caption="repoName"
|
|
:content-inset-level="0.5"
|
|
:default-opened="selectedExtensionRepos[repoName].isInstalled"
|
|
>
|
|
<template v-slot:header>
|
|
<q-item-section avatar>
|
|
<q-avatar
|
|
:icon="selectedExtensionRepos[repoName].isInstalled ? 'download_done': 'download'"
|
|
:text-color="selectedExtensionRepos[repoName].isInstalled ? 'green' : ''"
|
|
/>
|
|
</q-item-section>
|
|
|
|
<q-item-section>
|
|
<div class="row">
|
|
<div class="col-10">
|
|
Repository
|
|
<br />
|
|
<small v-text="repoName"></small>
|
|
</div>
|
|
<div class="col-2">
|
|
<!-- <div v-if="selectedExtension.stars" class="float-right">
|
|
<small v-text="selectedExtension.stars"> </small>
|
|
<q-rating
|
|
max="1"
|
|
v-model="maxStars"
|
|
size="1.5em"
|
|
color="yellow"
|
|
icon="star"
|
|
icon-selected="star"
|
|
readonly
|
|
no-dimming
|
|
>
|
|
</q-rating>
|
|
</div> -->
|
|
</div>
|
|
</div>
|
|
</q-item-section>
|
|
</template>
|
|
|
|
<q-card-section class="q-pa-none">
|
|
<q-separator></q-separator>
|
|
|
|
<q-list>
|
|
<q-expansion-item
|
|
v-for="release of selectedExtensionRepos[repoName].releases"
|
|
:key="release.version"
|
|
group="releases"
|
|
:icon="release.isInstalled ? 'download_done' : 'download'"
|
|
:label="release.description"
|
|
:caption="release.version"
|
|
:content-inset-level="0.5"
|
|
:header-class="release.isInstalled ? 'text-green' : ''"
|
|
>
|
|
<q-card>
|
|
<q-card-section>
|
|
<q-btn
|
|
v-if="!release.isInstalled"
|
|
@click="installExtension(release)"
|
|
color="primary unelevated mt-lg pt-lg"
|
|
>Install</q-btn
|
|
>
|
|
<q-btn v-else @click="showUninstall()" flat color="red">
|
|
Uninstall</q-btn
|
|
>
|
|
<a
|
|
v-if="release.html_url"
|
|
class="text-secondary float-right"
|
|
:href="release.html_url"
|
|
target="_blank"
|
|
style="color: inherit"
|
|
>Release Notes</a
|
|
>
|
|
</q-card-section>
|
|
|
|
<div
|
|
v-if="release.details_html"
|
|
v-html="release.details_html"
|
|
></div>
|
|
<q-separator></q-separator> </q-card
|
|
></q-expansion-item>
|
|
</q-list>
|
|
</q-card-section>
|
|
</q-expansion-item>
|
|
</q-card>
|
|
</div>
|
|
<q-spinner v-else color="primary" size="2.55em"></q-spinner>
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
v-if="selectedExtension?.isInstalled"
|
|
@click="showUninstall()"
|
|
flat
|
|
color="red"
|
|
>
|
|
Uninstall</q-btn
|
|
>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
|
</div>
|
|
</q-card>
|
|
</q-dialog>
|
|
</div>
|
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
<script>
|
|
new Vue({
|
|
el: '#vue',
|
|
data: function () {
|
|
return {
|
|
searchTerm: '',
|
|
tab: 'all',
|
|
filteredExtensions: null,
|
|
showUninstallDialog: false,
|
|
showUpgradeDialog: false,
|
|
selectedExtension: null,
|
|
selectedExtensionRepos: null,
|
|
maxStars: 5
|
|
}
|
|
},
|
|
watch: {
|
|
searchTerm(term) {
|
|
this.filterExtensions(term, this.tab)
|
|
}
|
|
},
|
|
methods: {
|
|
handleTabChanged: function (tab) {
|
|
this.filterExtensions(this.searchTerm, tab)
|
|
},
|
|
filterExtensions: function (term, tab) {
|
|
// Filter the extensions list
|
|
function extensionNameContains(searchTerm) {
|
|
return function (extension) {
|
|
return (
|
|
extension.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
extension.shortDescription
|
|
?.toLowerCase()
|
|
.includes(searchTerm.toLowerCase())
|
|
)
|
|
}
|
|
}
|
|
|
|
this.filteredExtensions = this.extensions
|
|
.filter(e => (tab === 'installed' ? e.isInstalled : true))
|
|
.filter(e => (tab === 'featured' ? e.isFeatured : true))
|
|
.filter(extensionNameContains(term))
|
|
},
|
|
installExtension: async function (release) {
|
|
const extension = this.selectedExtension
|
|
extension.inProgress = true
|
|
this.showUpgradeDialog = false
|
|
LNbits.api
|
|
.request(
|
|
'POST',
|
|
`/api/v1/extension?usr=${this.g.user.id}`,
|
|
this.g.user.wallets[0].adminkey,
|
|
{
|
|
ext_id: extension.id,
|
|
archive: release.archive,
|
|
source_repo: release.source_repo
|
|
}
|
|
)
|
|
.then(response => {
|
|
extension.isAvailable = true
|
|
extension.isInstalled = true
|
|
this.toggleExtension(extension)
|
|
extension.inProgress = false
|
|
this.filteredExtensions = this.extensions.concat([])
|
|
this.handleTabChanged('installed')
|
|
this.tab = 'installed'
|
|
})
|
|
.catch(err => {
|
|
LNbits.utils.notifyApiError(err)
|
|
extension.inProgress = false
|
|
})
|
|
},
|
|
uninstallExtension: async function () {
|
|
const extension = this.selectedExtension
|
|
this.showUpgradeDialog = false
|
|
this.showUninstallDialog = false
|
|
extension.inProgress = true
|
|
LNbits.api
|
|
.request(
|
|
'DELETE',
|
|
`/api/v1/extension/${extension.id}?usr=${this.g.user.id}`,
|
|
this.g.user.wallets[0].adminkey
|
|
)
|
|
.then(response => {
|
|
extension.isAvailable = false
|
|
extension.isInstalled = false
|
|
extension.inProgress = false
|
|
this.filteredExtensions = this.extensions.concat([])
|
|
this.handleTabChanged('installed')
|
|
this.tab = 'installed'
|
|
})
|
|
.catch(err => {
|
|
LNbits.utils.notifyApiError(err)
|
|
extension.inProgress = false
|
|
})
|
|
},
|
|
toggleExtension: function (extension) {
|
|
const action = extension.isActive ? 'activate' : 'deactivate'
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
"{{ url_for('install.extensions') }}?usr=" +
|
|
this.g.user.id +
|
|
'&' +
|
|
action +
|
|
'=' +
|
|
extension.id
|
|
)
|
|
.then(response => {})
|
|
.catch(err => {
|
|
LNbits.utils.notifyApiError(err)
|
|
extension.inProgress = false
|
|
})
|
|
},
|
|
|
|
showUninstall: function () {
|
|
this.showUpgradeDialog = false
|
|
this.showUninstallDialog = true
|
|
},
|
|
|
|
showUpgrade: async function (extension) {
|
|
this.selectedExtension = extension
|
|
this.showUpgradeDialog = true
|
|
this.selectedExtensionRepos = null
|
|
try {
|
|
const {data} = await LNbits.api.request(
|
|
'GET',
|
|
`/api/v1/extension/${extension.id}/releases?usr=${this.g.user.id}`,
|
|
this.g.user.wallets[0].adminkey
|
|
)
|
|
|
|
this.selectedExtensionRepos = data.reduce((repos, release) => {
|
|
repos[release.source_repo] = repos[release.source_repo] || {
|
|
releases: [],
|
|
isInstalled: false
|
|
}
|
|
release.isInstalled = this.isInstalledVersion(
|
|
this.selectedExtension,
|
|
release
|
|
)
|
|
if (release.isInstalled) {
|
|
repos[release.source_repo].isInstalled = true
|
|
}
|
|
repos[release.source_repo].releases.push(release)
|
|
return repos
|
|
}, {})
|
|
} catch (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
extension.inProgress = false
|
|
}
|
|
},
|
|
hasNewVersion: function (extension) {
|
|
if (extension.installedRelease && extension.latestRelease) {
|
|
return (
|
|
extension.installedRelease.version !==
|
|
extension.latestRelease.version
|
|
)
|
|
}
|
|
},
|
|
isInstalledVersion: function (extension, release) {
|
|
if (extension.installedRelease) {
|
|
return (
|
|
extension.installedRelease.source_repo === release.source_repo &&
|
|
extension.installedRelease.version === release.version
|
|
)
|
|
}
|
|
}
|
|
},
|
|
created: function () {
|
|
if (!this.g.user.admin) {
|
|
this.$q.notify({
|
|
timeout: 3000,
|
|
message: 'Only admin accounts can install extensions',
|
|
icon: null
|
|
})
|
|
}
|
|
this.extensions = JSON.parse('{{extensions | tojson | safe}}').map(e => ({
|
|
...e,
|
|
inProgress: false
|
|
}))
|
|
this.filteredExtensions = this.extensions.concat([])
|
|
for (let i = 0; i < this.filteredExtensions.length; i++) {
|
|
if (this.filteredExtensions[i].isInstalled != false) {
|
|
this.handleTabChanged('installed')
|
|
this.tab = 'installed'
|
|
}
|
|
}
|
|
},
|
|
mixins: [windowMixin]
|
|
})
|
|
</script>
|
|
{% endblock %}
|