1405 lines
55 KiB
Vue
1405 lines
55 KiB
Vue
<template id="page-account">
|
|
<div class="row q-col-gutter-md q-mb-md">
|
|
<div class="col-12">
|
|
<q-card>
|
|
<div class="q-pa-sm">
|
|
<div class="row items-center justify-between q-gutter-xs">
|
|
<div class="col">
|
|
<q-btn @click="updateAccount" unelevated color="primary">
|
|
<q-badge
|
|
v-if="isUserTouched"
|
|
color="negative"
|
|
size="xs"
|
|
floating
|
|
></q-badge>
|
|
<span v-text="$t('update_account')"></span>
|
|
</q-btn>
|
|
<q-badge v-if="isUserTouched" class="q-ml-sm" color="primary">
|
|
<span v-text="$t('must_save')"></span>
|
|
</q-badge>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row q-col-gutter-md q-mb-md">
|
|
<div class="col-12">
|
|
<q-card>
|
|
<q-splitter>
|
|
<template v-slot:before>
|
|
<!-- todo: small screen as in settings -->
|
|
<q-tabs v-model="tab" vertical active-color="primary">
|
|
<q-tab
|
|
name="user"
|
|
icon="person"
|
|
:label="$q.screen.gt.sm ? $t('account_settings') : ''"
|
|
>
|
|
<q-tooltip v-if="!$q.screen.gt.sm"
|
|
><span v-text="$t('account_settings')"></span
|
|
></q-tooltip>
|
|
</q-tab>
|
|
|
|
<q-tab
|
|
name="notifications"
|
|
icon="notifications"
|
|
:label="$q.screen.gt.sm ? $t('notifications') : ''"
|
|
>
|
|
<q-tooltip v-if="!$q.screen.gt.sm"
|
|
><span v-text="$t('notifications')"></span
|
|
></q-tooltip>
|
|
</q-tab>
|
|
<q-tab
|
|
name="theme"
|
|
icon="palette"
|
|
:label="$q.screen.gt.sm ? $t('look_and_feel') : ''"
|
|
>
|
|
<q-tooltip v-if="!$q.screen.gt.sm"
|
|
><span v-text="$t('look_and_feel')"></span
|
|
></q-tooltip>
|
|
</q-tab>
|
|
<q-tab
|
|
name="api_acls"
|
|
icon="lock"
|
|
:label="$q.screen.gt.sm ? $t('access_control_list') : ''"
|
|
>
|
|
<q-tooltip v-if="!$q.screen.gt.sm"
|
|
><span v-text="$t('access_control_list')"></span
|
|
></q-tooltip>
|
|
</q-tab>
|
|
<q-tab
|
|
name="assets"
|
|
icon="perm_media"
|
|
:label="$q.screen.gt.sm ? $t('assets') : ''"
|
|
>
|
|
<q-tooltip v-if="!$q.screen.gt.sm"
|
|
><span v-text="$t('assets')"></span
|
|
></q-tooltip>
|
|
</q-tab>
|
|
<q-tab
|
|
name="labels"
|
|
icon="local_offer"
|
|
:label="$q.screen.gt.sm ? $t('labels') : ''"
|
|
>
|
|
<q-tooltip v-if="!$q.screen.gt.sm"
|
|
><span v-text="$t('labels')"></span
|
|
></q-tooltip>
|
|
</q-tab>
|
|
</q-tabs>
|
|
</template>
|
|
<template v-slot:after>
|
|
<q-scroll-area style="height: 80vh">
|
|
<q-tab-panels v-if="g.user" v-model="tab">
|
|
<q-tab-panel name="user">
|
|
<div v-if="credentialsData.show">
|
|
<q-card-section>
|
|
<div class="row">
|
|
<div class="col">
|
|
<h4 class="q-my-none">
|
|
<span v-text="$t('password')"></span>
|
|
</h4>
|
|
</div>
|
|
<div class="col">
|
|
<q-img
|
|
v-if="g.user.extra.picture"
|
|
style="max-width: 100px"
|
|
:src="g.user.extra.picture"
|
|
class="float-right"
|
|
></q-img>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
<q-card-section>
|
|
<q-input
|
|
v-model="credentialsData.username"
|
|
:label="$t('username')"
|
|
filled
|
|
dense
|
|
:readonly="hasUsername"
|
|
class="q-mb-md"
|
|
></q-input>
|
|
<q-input
|
|
v-if="g.user.hasPassword"
|
|
v-model="credentialsData.oldPassword"
|
|
type="password"
|
|
autocomplete="off"
|
|
label="Old Password"
|
|
filled
|
|
dense
|
|
:rules="[
|
|
val =>
|
|
!val || val.length >= 8 || $t('invalid_password')
|
|
]"
|
|
></q-input>
|
|
<q-input
|
|
v-model="credentialsData.newPassword"
|
|
type="password"
|
|
autocomplete="off"
|
|
:label="$t('password')"
|
|
filled
|
|
dense
|
|
:rules="[
|
|
val =>
|
|
!val || val.length >= 8 || $t('invalid_password')
|
|
]"
|
|
></q-input>
|
|
<q-input
|
|
v-model="credentialsData.newPasswordRepeat"
|
|
type="password"
|
|
autocomplete="off"
|
|
:label="$t('password_repeat')"
|
|
filled
|
|
dense
|
|
class="q-mb-md"
|
|
:rules="[
|
|
val =>
|
|
!val || val.length >= 8 || $t('invalid_password')
|
|
]"
|
|
></q-input>
|
|
<q-btn
|
|
@click="updatePassword"
|
|
:disable="disableUpdatePassword()"
|
|
unelevated
|
|
color="primary"
|
|
class="float-right"
|
|
:label="$t('update_password')"
|
|
>
|
|
</q-btn>
|
|
</q-card-section>
|
|
<q-separator class="q-mt-xl"></q-separator>
|
|
<q-card-section>
|
|
<div class="col q-mb-sm">
|
|
<h4 class="q-my-none">
|
|
Nostr <span v-text="$t('pubkey')"></span>
|
|
</h4>
|
|
</div>
|
|
<q-input
|
|
v-model="credentialsData.pubkey"
|
|
type="text"
|
|
label="Pubkey"
|
|
filled
|
|
dense
|
|
></q-input>
|
|
<q-btn
|
|
@click="updatePubkey"
|
|
unelevated
|
|
color="primary"
|
|
class="q-mt-md float-right"
|
|
:label="$t('update_pubkey')"
|
|
>
|
|
</q-btn>
|
|
</q-card-section>
|
|
<q-separator class="q-mt-xl"></q-separator>
|
|
<q-card-section class="q-pb-lg">
|
|
<q-btn
|
|
@click="credentialsData.show = false"
|
|
:label="$t('back')"
|
|
outline
|
|
unelevated
|
|
color="grey"
|
|
></q-btn>
|
|
</q-card-section>
|
|
</div>
|
|
<div v-else>
|
|
<q-card-section v-if="g.user.extra.picture">
|
|
<div class="row">
|
|
<div class="col">
|
|
<q-img
|
|
style="max-width: 100px"
|
|
:src="g.user.extra.picture"
|
|
class="float-right"
|
|
></q-img>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
|
|
<q-card-section>
|
|
<q-input
|
|
v-model="g.user.id"
|
|
:label="$t('user_id')"
|
|
filled
|
|
dense
|
|
readonly
|
|
:type="showUserId ? 'text' : 'password'"
|
|
class="q-mb-md"
|
|
><q-btn
|
|
@click="showUserId = !showUserId"
|
|
dense
|
|
flat
|
|
:icon="showUserId ? 'visibility_off' : 'visibility'"
|
|
color="grey"
|
|
></q-btn>
|
|
</q-input>
|
|
<q-input
|
|
v-model="g.user.username"
|
|
:label="$t('username')"
|
|
filled
|
|
dense
|
|
:readonly="hasUsername"
|
|
class="q-mb-md"
|
|
>
|
|
</q-input>
|
|
<q-input
|
|
v-model="g.user.pubkey"
|
|
:label="$t('pubkey')"
|
|
filled
|
|
dense
|
|
readonly
|
|
class="q-mb-md"
|
|
>
|
|
</q-input>
|
|
<q-input
|
|
v-model="g.user.email"
|
|
:label="$t('email')"
|
|
filled
|
|
dense
|
|
readonly
|
|
class="q-mb-md"
|
|
>
|
|
</q-input>
|
|
<div v-if="!g.user.email" class="row">
|
|
<div
|
|
v-if="
|
|
'google-auth' in g.settings.authMethods ||
|
|
'github-auth' in g.settings.authMethods
|
|
"
|
|
class="col q-pa-sm text-h6"
|
|
>
|
|
<span v-text="$t('verify_email')"></span>:
|
|
</div>
|
|
<div
|
|
v-if="'google-auth' in g.settings.authMethods"
|
|
class="col q-pa-sm"
|
|
>
|
|
<q-btn
|
|
:href="`/api/v1/auth/google?user_id=${g.user.id}`"
|
|
type="a"
|
|
outline
|
|
no-caps
|
|
rounded
|
|
color="grey"
|
|
class="full-width"
|
|
>
|
|
<q-avatar size="32px" class="q-mr-md">
|
|
<q-img
|
|
:src="'{{ static_url_for('static', 'images/google-logo.png') }}'"
|
|
></q-img>
|
|
</q-avatar>
|
|
<div>Google</div>
|
|
</q-btn>
|
|
</div>
|
|
<div
|
|
v-if="'github-auth' in g.settings.authMethods"
|
|
class="col q-pa-sm"
|
|
>
|
|
<q-btn
|
|
:href="`/api/v1/auth/github?user_id=${g.user.id}`"
|
|
type="a"
|
|
outline
|
|
no-caps
|
|
color="grey"
|
|
rounded
|
|
class="full-width"
|
|
>
|
|
<q-avatar size="32px" class="q-mr-md">
|
|
<q-img
|
|
:src="'{{ static_url_for('static', 'images/github-logo.png') }}'"
|
|
></q-img>
|
|
</q-avatar>
|
|
<div>GitHub</div>
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
|
|
<q-card-section v-if="g.user.extra">
|
|
<q-input
|
|
v-model="g.user.extra.first_name"
|
|
:label="$t('first_name')"
|
|
filled
|
|
dense
|
|
class="q-mb-md"
|
|
>
|
|
</q-input>
|
|
<q-input
|
|
v-model="g.user.extra.last_name"
|
|
:label="$t('last_name')"
|
|
filled
|
|
dense
|
|
class="q-mb-md"
|
|
>
|
|
</q-input>
|
|
<q-input
|
|
v-model="g.user.extra.provider"
|
|
:label="$t('auth_provider')"
|
|
filled
|
|
dense
|
|
readonly
|
|
class="q-mb-md"
|
|
>
|
|
</q-input>
|
|
|
|
<q-input
|
|
v-model="g.user.extra.visible_wallet_count"
|
|
:label="$t('visible_wallet_count')"
|
|
filled
|
|
dense
|
|
type="number"
|
|
class="q-mb-md"
|
|
></q-input>
|
|
|
|
<q-input
|
|
v-model="g.user.external_id"
|
|
:label="$t('external_id')"
|
|
filled
|
|
dense
|
|
readonly
|
|
class="q-mb-md"
|
|
>
|
|
</q-input>
|
|
|
|
<q-input
|
|
v-model="g.user.extra.picture"
|
|
:label="$t('picture')"
|
|
:hint="$t('user_picture_desc')"
|
|
filled
|
|
dense
|
|
class="q-mb-md"
|
|
>
|
|
</q-input>
|
|
</q-card-section>
|
|
<q-separator></q-separator>
|
|
<q-card-section>
|
|
<q-btn
|
|
@click="showUpdateCredentials()"
|
|
:label="$t('change_password')"
|
|
filled
|
|
color="primary"
|
|
class="float-right q-mb-md"
|
|
></q-btn>
|
|
</q-card-section>
|
|
</div>
|
|
</q-tab-panel>
|
|
<q-tab-panel name="theme">
|
|
<q-btn
|
|
v-if="g.user.admin"
|
|
class="absolute-top-right"
|
|
flat
|
|
round
|
|
icon="settings"
|
|
to="/admin#site_customisation"
|
|
><q-tooltip v-text="$t('admin_settings')"></q-tooltip
|
|
></q-btn>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('language')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<lnbits-language-dropdown
|
|
@language-changed="
|
|
siteCustomisationChanged({locale: $event})
|
|
"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('color_scheme')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-btn
|
|
v-for="theme in themeOptions"
|
|
:key="theme.name"
|
|
@click="
|
|
siteCustomisationChanged({themeChoice: theme.name})
|
|
"
|
|
:color="theme.color"
|
|
dense
|
|
flat
|
|
icon="circle"
|
|
size="md"
|
|
><q-tooltip
|
|
><span v-text="theme.name"></span
|
|
></q-tooltip>
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('background_image')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-input
|
|
v-model="g.bgimageChoice"
|
|
:label="$t('background_image')"
|
|
@update:model-value="
|
|
siteCustomisationChanged({bgimageChoice: $event})
|
|
"
|
|
>
|
|
<q-tooltip
|
|
><span v-text="$t('background_image')"></span
|
|
></q-tooltip>
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('gradient_background')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-toggle
|
|
dense
|
|
flat
|
|
round
|
|
icon="gradient"
|
|
v-model="g.gradientChoice"
|
|
@update:model-value="
|
|
siteCustomisationChanged({gradientChoice: $event})
|
|
"
|
|
>
|
|
<q-tooltip
|
|
><span v-text="$t('toggle_gradient')"></span
|
|
></q-tooltip>
|
|
</q-toggle>
|
|
</div>
|
|
</div>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('rounded_ui')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-toggle
|
|
dense
|
|
flat
|
|
round
|
|
icon="rounded_corner"
|
|
v-model="g.cardRoundedChoice"
|
|
@update:model-value="
|
|
siteCustomisationChanged({cardRoundedChoice: $event})
|
|
"
|
|
>
|
|
<q-tooltip
|
|
><span v-text="$t('toggle_rounded_ui')"></span
|
|
></q-tooltip>
|
|
</q-toggle>
|
|
</div>
|
|
</div>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('card_gradient')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-toggle
|
|
dense
|
|
flat
|
|
round
|
|
icon="gradient"
|
|
v-model="g.cardGradientChoice"
|
|
@update:model-value="
|
|
siteCustomisationChanged({cardGradientChoice: $event})
|
|
"
|
|
>
|
|
<q-tooltip
|
|
><span v-text="$t('toggle_card_gradient')"></span
|
|
></q-tooltip>
|
|
</q-toggle>
|
|
</div>
|
|
</div>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('card_shadow')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-toggle
|
|
dense
|
|
flat
|
|
round
|
|
icon="blur_on"
|
|
v-model="g.cardShadowChoice"
|
|
@update:model-value="
|
|
siteCustomisationChanged({cardShadowChoice: $event})
|
|
"
|
|
>
|
|
<q-tooltip
|
|
><span v-text="$t('toggle_card_shadow')"></span
|
|
></q-tooltip>
|
|
</q-toggle>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('toggle_darkmode')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-toggle
|
|
dense
|
|
flat
|
|
round
|
|
v-model="g.darkChoice"
|
|
@update:model-value="
|
|
siteCustomisationChanged({darkChoice: $event})
|
|
"
|
|
:icon="$q.dark.isActive ? 'brightness_3' : 'wb_sunny'"
|
|
size="sm"
|
|
>
|
|
<q-tooltip
|
|
><span v-text="$t('toggle_darkmode')"></span
|
|
></q-tooltip>
|
|
</q-toggle>
|
|
</div>
|
|
</div>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('border_choices')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-select
|
|
v-model="g.borderChoice"
|
|
:options="borderOptions"
|
|
label="Borders"
|
|
@update:model-value="
|
|
siteCustomisationChanged({borderChoice: $event})
|
|
"
|
|
>
|
|
<q-tooltip
|
|
><span v-text="$t('border_choices')"></span
|
|
></q-tooltip>
|
|
</q-select>
|
|
</div>
|
|
</div>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('notifications')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<lnbits-notifications-btn
|
|
v-if="g.user"
|
|
pubkey="g.settings.webpushPubkey"
|
|
></lnbits-notifications-btn>
|
|
</div>
|
|
</div>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('payment_reactions')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-select
|
|
v-model="g.reactionChoice"
|
|
:options="reactionOptions"
|
|
label="Reactions"
|
|
@update:model-value="
|
|
siteCustomisationChanged({reactionChoice: $event})
|
|
"
|
|
>
|
|
<q-tooltip
|
|
><span v-text="$t('payment_reactions')"></span
|
|
></q-tooltip>
|
|
</q-select>
|
|
</div>
|
|
</div>
|
|
<q-card-section>
|
|
<q-btn
|
|
@click="resetThemeDefaults"
|
|
:label="$t('reset_defaults')"
|
|
filled
|
|
color="primary"
|
|
class="float-right q-mb-md"
|
|
></q-btn>
|
|
</q-card-section>
|
|
</q-tab-panel>
|
|
<q-tab-panel name="notifications">
|
|
<q-card-section>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span
|
|
v-text="$t('notifications_nostr_identifier')"
|
|
></span>
|
|
<br />
|
|
<q-badge
|
|
v-if="!g.settings.nostrConfigured"
|
|
v-text="$t('not_connected')"
|
|
></q-badge>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model="g.user.extra.notifications.nostr_identifier"
|
|
:hint="$t('notifications_nostr_identifier_desc')"
|
|
>
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('notifications_chat_id')"></span>
|
|
<br />
|
|
<q-badge
|
|
v-if="!g.settings.telegramConfigured"
|
|
v-text="$t('not_connected')"
|
|
></q-badge>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model="g.user.extra.notifications.telegram_chat_id"
|
|
:hint="$t('notifications_chat_id_desc')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<q-separator class="q-mb-md"></q-separator>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span
|
|
v-text="$t('notification_outgoing_payment')"
|
|
></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="number"
|
|
min="0"
|
|
step="1"
|
|
v-model="
|
|
g.user.extra.notifications.outgoing_payments_sats
|
|
"
|
|
:hint="$t('notification_outgoing_payment_desc')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span
|
|
v-text="$t('notification_incoming_payment')"
|
|
></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-input
|
|
filled
|
|
dense
|
|
type="number"
|
|
min="0"
|
|
step="1"
|
|
v-model="
|
|
g.user.extra.notifications.incoming_payments_sats
|
|
"
|
|
:hint="$t('notification_incoming_payment_desc')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="row q-mb-md">
|
|
<div class="col-4">
|
|
<span v-text="$t('exclude_wallets')"></span>
|
|
</div>
|
|
<div class="col-8">
|
|
<q-select
|
|
filled
|
|
dense
|
|
emit-value
|
|
map-options
|
|
multiple
|
|
v-model="g.user.extra.notifications.excluded_wallets"
|
|
:options="g.user.walletOptions"
|
|
:label="$t('exclude_wallets')"
|
|
:hint="$t('notifications_excluded_wallets_desc')"
|
|
class="q-mt-sm"
|
|
>
|
|
</q-select>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-tab-panel>
|
|
<q-tab-panel name="api_acls">
|
|
<div class="row q-mb-md">
|
|
<q-badge v-if="g.user.admin">
|
|
<span
|
|
v-text="$t('access_control_list_admin_warning')"
|
|
></span>
|
|
</q-badge>
|
|
</div>
|
|
|
|
<q-card-section>
|
|
<div class="row q-mb-md q-gutter-y-md">
|
|
<div class="col-sm-12 col-md-6">
|
|
<q-select
|
|
v-model="selectedApiAcl.id"
|
|
emit-value
|
|
map-options
|
|
@update:model-value="handleApiACLSelected"
|
|
:options="
|
|
apiAcl.data.map(t => ({label: t.name, value: t.id}))
|
|
"
|
|
:label="$t('access_control_list')"
|
|
dense
|
|
>
|
|
</q-select>
|
|
</div>
|
|
<div class="col-sm-12 col-md-6">
|
|
<q-btn
|
|
@click="askPasswordAndRunFunction('newApiAclDialog')"
|
|
filled
|
|
outline
|
|
icon="add"
|
|
:label="$t('access_control_list')"
|
|
color="grey"
|
|
class="float-right"
|
|
></q-btn>
|
|
</div>
|
|
</div>
|
|
<div v-if="selectedApiAcl.id">
|
|
<div class="row q-mb-md">
|
|
<div class="col-sm-12 col-md-6">
|
|
<q-select
|
|
:options="
|
|
selectedApiAcl.token_id_list.map(t => ({
|
|
label: t.name,
|
|
value: t.id
|
|
}))
|
|
"
|
|
v-model="apiAcl.selectedTokenId"
|
|
emit-value
|
|
map-options
|
|
:label="$t('api_tokens')"
|
|
dense
|
|
>
|
|
</q-select>
|
|
</div>
|
|
|
|
<div class="col-sm-12 col-md-6 q-pl-sm">
|
|
<q-btn
|
|
v-if="apiAcl.selectedTokenId"
|
|
@click="askPasswordAndRunFunction('deleteToken')"
|
|
icon="delete"
|
|
filled
|
|
color="negative"
|
|
class="float-left"
|
|
></q-btn>
|
|
<q-btn
|
|
@click="
|
|
askPasswordAndRunFunction('newTokenAclDialog')
|
|
"
|
|
outline
|
|
icon="add"
|
|
:label="$t('api_token')"
|
|
filled
|
|
color="grey"
|
|
class="float-right"
|
|
></q-btn>
|
|
</div>
|
|
</div>
|
|
<div v-if="apiAcl.apiToken" class="row q-mb-md">
|
|
<div class="col-12">
|
|
<q-badge>
|
|
<span>Use this token in the HTTP</span>
|
|
<strong>
|
|
<code>Authorization</code>
|
|
|
|
</strong>
|
|
<span> header.</span>
|
|
</q-badge>
|
|
</div>
|
|
<div class="col-12">
|
|
<table
|
|
class="full-width lnbits__table-bordered"
|
|
style="
|
|
border-collapse: collapse;
|
|
background-color: grey;
|
|
"
|
|
>
|
|
<thead>
|
|
<tr>
|
|
<th>
|
|
<span class="float-left">Header Name</span>
|
|
</th>
|
|
<th>
|
|
<span class="float-left">Header Value</span>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>
|
|
<strong>Authorization</strong>
|
|
</td>
|
|
<td>
|
|
<div class="row q-pt-sm">
|
|
<div class="col-2 q-mt-sm">
|
|
<strong>Bearer </strong>
|
|
</div>
|
|
<div class="col-10">
|
|
<q-input
|
|
v-model="apiAcl.apiToken"
|
|
:label="$t('api_token_id')"
|
|
filled
|
|
dense
|
|
readonly
|
|
:type="
|
|
selectedApiAcl.showId
|
|
? 'text'
|
|
: 'password'
|
|
"
|
|
class="q-mb-md"
|
|
>
|
|
<q-btn
|
|
@click="
|
|
selectedApiAcl.showId =
|
|
!selectedApiAcl.showId
|
|
"
|
|
dense
|
|
flat
|
|
:icon="
|
|
selectedApiAcl.showId
|
|
? 'visibility_off'
|
|
: 'visibility'
|
|
"
|
|
color="black"
|
|
></q-btn>
|
|
|
|
<q-btn
|
|
@click="
|
|
utils.copyText(apiAcl.apiToken)
|
|
"
|
|
icon="content_copy"
|
|
color="black"
|
|
flat
|
|
dense
|
|
></q-btn>
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="col-12">
|
|
<q-badge>
|
|
<span
|
|
>Please store this token. It cannot be later
|
|
retrieved, only revoked.</span
|
|
>
|
|
</q-badge>
|
|
</div>
|
|
</div>
|
|
|
|
<q-table
|
|
row-key="path"
|
|
:rows="selectedApiAcl.endpoints"
|
|
:columns="apiAcl.columns"
|
|
v-model:pagination="apiAcl.pagination"
|
|
>
|
|
<template v-slot:header="props">
|
|
<q-tr :props="props">
|
|
<q-th
|
|
v-for="col in props.cols"
|
|
:key="col.name"
|
|
:props="props"
|
|
>
|
|
<q-toggle
|
|
v-if="col.name == 'read'"
|
|
v-model="selectedApiAcl.allRead"
|
|
@update:model-value="
|
|
handleAllEndpointsReadAccess()
|
|
"
|
|
:label="$t('read')"
|
|
size="sm"
|
|
></q-toggle>
|
|
<q-toggle
|
|
v-else-if="col.name == 'write'"
|
|
v-model="selectedApiAcl.allWrite"
|
|
@update:model-value="
|
|
handleAllEndpointsWriteAccess()
|
|
"
|
|
:label="$t('write')"
|
|
size="sm"
|
|
></q-toggle>
|
|
<span v-else v-text="col.label"></span>
|
|
</q-th>
|
|
</q-tr>
|
|
</template>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td>
|
|
<span v-text="props.row.name"></span>
|
|
</q-td>
|
|
<q-td>
|
|
<span v-text="props.row.path"></span>
|
|
</q-td>
|
|
<q-td>
|
|
<q-toggle size="sm" v-model="props.row.read" />
|
|
</q-td>
|
|
<q-td>
|
|
<q-toggle size="sm" v-model="props.row.write" />
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
<q-separator></q-separator>
|
|
</div>
|
|
|
|
<div v-if="selectedApiAcl.id" class="row q-mt-md">
|
|
<div class="col-sm-12 col-md-6">
|
|
<q-btn
|
|
@click="askPasswordAndRunFunction('updateApiACLs')"
|
|
:label="$t('update')"
|
|
filled
|
|
color="primary"
|
|
></q-btn>
|
|
</div>
|
|
<div class="col-sm-12 col-md-6">
|
|
<q-btn
|
|
@click="askPasswordAndRunFunction('deleteApiACL')"
|
|
:label="$t('delete')"
|
|
icon="delete"
|
|
color="negative"
|
|
class="float-right"
|
|
>
|
|
</q-btn>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-tab-panel>
|
|
<q-tab-panel name="assets">
|
|
<q-card-section>
|
|
<div class="row">
|
|
<div class="col-md-2 col-sm-12">
|
|
<q-btn
|
|
color="primary"
|
|
:label="$t('upload')"
|
|
@click="$refs.imageInput.click()"
|
|
class="full-width"
|
|
></q-btn>
|
|
<input
|
|
type="file"
|
|
ref="imageInput"
|
|
style="display: none"
|
|
@change="onImageInput"
|
|
/>
|
|
</div>
|
|
<div class="col-md-4 col-sm-12">
|
|
<q-toggle
|
|
v-model="assetsUploadToPublic"
|
|
label="Visible for everyone (public)"
|
|
></q-toggle>
|
|
</div>
|
|
<div class="col-md-6 col-sm-12">
|
|
<q-input
|
|
:label="$t('search')"
|
|
dense
|
|
class="full-width q-pb-xl"
|
|
v-model="assetsTable.search"
|
|
>
|
|
<template v-slot:before>
|
|
<q-icon name="search"> </q-icon>
|
|
</template>
|
|
<template v-slot:append>
|
|
<q-icon
|
|
v-if="assetsTable.search !== ''"
|
|
name="close"
|
|
@click="assetsTable.search = ''"
|
|
class="cursor-pointer"
|
|
>
|
|
</q-icon>
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
<q-separator></q-separator>
|
|
<q-card-section>
|
|
<q-table
|
|
grid
|
|
grid-header
|
|
flat
|
|
bordered
|
|
:rows="assets"
|
|
:columns="assetsTable.columns"
|
|
v-model:pagination="assetsTable.pagination"
|
|
:loading="assetsTable.loading"
|
|
@request="getUserAssets"
|
|
row-key="id"
|
|
:filter="filter"
|
|
hide-header
|
|
>
|
|
<template v-slot:item="props">
|
|
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4">
|
|
<q-card class="q-ma-sm wallet-list-card text-center">
|
|
<q-card-section>
|
|
<a
|
|
v-if="props.row.thumbnail_base64"
|
|
target="_blank"
|
|
style="color: inherit"
|
|
:href="`/api/v1/assets/${props.row.id}/data`"
|
|
>
|
|
<q-img
|
|
:src="
|
|
'data:image/png;base64,' +
|
|
props.row.thumbnail_base64
|
|
"
|
|
:alt="props.row.name"
|
|
loading="lazy"
|
|
style="height: 128px"
|
|
class="text-center cursor-pointer"
|
|
>
|
|
</q-img>
|
|
</a>
|
|
<q-icon v-else name="web_asset"></q-icon>
|
|
</q-card-section>
|
|
<q-separator></q-separator>
|
|
|
|
<q-card-section>
|
|
<q-btn-dropdown
|
|
color="grey"
|
|
dense
|
|
outline
|
|
no-caps
|
|
:label="props.row.name"
|
|
:icon="props.row.is_public ? 'public' : ''"
|
|
>
|
|
<q-list>
|
|
<q-item
|
|
clickable
|
|
v-close-popup
|
|
@click="copyAssetLinkToClipboard(props.row)"
|
|
>
|
|
<q-item-section avatar>
|
|
<q-avatar icon="content_copy" />
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>Copy Link</q-item-label>
|
|
<q-item-label caption
|
|
>Copy asset link to
|
|
clipboard</q-item-label
|
|
>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item
|
|
clickable
|
|
v-close-popup
|
|
@click="toggleAssetPublicAccess(props.row)"
|
|
>
|
|
<q-item-section avatar>
|
|
<q-avatar
|
|
:icon="
|
|
props.row.is_public
|
|
? 'public_off'
|
|
: 'public'
|
|
"
|
|
text-color="primary"
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section v-if="props.row.is_public">
|
|
<q-item-label>Unpublish</q-item-label>
|
|
<q-item-label caption
|
|
>Make this asset private</q-item-label
|
|
>
|
|
</q-item-section>
|
|
<q-item-section v-else>
|
|
<q-item-label>Publish</q-item-label>
|
|
<q-item-label caption
|
|
>Make this asset public</q-item-label
|
|
>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item
|
|
clickable
|
|
v-close-popup
|
|
@click="deleteAsset(props.row)"
|
|
>
|
|
<q-item-section avatar>
|
|
<q-avatar
|
|
icon="delete"
|
|
text-color="negative"
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>Delete</q-item-label>
|
|
<q-item-label caption
|
|
>Permanently delete this
|
|
asset</q-item-label
|
|
>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-btn-dropdown>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-tab-panel>
|
|
<q-tab-panel name="labels">
|
|
<q-card-section>
|
|
<div class="row">
|
|
<div class="col-md-2 col-sm-12">
|
|
<q-btn
|
|
@click="openAddLabelDialog()"
|
|
:label="$t('add_label')"
|
|
color="primary"
|
|
class="full-width"
|
|
></q-btn>
|
|
</div>
|
|
<div class="col-md-1 col-sm-12"></div>
|
|
<div class="col-md-9 col-sm-12">
|
|
<q-input
|
|
:label="$t('search')"
|
|
dense
|
|
class="full-width q-pb-xl"
|
|
v-model="labelsTable.search"
|
|
>
|
|
<template v-slot:before>
|
|
<q-icon name="search"> </q-icon>
|
|
</template>
|
|
<template v-slot:append>
|
|
<q-icon
|
|
v-if="labelsTable.search !== ''"
|
|
name="close"
|
|
@click="labelsTable.search = ''"
|
|
class="cursor-pointer"
|
|
>
|
|
</q-icon>
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
<q-separator></q-separator>
|
|
<q-card-section>
|
|
<q-table
|
|
:rows="g.user.extra.labels"
|
|
:columns="labelsTable.columns"
|
|
v-model:pagination="labelsTable.pagination"
|
|
:loading="labelsTable.loading"
|
|
row-key="name"
|
|
:filter="labelsTable.search"
|
|
>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td key="actions" :props="props">
|
|
<q-btn
|
|
@click="openEditLabelDialog(props.row)"
|
|
dense
|
|
flat
|
|
icon="edit"
|
|
color="primary"
|
|
></q-btn>
|
|
<q-btn
|
|
@click="deleteUserLabel(props.row)"
|
|
dense
|
|
flat
|
|
icon="delete"
|
|
color="negative"
|
|
class="q-ml-md"
|
|
></q-btn>
|
|
</q-td>
|
|
<q-td key="name" :props="props">
|
|
<span v-text="props.row.name"></span>
|
|
</q-td>
|
|
<q-td key="description" :props="props">
|
|
<span v-text="props.row.description"></span>
|
|
</q-td>
|
|
<q-td key="color" :props="props">
|
|
<q-badge
|
|
class="q-pa-sm"
|
|
:style="{
|
|
backgroundColor: props.row.color,
|
|
color: 'white'
|
|
}"
|
|
>
|
|
<span v-text="props.row.color"></span>
|
|
</q-badge>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-tab-panel>
|
|
</q-tab-panels>
|
|
</q-scroll-area>
|
|
</template>
|
|
</q-splitter>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
|
|
<lnbits-dialog
|
|
:show="apiAcl.showPasswordDialog"
|
|
title="Password Required"
|
|
:action="{
|
|
label: $t('ok')
|
|
}"
|
|
@update:show="apiAcl.showPasswordDialog = $event"
|
|
@action="runPasswordGuardedFunction()"
|
|
>
|
|
<div class="row q-col-gutter-md">
|
|
<div class="col-12">
|
|
<q-input
|
|
v-model="apiAcl.password"
|
|
type="password"
|
|
dense
|
|
filled
|
|
label="Password"
|
|
hint="User password is required for this action."
|
|
>
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
</lnbits-dialog>
|
|
|
|
<lnbits-dialog
|
|
:show="apiAcl.showNewAclDialog"
|
|
title="New API Access Control List"
|
|
:action="{
|
|
label: 'Create'
|
|
}"
|
|
:cancel-label="$t('close')"
|
|
@update:show="apiAcl.showNewAclDialog = $event"
|
|
@action="addApiACL()"
|
|
>
|
|
<div class="row q-col-gutter-md">
|
|
<div class="col-12">
|
|
<q-input v-model="apiAcl.newAclName" dense filled label="ACL Name">
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
</lnbits-dialog>
|
|
|
|
<lnbits-dialog
|
|
:show="apiAcl.showNewTokenDialog"
|
|
title="New API Token"
|
|
:action="{
|
|
label: 'Create'
|
|
}"
|
|
:cancel-label="$t('close')"
|
|
@update:show="apiAcl.showNewTokenDialog = $event"
|
|
@action="generateApiToken()"
|
|
>
|
|
<div class="row q-col-gutter-md">
|
|
<div class="col-12">
|
|
<q-input v-model="apiAcl.newTokenName" dense filled label="Token Name">
|
|
</q-input>
|
|
</div>
|
|
<div class="col-12">
|
|
<q-input
|
|
v-model="apiAcl.newTokenExpiry"
|
|
dense
|
|
filled
|
|
label="Expiration"
|
|
hit="Expiration time in minutes (default xxx)"
|
|
>
|
|
<template v-slot:prepend>
|
|
<q-icon name="event" class="cursor-pointer">
|
|
<q-popup-proxy
|
|
cover
|
|
transition-show="scale"
|
|
transition-hide="scale"
|
|
>
|
|
<q-date v-model="apiAcl.newTokenExpiry" mask="YYYY-MM-DD HH:mm">
|
|
<div class="row items-center justify-end">
|
|
<q-btn v-close-popup label="Close" color="primary" flat />
|
|
</div>
|
|
</q-date>
|
|
</q-popup-proxy>
|
|
</q-icon>
|
|
</template>
|
|
|
|
<template v-slot:append>
|
|
<q-icon name="access_time" class="cursor-pointer">
|
|
<q-popup-proxy
|
|
cover
|
|
transition-show="scale"
|
|
transition-hide="scale"
|
|
>
|
|
<q-time
|
|
v-model="apiAcl.newTokenExpiry"
|
|
mask="YYYY-MM-DD HH:mm"
|
|
format24h
|
|
>
|
|
<div class="row items-center justify-end">
|
|
<q-btn v-close-popup label="Close" color="primary" flat />
|
|
</div>
|
|
</q-time>
|
|
</q-popup-proxy>
|
|
</q-icon>
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
</lnbits-dialog>
|
|
|
|
<lnbits-dialog
|
|
:show="labelsDialog.show"
|
|
:title="$t('label')"
|
|
:action="{
|
|
label: g.user.extra.labels.some(
|
|
label => label.name === labelsDialog.data.name
|
|
)
|
|
? $t('update_label')
|
|
: $t('add_label'),
|
|
disable: !labelsDialog.data.name || !labelsDialog.data.color
|
|
}"
|
|
:cancel-label="$t('cancel')"
|
|
@update:show="labelsDialog.show = $event"
|
|
@action="
|
|
g.user.extra.labels.some(label => label.name === labelsDialog.data.name)
|
|
? updateUserLabel()
|
|
: addUserLabel()
|
|
"
|
|
>
|
|
<div class="row q-col-gutter-md">
|
|
<div class="col-12">
|
|
<q-input
|
|
v-model="labelsDialog.data.name"
|
|
dense
|
|
filled
|
|
:label="$t('name')"
|
|
>
|
|
</q-input>
|
|
</div>
|
|
<div class="col-12">
|
|
<q-input
|
|
v-model="labelsDialog.data.description"
|
|
dense
|
|
filled
|
|
type="textarea"
|
|
rows="3"
|
|
:label="$t('description')"
|
|
>
|
|
</q-input>
|
|
</div>
|
|
<div class="col-12">
|
|
<q-input
|
|
v-model="labelsDialog.data.color"
|
|
filled
|
|
dense
|
|
class="my-input"
|
|
>
|
|
<template v-slot:append>
|
|
<q-icon name="colorize" class="cursor-pointer">
|
|
<q-popup-proxy
|
|
cover
|
|
transition-show="scale"
|
|
transition-hide="scale"
|
|
>
|
|
<q-color
|
|
v-model="labelsDialog.data.color"
|
|
default-view="palette"
|
|
/>
|
|
</q-popup-proxy>
|
|
</q-icon>
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
</lnbits-dialog>
|
|
</template>
|