Follow upstream recommendation to have html templates below script in .vue files
This commit is contained in:
@@ -1,35 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<FormGroup v-show="manifest.addons.email">
|
||||
<label>{{ $t('appstore.installDialog.userManagement') }}</label>
|
||||
<p>
|
||||
{{ $t('appstore.installDialog.userManagementMailbox') }}
|
||||
<span v-html="$t('appstore.installDialog.configuredForCloudronEmail', { emailDocsLink: 'https://docs.cloudron.io/email/' })"></span>
|
||||
</p>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label v-show="cloudronAuth && !manifest.addons.email">{{ $t('appstore.installDialog.userManagement') }} <sup><a href="https://docs.cloudron.io/apps/#access-restriction" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<label v-show="!cloudronAuth || manifest.addons.email">{{ $t('app.accessControl.userManagement.dashboardVisibility') }} <sup><a href="https://docs.cloudron.io/apps/#dashboard-visibility" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<p v-show="!cloudronAuth || manifest.addons.email">{{ $t('appstore.installDialog.userManagementNone') }}</p>
|
||||
<Radiobutton v-model="accessRestrictionOption" :value="OPTIONS.NOSSO" v-if="optionalSso" :label="$t('appstore.installDialog.userManagementLeaveToApp')"/>
|
||||
<Radiobutton v-model="accessRestrictionOption" :value="OPTIONS.ANY" :label="cloudronAuth ? $t('appstore.installDialog.userManagementAllUsers') : $t('app.accessControl.userManagement.visibleForAllUsers')"/>
|
||||
<Radiobutton v-model="accessRestrictionOption" :value="OPTIONS.RESTRICT" :label="cloudronAuth ? $t('appstore.installDialog.userManagementSelectUsers') : $t('app.accessControl.userManagement.visibleForSelected')"/>
|
||||
</FormGroup>
|
||||
|
||||
<div v-if="accessRestrictionOption === OPTIONS.RESTRICT">
|
||||
<div style="margin-left: 20px; display: flex;">
|
||||
<div>
|
||||
{{ $t('appstore.installDialog.users') }}: <MultiSelect v-model="accessRestriction.users" :options="users" option-label="username" />
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('appstore.installDialog.groups') }}: <MultiSelect v-model="accessRestriction.groups" :options="groups" option-label="name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
@@ -87,5 +55,34 @@ onMounted(async () => {
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<FormGroup v-show="manifest.addons.email">
|
||||
<label>{{ $t('appstore.installDialog.userManagement') }}</label>
|
||||
<p>
|
||||
{{ $t('appstore.installDialog.userManagementMailbox') }}
|
||||
<span v-html="$t('appstore.installDialog.configuredForCloudronEmail', { emailDocsLink: 'https://docs.cloudron.io/email/' })"></span>
|
||||
</p>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label v-show="cloudronAuth && !manifest.addons.email">{{ $t('appstore.installDialog.userManagement') }} <sup><a href="https://docs.cloudron.io/apps/#access-restriction" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<label v-show="!cloudronAuth || manifest.addons.email">{{ $t('app.accessControl.userManagement.dashboardVisibility') }} <sup><a href="https://docs.cloudron.io/apps/#dashboard-visibility" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<p v-show="!cloudronAuth || manifest.addons.email">{{ $t('appstore.installDialog.userManagementNone') }}</p>
|
||||
<Radiobutton v-model="accessRestrictionOption" :value="OPTIONS.NOSSO" v-if="optionalSso" :label="$t('appstore.installDialog.userManagementLeaveToApp')"/>
|
||||
<Radiobutton v-model="accessRestrictionOption" :value="OPTIONS.ANY" :label="cloudronAuth ? $t('appstore.installDialog.userManagementAllUsers') : $t('app.accessControl.userManagement.visibleForAllUsers')"/>
|
||||
<Radiobutton v-model="accessRestrictionOption" :value="OPTIONS.RESTRICT" :label="cloudronAuth ? $t('appstore.installDialog.userManagementSelectUsers') : $t('app.accessControl.userManagement.visibleForSelected')"/>
|
||||
</FormGroup>
|
||||
|
||||
<div v-if="accessRestrictionOption === OPTIONS.RESTRICT">
|
||||
<div style="margin-left: 20px; display: flex;">
|
||||
<div>
|
||||
{{ $t('appstore.installDialog.users') }}: <MultiSelect v-model="accessRestriction.users" :options="users" option-label="username" />
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('appstore.installDialog.groups') }}: <MultiSelect v-model="accessRestriction.groups" :options="groups" option-label="name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,69 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<InputDialog ref="inputDialog" />
|
||||
|
||||
<Dialog ref="newDialog"
|
||||
:title="$t('profile.createApiToken.title')"
|
||||
:confirm-label="addedToken ? '' : $t('profile.createApiToken.generateToken')"
|
||||
confirm-style="success"
|
||||
:reject-label="$t('main.dialog.close')"
|
||||
@confirm="onSubmitAddApiToken()"
|
||||
@close="onReset()"
|
||||
>
|
||||
<div>
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<div v-if="!addedToken">
|
||||
<form novalidate @submit="onSubmitAddApiToken()" autocomplete="off">
|
||||
<input style="display: none" type="submit" :disabled="!isValid"/>
|
||||
<FormGroup>
|
||||
<label for="apiTokenName">{{ $t('profile.createApiToken.name') }}</label>
|
||||
<TextInput id="apiTokenName" v-model="tokenName" required/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label>{{ $t('profile.createApiToken.access') }}</label>
|
||||
<Radiobutton v-model="tokenScope" value="r" :label="$t('profile.apiTokens.readonly')" />
|
||||
<Radiobutton v-model="tokenScope" value="rw" :label="$t('profile.apiTokens.readwrite')" />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ $t('profile.createApiToken.description') }}
|
||||
<TextInput v-model="addedToken" readonly/>
|
||||
<Button tool @click="onCopyApiTokenToClipboard(addedToken)" icon="fa fa-clipboard" />
|
||||
<p>{{ $t('profile.createApiToken.copyNow') }}</p>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Section :title="$t('profile.apiTokens.title')">
|
||||
<template #header-buttons>
|
||||
<Button @click="newDialog.open()" icon="fa fa-plus">{{ $t('profile.apiTokens.newApiToken') }}</Button>
|
||||
</template>
|
||||
|
||||
<p v-html="$t('profile.apiTokens.description', { apiDocsLink: 'https://docs.cloudron.io/api.html' })"></p>
|
||||
<br/>
|
||||
|
||||
<TableView :columns="columns" :model="apiTokens">
|
||||
<template #lastUsedTime="slotProps">
|
||||
<span v-if="slotProps.lastUsedTime">{{ prettyLongDate(slotProps.lastUsedTime) }}</span>
|
||||
<span v-else>{{ $t('profile.apiTokens.neverUsed') }}</span>
|
||||
</template>
|
||||
<template #scope="slotProps">
|
||||
<span v-if="slotProps.scope['*'] === 'rw'">{{ $t('profile.apiTokens.readwrite') }}</span>
|
||||
<span v-else>{{ $t('profile.apiTokens.readonly') }}</span>
|
||||
</template>
|
||||
<template #actions="slotProps">
|
||||
<div class="table-actions">
|
||||
<Button small outline tool danger @click="onRevokeToken(slotProps)" v-tooltip="$t('profile.apiTokens.revokeTokenTooltip')" icon="far fa-trash-alt" />
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
||||
@@ -168,3 +102,69 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<InputDialog ref="inputDialog" />
|
||||
|
||||
<Dialog ref="newDialog"
|
||||
:title="$t('profile.createApiToken.title')"
|
||||
:confirm-label="addedToken ? '' : $t('profile.createApiToken.generateToken')"
|
||||
confirm-style="success"
|
||||
:reject-label="$t('main.dialog.close')"
|
||||
@confirm="onSubmitAddApiToken()"
|
||||
@close="onReset()"
|
||||
>
|
||||
<div>
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<div v-if="!addedToken">
|
||||
<form novalidate @submit="onSubmitAddApiToken()" autocomplete="off">
|
||||
<input style="display: none" type="submit" :disabled="!isValid"/>
|
||||
<FormGroup>
|
||||
<label for="apiTokenName">{{ $t('profile.createApiToken.name') }}</label>
|
||||
<TextInput id="apiTokenName" v-model="tokenName" required/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label>{{ $t('profile.createApiToken.access') }}</label>
|
||||
<Radiobutton v-model="tokenScope" value="r" :label="$t('profile.apiTokens.readonly')" />
|
||||
<Radiobutton v-model="tokenScope" value="rw" :label="$t('profile.apiTokens.readwrite')" />
|
||||
</FormGroup>
|
||||
</form>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ $t('profile.createApiToken.description') }}
|
||||
<TextInput v-model="addedToken" readonly/>
|
||||
<Button tool @click="onCopyApiTokenToClipboard(addedToken)" icon="fa fa-clipboard" />
|
||||
<p>{{ $t('profile.createApiToken.copyNow') }}</p>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Section :title="$t('profile.apiTokens.title')">
|
||||
<template #header-buttons>
|
||||
<Button @click="newDialog.open()" icon="fa fa-plus">{{ $t('profile.apiTokens.newApiToken') }}</Button>
|
||||
</template>
|
||||
|
||||
<p v-html="$t('profile.apiTokens.description', { apiDocsLink: 'https://docs.cloudron.io/api.html' })"></p>
|
||||
<br/>
|
||||
|
||||
<TableView :columns="columns" :model="apiTokens">
|
||||
<template #lastUsedTime="slotProps">
|
||||
<span v-if="slotProps.lastUsedTime">{{ prettyLongDate(slotProps.lastUsedTime) }}</span>
|
||||
<span v-else>{{ $t('profile.apiTokens.neverUsed') }}</span>
|
||||
</template>
|
||||
<template #scope="slotProps">
|
||||
<span v-if="slotProps.scope['*'] === 'rw'">{{ $t('profile.apiTokens.readwrite') }}</span>
|
||||
<span v-else>{{ $t('profile.apiTokens.readonly') }}</span>
|
||||
</template>
|
||||
<template #actions="slotProps">
|
||||
<div class="table-actions">
|
||||
<Button small outline tool danger @click="onRevokeToken(slotProps)" v-tooltip="$t('profile.apiTokens.revokeTokenTooltip')" icon="far fa-trash-alt" />
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,64 +1,3 @@
|
||||
<template>
|
||||
<Dialog ref="dialogHandle" @close="onClose()">
|
||||
<div class="content" :class="{ 'step-detail': step === STEP.DETAILS, 'step-install': step === STEP.INSTALL }">
|
||||
<div class="header">
|
||||
<div class="summary">
|
||||
<div class="title">{{ manifest.title }}</div>
|
||||
<div class="lastUpdated">{{ $t('appstore.installDialog.lastUpdated', { date: prettyDate(app.creationDate) }) }}</div>
|
||||
<div class="memoryRequirement">{{ $t('appstore.installDialog.memoryRequirement', { size: prettyFileSize(manifest.memoryLimit) }) }}</div>
|
||||
<div class="author"><a :href="manifest.website" target="_blank">Website</a></div>
|
||||
</div>
|
||||
<img class="icon" :src="app.iconUrl" />
|
||||
</div>
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<div v-if="step === STEP.DETAILS">
|
||||
<Button @click="setStep(STEP.INSTALL)" icon="fa-solid fa-circle-down">Install {{ manifest.title }}</Button>
|
||||
<div class="screenshots">
|
||||
<img class="screenshot" v-for="image in manifest.mediaLinks" :key="image" :src="image"/>
|
||||
</div>
|
||||
<div class="description" v-html="description"></div>
|
||||
</div>
|
||||
<div v-else-if="step === STEP.INSTALL">
|
||||
<form @submit.prevent="submit()" autocomplete="off">
|
||||
<fieldset :disabled="busy">
|
||||
<input style="display: none;" type="submit" :disabled="!formValid" />
|
||||
|
||||
<FormGroup :class="{ 'has-error': formError.location }">
|
||||
<label for="location">{{ $t('appstore.installDialog.location') }}</label>
|
||||
<div>
|
||||
<TextInput id="location" ref="locationInput" v-model="location" />
|
||||
<Dropdown v-model="domain" :options="domains" option-label="domain" />
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<p class="text-small text-warning" v-show="domain.provider === 'noop' || domain.provider === 'manual'" v-html="$t('appstore.installDialog.manualWarning', { location: ((location ? location + '.' : '') + domain.domain) })"></p>
|
||||
|
||||
<FormGroup v-for="(port, key) in secondaryDomains" :key="key">
|
||||
<label :for="'secondaryDomainInput' + key">{{ port.title }}</label>
|
||||
<small>{{ port.description }}</small>
|
||||
<div>
|
||||
<TextInput :id="'secondaryDomainInput' + key" v-model="port.value" :placeholder="$t('appstore.installDialog.locationPlaceholder')"/>
|
||||
<Dropdown v-model="port.domain" :options="domains" option-label="domain" option-key="domain" />
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup :class="{ 'has-error': formError.upstreamUri }" v-show="manifest.id === PROXY_APP_ID">
|
||||
<label for="upstreamUri">Upstream URI</label>
|
||||
<TextInput id="upstreamUri" v-model="upstreamUri" />
|
||||
</FormGroup>
|
||||
|
||||
<PortBindings v-model:tcp-ports="tcpPorts" v-model:udp-ports="udpPorts" :error="formError"/>
|
||||
<AccessControl v-model="accessRestriction" :manifest="manifest"/>
|
||||
|
||||
<Button style="margin-top: 15px" @click="submit" icon="fa-solid fa-circle-down" :disabled="!formValid" :loading="busy">Install {{ manifest.title }}</Button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref, computed, useTemplateRef, onMounted } from 'vue';
|
||||
@@ -212,6 +151,67 @@ defineExpose({
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog ref="dialogHandle" @close="onClose()">
|
||||
<div class="content" :class="{ 'step-detail': step === STEP.DETAILS, 'step-install': step === STEP.INSTALL }">
|
||||
<div class="header">
|
||||
<div class="summary">
|
||||
<div class="title">{{ manifest.title }}</div>
|
||||
<div class="lastUpdated">{{ $t('appstore.installDialog.lastUpdated', { date: prettyDate(app.creationDate) }) }}</div>
|
||||
<div class="memoryRequirement">{{ $t('appstore.installDialog.memoryRequirement', { size: prettyFileSize(manifest.memoryLimit) }) }}</div>
|
||||
<div class="author"><a :href="manifest.website" target="_blank">Website</a></div>
|
||||
</div>
|
||||
<img class="icon" :src="app.iconUrl" />
|
||||
</div>
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<div v-if="step === STEP.DETAILS">
|
||||
<Button @click="setStep(STEP.INSTALL)" icon="fa-solid fa-circle-down">Install {{ manifest.title }}</Button>
|
||||
<div class="screenshots">
|
||||
<img class="screenshot" v-for="image in manifest.mediaLinks" :key="image" :src="image"/>
|
||||
</div>
|
||||
<div class="description" v-html="description"></div>
|
||||
</div>
|
||||
<div v-else-if="step === STEP.INSTALL">
|
||||
<form @submit.prevent="submit()" autocomplete="off">
|
||||
<fieldset :disabled="busy">
|
||||
<input style="display: none;" type="submit" :disabled="!formValid" />
|
||||
|
||||
<FormGroup :class="{ 'has-error': formError.location }">
|
||||
<label for="location">{{ $t('appstore.installDialog.location') }}</label>
|
||||
<div>
|
||||
<TextInput id="location" ref="locationInput" v-model="location" />
|
||||
<Dropdown v-model="domain" :options="domains" option-label="domain" />
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<p class="text-small text-warning" v-show="domain.provider === 'noop' || domain.provider === 'manual'" v-html="$t('appstore.installDialog.manualWarning', { location: ((location ? location + '.' : '') + domain.domain) })"></p>
|
||||
|
||||
<FormGroup v-for="(port, key) in secondaryDomains" :key="key">
|
||||
<label :for="'secondaryDomainInput' + key">{{ port.title }}</label>
|
||||
<small>{{ port.description }}</small>
|
||||
<div>
|
||||
<TextInput :id="'secondaryDomainInput' + key" v-model="port.value" :placeholder="$t('appstore.installDialog.locationPlaceholder')"/>
|
||||
<Dropdown v-model="port.domain" :options="domains" option-label="domain" option-key="domain" />
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup :class="{ 'has-error': formError.upstreamUri }" v-show="manifest.id === PROXY_APP_ID">
|
||||
<label for="upstreamUri">Upstream URI</label>
|
||||
<TextInput id="upstreamUri" v-model="upstreamUri" />
|
||||
</FormGroup>
|
||||
|
||||
<PortBindings v-model:tcp-ports="tcpPorts" v-model:udp-ports="udpPorts" :error="formError"/>
|
||||
<AccessControl v-model="accessRestriction" :manifest="manifest"/>
|
||||
|
||||
<Button style="margin-top: 15px" @click="submit" icon="fa-solid fa-circle-down" :disabled="!formValid" :loading="busy">Install {{ manifest.title }}</Button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.content {
|
||||
|
||||
@@ -1,61 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<InputDialog ref="inputDialog" />
|
||||
|
||||
<Dialog ref="newDialog"
|
||||
:title="$t('profile.createAppPassword.title')"
|
||||
:confirm-label="addedPassword ? '' : $t('profile.createAppPassword.generatePassword')"
|
||||
confirm-style="success"
|
||||
:reject-label="$t('main.dialog.close')"
|
||||
@confirm="onSubmit()"
|
||||
@close="onReset()"
|
||||
>
|
||||
<div>
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<div v-if="!addedPassword">
|
||||
<form novalidate @submit="onSubmit()" autocomplete="off">
|
||||
<input style="display: none" type="submit" :disabled="!isValid"/>
|
||||
<FormGroup>
|
||||
<label for="passwordName">{{ $t('profile.createAppPassword.name') }}</label>
|
||||
<TextInput id="passwordName" v-model="passwordName" required/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label>{{ $t('profile.createAppPassword.app') }}</label>
|
||||
<Dropdown outline v-model="identifier" :options="identifiers" option-label="label" option-key="id" /> {{ dropdownValueWithKey }}
|
||||
</FormGroup>
|
||||
</form>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ $t('profile.createAppPassword.description') }}
|
||||
<TextInput v-model="addedPassword" readonly/>
|
||||
<Button tool @click="onCopyToClipboard(addedPassword)" icon="fa fa-clipboard" />
|
||||
<p>{{ $t('profile.createAppPassword.copyNow') }}</p>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Section :title="$t('profile.appPasswords.title')">
|
||||
<template #header-buttons>
|
||||
<Button @click="newDialog.open()" icon="fa fa-plus">{{ $t('profile.appPasswords.newPassword') }}</Button>
|
||||
</template>
|
||||
|
||||
<p>{{ $t('profile.appPasswords.description') }}</p>
|
||||
<br/>
|
||||
|
||||
<TableView :columns="columns" :model="passwords" :placeholder="$t('profile.appPasswords.noPasswordsPlaceholder')">
|
||||
<template #creationTime="slotProps">{{ prettyLongDate(slotProps.creationTime) }}</template>
|
||||
<template #actions="slotProps">
|
||||
<div class="table-actions">
|
||||
<Button small outline tool danger @click="onRemove(slotProps.id)" v-tooltip="$t('profile.appPasswords.deletePasswordTooltip')" icon="far fa-trash-alt" />
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
||||
@@ -203,3 +145,61 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<InputDialog ref="inputDialog" />
|
||||
|
||||
<Dialog ref="newDialog"
|
||||
:title="$t('profile.createAppPassword.title')"
|
||||
:confirm-label="addedPassword ? '' : $t('profile.createAppPassword.generatePassword')"
|
||||
confirm-style="success"
|
||||
:reject-label="$t('main.dialog.close')"
|
||||
@confirm="onSubmit()"
|
||||
@close="onReset()"
|
||||
>
|
||||
<div>
|
||||
<Transition name="slide-left" mode="out-in">
|
||||
<div v-if="!addedPassword">
|
||||
<form novalidate @submit="onSubmit()" autocomplete="off">
|
||||
<input style="display: none" type="submit" :disabled="!isValid"/>
|
||||
<FormGroup>
|
||||
<label for="passwordName">{{ $t('profile.createAppPassword.name') }}</label>
|
||||
<TextInput id="passwordName" v-model="passwordName" required/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label>{{ $t('profile.createAppPassword.app') }}</label>
|
||||
<Dropdown outline v-model="identifier" :options="identifiers" option-label="label" option-key="id" /> {{ dropdownValueWithKey }}
|
||||
</FormGroup>
|
||||
</form>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ $t('profile.createAppPassword.description') }}
|
||||
<TextInput v-model="addedPassword" readonly/>
|
||||
<Button tool @click="onCopyToClipboard(addedPassword)" icon="fa fa-clipboard" />
|
||||
<p>{{ $t('profile.createAppPassword.copyNow') }}</p>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Section :title="$t('profile.appPasswords.title')">
|
||||
<template #header-buttons>
|
||||
<Button @click="newDialog.open()" icon="fa fa-plus">{{ $t('profile.appPasswords.newPassword') }}</Button>
|
||||
</template>
|
||||
|
||||
<p>{{ $t('profile.appPasswords.description') }}</p>
|
||||
<br/>
|
||||
|
||||
<TableView :columns="columns" :model="passwords" :placeholder="$t('profile.appPasswords.noPasswordsPlaceholder')">
|
||||
<template #creationTime="slotProps">{{ prettyLongDate(slotProps.creationTime) }}</template>
|
||||
<template #actions="slotProps">
|
||||
<div class="table-actions">
|
||||
<Button small outline tool danger @click="onRemove(slotProps.id)" v-tooltip="$t('profile.appPasswords.deletePasswordTooltip')" icon="far fa-trash-alt" />
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,74 +1,3 @@
|
||||
<template>
|
||||
<Dialog ref="applinkDialog"
|
||||
:title="mode === 'edit' ? $t('app.editApplinkDialog.title') : $t('app.addApplinkDialog.title')"
|
||||
:alternate-label="mode === 'edit' ? 'Delete' : ''"
|
||||
alternate-style="danger"
|
||||
:reject-label="$t('main.dialog.cancel')"
|
||||
:confirm-label="$t('main.dialog.save')"
|
||||
confirm-style="success"
|
||||
:confirm-active="isValid"
|
||||
:confirm-busy="busy"
|
||||
@confirm="onSubmit()"
|
||||
@alternate="onRemove()"
|
||||
>
|
||||
<InputDialog ref="inputDialog" />
|
||||
|
||||
<form @submit="onSubmit()" autocomplete="off">
|
||||
<fieldset :disabled="busy">
|
||||
<input style="display: none;" type="submit" :disabled="!isValid" />
|
||||
|
||||
<p class="has-error" v-show="error.generic">{{ error.generic }}</p>
|
||||
|
||||
<FormGroup :class="{ 'has-error': error.upstreamUri }">
|
||||
<label for="applinkUpstreamUri">{{ $t('app.applinks.upstreamUri') }}</label>
|
||||
<TextInput id="applinkUpstreamUri" v-model="upstreamUri" required />
|
||||
<span class="text-danger" v-show="error.upstreamUri">{{ error.upstreamUri }}</span>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label for="applinkLabel">{{ $t('app.applinks.label') }}</label>
|
||||
<TextInput id="applinkLabel" v-model="label" />
|
||||
</FormGroup>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<label for="previewIcon">{{ $t('app.display.icon') }}</label>
|
||||
</div>
|
||||
<div id="previewIcon" class="app-custom-icon" @click="showCustomIconSelector()">
|
||||
<img :src="iconSrc" />
|
||||
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
||||
</div>
|
||||
<span style="cursor: pointer;" @click="resetCustomIcon()">{{ $t('app.applinks.clearIconAction') }}</span> - <span class="text-small">{{ $t('app.applinks.clearIconDescription') }}</span>
|
||||
<input type="file" ref="iconFileInput" style="display: none" accept="image/png" @change="onIconFileInputChanged($event)"/>
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<label for="applinkTags">{{ $t('app.display.tags') }}</label>
|
||||
<TagInput id="applinkTags" :placeholder="$t('app.display.tagsPlaceholder')" v-model="tags" v-tooltip="$t('app.display.tagsTooltip')" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label>{{ $t('app.accessControl.userManagement.dashboardVisibility') }} <sup><a href="https://docs.cloudron.io/apps/#dashboard-visibility" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<Radiobutton v-model="accessRestrictionOption" value="any" :label="$t('app.accessControl.userManagement.visibleForAllUsers')"/>
|
||||
<Radiobutton v-model="accessRestrictionOption" value="groups" :label="$t('app.accessControl.userManagement.visibleForSelected')"/>
|
||||
<!-- <span class="label label-danger"v-show="accessRestrictionOption === 'groups' && !isAccessRestrictionValid(applinkDialogData)">{{ $t('appstore.installDialog.errorUserManagementSelectAtLeastOne') }}</span> -->
|
||||
</FormGroup>
|
||||
|
||||
<div v-if="accessRestrictionOption === 'groups'">
|
||||
<div style="margin-left: 20px; display: flex;">
|
||||
<div>
|
||||
{{ $t('appstore.installDialog.users') }}: <MultiSelect v-model="accessRestriction.users" :options="users" option-label="username" />
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('appstore.installDialog.groups') }}: <MultiSelect v-model="accessRestriction.groups" :options="groups" option-label="name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { Dialog, FormGroup, InputDialog, MultiSelect, Radiobutton, TagInput, TextInput } from 'pankow';
|
||||
@@ -212,6 +141,7 @@ export default {
|
||||
|
||||
if (!yes) return;
|
||||
|
||||
// TODO
|
||||
console.log('remove', this.id)
|
||||
// await volumesModel.remove(volume.id);
|
||||
// await this.refresh();
|
||||
@@ -220,3 +150,74 @@ export default {
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog ref="applinkDialog"
|
||||
:title="mode === 'edit' ? $t('app.editApplinkDialog.title') : $t('app.addApplinkDialog.title')"
|
||||
:alternate-label="mode === 'edit' ? 'Delete' : ''"
|
||||
alternate-style="danger"
|
||||
:reject-label="$t('main.dialog.cancel')"
|
||||
:confirm-label="$t('main.dialog.save')"
|
||||
confirm-style="success"
|
||||
:confirm-active="isValid"
|
||||
:confirm-busy="busy"
|
||||
@confirm="onSubmit()"
|
||||
@alternate="onRemove()"
|
||||
>
|
||||
<InputDialog ref="inputDialog" />
|
||||
|
||||
<form @submit="onSubmit()" autocomplete="off">
|
||||
<fieldset :disabled="busy">
|
||||
<input style="display: none;" type="submit" :disabled="!isValid" />
|
||||
|
||||
<p class="has-error" v-show="error.generic">{{ error.generic }}</p>
|
||||
|
||||
<FormGroup :class="{ 'has-error': error.upstreamUri }">
|
||||
<label for="applinkUpstreamUri">{{ $t('app.applinks.upstreamUri') }}</label>
|
||||
<TextInput id="applinkUpstreamUri" v-model="upstreamUri" required />
|
||||
<span class="text-danger" v-show="error.upstreamUri">{{ error.upstreamUri }}</span>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label for="applinkLabel">{{ $t('app.applinks.label') }}</label>
|
||||
<TextInput id="applinkLabel" v-model="label" />
|
||||
</FormGroup>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<label for="previewIcon">{{ $t('app.display.icon') }}</label>
|
||||
</div>
|
||||
<div id="previewIcon" class="app-custom-icon" @click="showCustomIconSelector()">
|
||||
<img :src="iconSrc" />
|
||||
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
||||
</div>
|
||||
<span style="cursor: pointer;" @click="resetCustomIcon()">{{ $t('app.applinks.clearIconAction') }}</span> - <span class="text-small">{{ $t('app.applinks.clearIconDescription') }}</span>
|
||||
<input type="file" ref="iconFileInput" style="display: none" accept="image/png" @change="onIconFileInputChanged($event)"/>
|
||||
</div>
|
||||
|
||||
<FormGroup>
|
||||
<label for="applinkTags">{{ $t('app.display.tags') }}</label>
|
||||
<TagInput id="applinkTags" :placeholder="$t('app.display.tagsPlaceholder')" v-model="tags" v-tooltip="$t('app.display.tagsTooltip')" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label>{{ $t('app.accessControl.userManagement.dashboardVisibility') }} <sup><a href="https://docs.cloudron.io/apps/#dashboard-visibility" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<Radiobutton v-model="accessRestrictionOption" value="any" :label="$t('app.accessControl.userManagement.visibleForAllUsers')"/>
|
||||
<Radiobutton v-model="accessRestrictionOption" value="groups" :label="$t('app.accessControl.userManagement.visibleForSelected')"/>
|
||||
<!-- <span class="label label-danger"v-show="accessRestrictionOption === 'groups' && !isAccessRestrictionValid(applinkDialogData)">{{ $t('appstore.installDialog.errorUserManagementSelectAtLeastOne') }}</span> -->
|
||||
</FormGroup>
|
||||
|
||||
<div v-if="accessRestrictionOption === 'groups'">
|
||||
<div style="margin-left: 20px; display: flex;">
|
||||
<div>
|
||||
{{ $t('appstore.installDialog.users') }}: <MultiSelect v-model="accessRestriction.users" :options="users" option-label="username" />
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('appstore.installDialog.groups') }}: <MultiSelect v-model="accessRestriction.groups" :options="groups" option-label="name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
@@ -1,100 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<ApplinkDialog ref="applinkDialog" @success="refreshApps()"/>
|
||||
|
||||
<h1 class="section-header">
|
||||
{{ $t('apps.title') }}
|
||||
<div>
|
||||
<TextInput v-model="filter" placeholder="Filter ..." />
|
||||
<ButtonGroup>
|
||||
<Dropdown outline tool :options="tagFilterOptions" option-key="id" option-label="name" v-model="tagFilter"></Dropdown>
|
||||
<Dropdown outline tool :options="stateFilterOptions" option-key="id" v-model="stateFilter"></Dropdown>
|
||||
<Dropdown outline tool :options="domainFilterOptions" option-key="id" option-label="domain" v-model="domainFilter"></Dropdown>
|
||||
</ButtonGroup>
|
||||
<Button tool @click="toggleView()" :icon="viewType === VIEW_TYPE.GRID ? 'fas fa-list' : 'fas fa-grip'"></Button>
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<div v-show="ready">
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid" v-if="viewType === VIEW_TYPE.GRID">
|
||||
<a v-for="app in filteredApps" :key="app.id" class="grid-item" @click="onOpenApp(app, $event)" :href="'https://' + app.fqdn" target="_blank" v-tooltip="app.fqdn">
|
||||
<div class="config" v-show="isOperator(app)" @click.prevent="openAppEdit(app)"><Icon icon="fa-solid fa-cog" /></div>
|
||||
<img :src="API_ORIGIN + app.iconUrl" v-fallback-image="API_ORIGIN + '/img/appicon_fallback.png'"/>
|
||||
<div class="grid-item-label">{{ app.label || app.subdomain || app.fqdn }}</div>
|
||||
<div class="grid-item-task-label">{{ installationStateLabel(app) }}</div>
|
||||
<div class="apps-progress" v-show="isOperator(app)">
|
||||
<div class="apps-progress-filled" :style="{ width: app.progress+'%' }"></div>
|
||||
</div>
|
||||
</a>
|
||||
</TransitionGroup>
|
||||
|
||||
<div class="list" v-if="viewType === VIEW_TYPE.LIST">
|
||||
<TableView :columns="listColumns" :model="filteredApps">
|
||||
<template #icon="slotProps">
|
||||
<a :href="'https://' + slotProps.fqdn" target="_blank">
|
||||
<img class="list-icon" :src="API_ORIGIN + slotProps.iconUrl" v-fallback-image="API_ORIGIN + '/img/appicon_fallback.png'"/>
|
||||
</a>
|
||||
</template>
|
||||
<template #label="slotProps">
|
||||
<a :href="'https://' + slotProps.fqdn" target="_blank" v-tooltip="slotProps.fqdn">
|
||||
{{ slotProps.label || slotProps.subdomain || slotProps.fqdn }}
|
||||
</a>
|
||||
</template>
|
||||
<template #appTitle="slotProps">
|
||||
{{ slotProps.manifest.title }}
|
||||
</template>
|
||||
<template #domain="slotProps">
|
||||
<a :href="'https://' + slotProps.fqdn" target="_blank">
|
||||
{{ slotProps.fqdn }}
|
||||
</a>
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<div class="list-status">
|
||||
{{ installationStateLabel(slotProps) }}
|
||||
<div class="apps-progress" v-show="isOperator(slotProps)">
|
||||
<div class="apps-progress-filled" :style="{ width: slotProps.progress+'%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #sso="slotProps">
|
||||
<div v-show="slotProps.type !== APP_TYPES.LINK">
|
||||
<Icon icon="fa-brands fa-openid" v-show="slotProps.ssoAuth && slotProps.manifest.addons.oidc" v-tooltip="$t('apps.auth.openid')" />
|
||||
<Icon icon="fas fa-user" v-show="slotProps.ssoAuth && (!slotProps.manifest.addons.oidc && !slotProps.manifest.addons.email)" v-tooltip="$t('apps.auth.sso')" />
|
||||
<Icon icon="far fa-user" v-show="!slotProps.ssoAuth && !slotProps.manifest.addons.email" v-tooltip="$t('apps.auth.nosso')" />
|
||||
<Icon icon="fas fa-envelope" v-show="slotProps.manifest.addons.email" v-tooltip="$t('apps.auth.email')" />
|
||||
</div>
|
||||
</template>
|
||||
<template #actions="slotProps">
|
||||
<div class="actions">
|
||||
<ButtonGroup>
|
||||
<Button tool v-if="slotProps.type !== APP_TYPES.LINK" :href="'/logs.html?appId=' + slotProps.id" target="_blank" v-tooltip="$t('app.logsActionTooltip')" icon="fas fa-align-left"></Button>
|
||||
<Button tool v-if="slotProps.type !== APP_TYPES.PROXIED && slotProps.type !== APP_TYPES.LINK" :href="'/terminal.html?id=' + slotProps.id" target="_blank" v-tooltip="$t('app.terminalActionTooltip')" icon="fa fa-terminal"></Button>
|
||||
<Button tool v-if="slotProps.manifest.addons.localstorage" :href="'/filemanager.html#/home/app/' + slotProps.id" target="_blank" v-tooltip="$t('app.filemanagerActionTooltip')" icon="fas fa-folder"></Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<Button tool @click="openAppEdit(slotProps)" icon="fa-solid fa-cog"></Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
</div>
|
||||
|
||||
<div class="empty-placeholder" v-if="apps.length === 0">
|
||||
<!-- for admins -->
|
||||
<div v-if="profile.isAtLeastAdmin">
|
||||
<h4>{{ $t('apps.noApps.title') }}</h4>
|
||||
<h5 v-html="$t('apps.noApps.description', { appStoreLink: '#/appstore' })"></h5>
|
||||
</div>
|
||||
|
||||
<!-- for non-admins -->
|
||||
<div v-if="!profile.isAtLeastAdmin">
|
||||
<h4>{{ $t('apps.noAccess.title') }}</h4>
|
||||
<h5>{{ $t('apps.noAccess.description') }}</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { Button, ButtonGroup, Dropdown, Icon, TableView, TextInput } from 'pankow';
|
||||
@@ -294,6 +197,103 @@ export default {
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ApplinkDialog ref="applinkDialog" @success="refreshApps()"/>
|
||||
|
||||
<h1 class="section-header">
|
||||
{{ $t('apps.title') }}
|
||||
<div>
|
||||
<TextInput v-model="filter" placeholder="Filter ..." />
|
||||
<ButtonGroup>
|
||||
<Dropdown outline tool :options="tagFilterOptions" option-key="id" option-label="name" v-model="tagFilter"></Dropdown>
|
||||
<Dropdown outline tool :options="stateFilterOptions" option-key="id" v-model="stateFilter"></Dropdown>
|
||||
<Dropdown outline tool :options="domainFilterOptions" option-key="id" option-label="domain" v-model="domainFilter"></Dropdown>
|
||||
</ButtonGroup>
|
||||
<Button tool @click="toggleView()" :icon="viewType === VIEW_TYPE.GRID ? 'fas fa-list' : 'fas fa-grip'"></Button>
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<div v-show="ready">
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid" v-if="viewType === VIEW_TYPE.GRID">
|
||||
<a v-for="app in filteredApps" :key="app.id" class="grid-item" @click="onOpenApp(app, $event)" :href="'https://' + app.fqdn" target="_blank" v-tooltip="app.fqdn">
|
||||
<div class="config" v-show="isOperator(app)" @click.prevent="openAppEdit(app)"><Icon icon="fa-solid fa-cog" /></div>
|
||||
<img :src="API_ORIGIN + app.iconUrl" v-fallback-image="API_ORIGIN + '/img/appicon_fallback.png'"/>
|
||||
<div class="grid-item-label">{{ app.label || app.subdomain || app.fqdn }}</div>
|
||||
<div class="grid-item-task-label">{{ installationStateLabel(app) }}</div>
|
||||
<div class="apps-progress" v-show="isOperator(app)">
|
||||
<div class="apps-progress-filled" :style="{ width: app.progress+'%' }"></div>
|
||||
</div>
|
||||
</a>
|
||||
</TransitionGroup>
|
||||
|
||||
<div class="list" v-if="viewType === VIEW_TYPE.LIST">
|
||||
<TableView :columns="listColumns" :model="filteredApps">
|
||||
<template #icon="slotProps">
|
||||
<a :href="'https://' + slotProps.fqdn" target="_blank">
|
||||
<img class="list-icon" :src="API_ORIGIN + slotProps.iconUrl" v-fallback-image="API_ORIGIN + '/img/appicon_fallback.png'"/>
|
||||
</a>
|
||||
</template>
|
||||
<template #label="slotProps">
|
||||
<a :href="'https://' + slotProps.fqdn" target="_blank" v-tooltip="slotProps.fqdn">
|
||||
{{ slotProps.label || slotProps.subdomain || slotProps.fqdn }}
|
||||
</a>
|
||||
</template>
|
||||
<template #appTitle="slotProps">
|
||||
{{ slotProps.manifest.title }}
|
||||
</template>
|
||||
<template #domain="slotProps">
|
||||
<a :href="'https://' + slotProps.fqdn" target="_blank">
|
||||
{{ slotProps.fqdn }}
|
||||
</a>
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<div class="list-status">
|
||||
{{ installationStateLabel(slotProps) }}
|
||||
<div class="apps-progress" v-show="isOperator(slotProps)">
|
||||
<div class="apps-progress-filled" :style="{ width: slotProps.progress+'%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #sso="slotProps">
|
||||
<div v-show="slotProps.type !== APP_TYPES.LINK">
|
||||
<Icon icon="fa-brands fa-openid" v-show="slotProps.ssoAuth && slotProps.manifest.addons.oidc" v-tooltip="$t('apps.auth.openid')" />
|
||||
<Icon icon="fas fa-user" v-show="slotProps.ssoAuth && (!slotProps.manifest.addons.oidc && !slotProps.manifest.addons.email)" v-tooltip="$t('apps.auth.sso')" />
|
||||
<Icon icon="far fa-user" v-show="!slotProps.ssoAuth && !slotProps.manifest.addons.email" v-tooltip="$t('apps.auth.nosso')" />
|
||||
<Icon icon="fas fa-envelope" v-show="slotProps.manifest.addons.email" v-tooltip="$t('apps.auth.email')" />
|
||||
</div>
|
||||
</template>
|
||||
<template #actions="slotProps">
|
||||
<div class="actions">
|
||||
<ButtonGroup>
|
||||
<Button tool v-if="slotProps.type !== APP_TYPES.LINK" :href="'/logs.html?appId=' + slotProps.id" target="_blank" v-tooltip="$t('app.logsActionTooltip')" icon="fas fa-align-left"></Button>
|
||||
<Button tool v-if="slotProps.type !== APP_TYPES.PROXIED && slotProps.type !== APP_TYPES.LINK" :href="'/terminal.html?id=' + slotProps.id" target="_blank" v-tooltip="$t('app.terminalActionTooltip')" icon="fa fa-terminal"></Button>
|
||||
<Button tool v-if="slotProps.manifest.addons.localstorage" :href="'/filemanager.html#/home/app/' + slotProps.id" target="_blank" v-tooltip="$t('app.filemanagerActionTooltip')" icon="fas fa-folder"></Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<Button tool @click="openAppEdit(slotProps)" icon="fa-solid fa-cog"></Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
</div>
|
||||
|
||||
<div class="empty-placeholder" v-if="apps.length === 0">
|
||||
<!-- for admins -->
|
||||
<div v-if="profile.isAtLeastAdmin">
|
||||
<h4>{{ $t('apps.noApps.title') }}</h4>
|
||||
<h5 v-html="$t('apps.noApps.description', { appStoreLink: '#/appstore' })"></h5>
|
||||
</div>
|
||||
|
||||
<!-- for non-admins -->
|
||||
<div v-if="!profile.isAtLeastAdmin">
|
||||
<h4>{{ $t('apps.noAccess.title') }}</h4>
|
||||
<h5>{{ $t('apps.noAccess.description') }}</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.grid-animation-move,
|
||||
|
||||
@@ -1,55 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<AppInstallDialog ref="appInstallDialog" @close="onAppInstallDialogClose"/>
|
||||
<ApplinkDialog ref="applinkDialog" @success="onApplinkDialogSuccess()"/>
|
||||
|
||||
<div class="filter-bar">
|
||||
<div></div>
|
||||
<TextInput ref="searchInput" @keydown.esc="search = ''" v-model="search" :placeholder="$t('appstore.searchPlaceholder')" style="max-width: 100%; width: 500px;"/>
|
||||
<ButtonGroup>
|
||||
<Button outline icon="fas fa-exchange-alt" @click="onInstall(proxyApp)">{{ $t('apps.addAppproxyAction') }}</Button>
|
||||
<Button outline icon="fas fa-link" @click="onApplinkDialogOpen()">{{ $t('apps.addApplinkAction') }}</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div v-if="!search">
|
||||
<h4 v-show="filteredPopularApps.length">{{ $t('appstore.category.popular') }}</h4>
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid" v-show="ready">
|
||||
<div class="item" v-for="app in filteredPopularApps" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)">
|
||||
<img class="icon" :src="app.iconUrl" />
|
||||
<div class="description">
|
||||
<div class="title">{{ app.manifest.title }}</div>
|
||||
<div class="tagline">{{ app.manifest.tagline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
|
||||
<h4 v-show="filteredAllApps.length">{{ $t('appstore.category.all') }}</h4>
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid" v-show="ready">
|
||||
<div class="item" v-for="app in filteredAllApps" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)">
|
||||
<img class="icon" :src="app.iconUrl" />
|
||||
<div class="description">
|
||||
<div class="title">{{ app.manifest.title }}</div>
|
||||
<div class="tagline">{{ app.manifest.tagline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
<div v-else>
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid" v-show="ready">
|
||||
<div class="item" v-for="app in filteredApps" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)">
|
||||
<img class="icon" :src="app.iconUrl" />
|
||||
<div class="description">
|
||||
<div class="title">{{ app.manifest.title }}</div>
|
||||
<div class="tagline">{{ app.manifest.tagline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref, computed, useTemplateRef, onMounted } from 'vue';
|
||||
@@ -130,6 +78,58 @@ onMounted(async () => {
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AppInstallDialog ref="appInstallDialog" @close="onAppInstallDialogClose"/>
|
||||
<ApplinkDialog ref="applinkDialog" @success="onApplinkDialogSuccess()"/>
|
||||
|
||||
<div class="filter-bar">
|
||||
<div></div>
|
||||
<TextInput ref="searchInput" @keydown.esc="search = ''" v-model="search" :placeholder="$t('appstore.searchPlaceholder')" style="max-width: 100%; width: 500px;"/>
|
||||
<ButtonGroup>
|
||||
<Button outline icon="fas fa-exchange-alt" @click="onInstall(proxyApp)">{{ $t('apps.addAppproxyAction') }}</Button>
|
||||
<Button outline icon="fas fa-link" @click="onApplinkDialogOpen()">{{ $t('apps.addApplinkAction') }}</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div v-if="!search">
|
||||
<h4 v-show="filteredPopularApps.length">{{ $t('appstore.category.popular') }}</h4>
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid" v-show="ready">
|
||||
<div class="item" v-for="app in filteredPopularApps" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)">
|
||||
<img class="icon" :src="app.iconUrl" />
|
||||
<div class="description">
|
||||
<div class="title">{{ app.manifest.title }}</div>
|
||||
<div class="tagline">{{ app.manifest.tagline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
|
||||
<h4 v-show="filteredAllApps.length">{{ $t('appstore.category.all') }}</h4>
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid" v-show="ready">
|
||||
<div class="item" v-for="app in filteredAllApps" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)">
|
||||
<img class="icon" :src="app.iconUrl" />
|
||||
<div class="description">
|
||||
<div class="title">{{ app.manifest.title }}</div>
|
||||
<div class="tagline">{{ app.manifest.tagline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
<div v-else>
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid" v-show="ready">
|
||||
<div class="item" v-for="app in filteredApps" :key="app.id" :ref="'item-' + app.id" @click="onInstall(app)">
|
||||
<img class="icon" :src="app.iconUrl" />
|
||||
<div class="description">
|
||||
<div class="title">{{ app.manifest.title }}</div>
|
||||
<div class="tagline">{{ app.manifest.tagline }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.grid-animation-move,
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<!-- router-view needs some fake node first for some unknown reason -->
|
||||
<span></span>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
@@ -12,11 +6,14 @@ export default {
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (!localStorage.token) console.error('Set localStorage.token')
|
||||
if (!localStorage.token) console.error('Set localStorage.token');
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
<template>
|
||||
<!-- router-view needs some fake node first for some unknown reason -->
|
||||
<span></span>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
<template>
|
||||
<div class="viewer">
|
||||
<TextViewer ref="textEditor"
|
||||
v-show="active === 'textEditor'"
|
||||
:save-handler="saveHandler"
|
||||
@close="onClose"
|
||||
:tr="$t"
|
||||
/>
|
||||
<ImageViewer ref="imageViewer" v-show="active === 'imageViewer'" @close="onClose" :navigation-handler="imageViewerNavigationHandler"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { TextViewer, ImageViewer } from 'pankow-viewers';
|
||||
@@ -85,6 +73,18 @@ export default {
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="viewer">
|
||||
<TextViewer ref="textEditor"
|
||||
v-show="active === 'textEditor'"
|
||||
:save-handler="saveHandler"
|
||||
@close="onClose"
|
||||
:tr="$t"
|
||||
/>
|
||||
<ImageViewer ref="imageViewer" v-show="active === 'imageViewer'" @close="onClose" :navigation-handler="imageViewerNavigationHandler"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.viewer{
|
||||
|
||||
@@ -1,100 +1,3 @@
|
||||
<template>
|
||||
<MainLayout>
|
||||
<template #dialogs>
|
||||
<Notification />
|
||||
|
||||
<Dialog ref="fatalErrorDialog" modal title="Error">
|
||||
<p>{{ fatalError }}</p>
|
||||
</Dialog>
|
||||
|
||||
<Dialog ref="extractInProgressDialog" modal :title="$t('filemanager.extractionInProgress')">
|
||||
<div style="text-align: center;">
|
||||
<Spinner style="margin: 10px; width: 50px; height: 50px"/>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Dialog ref="pasteInProgressDialog" modal :title="$t('filemanager.pasteInProgress')">
|
||||
<div style="text-align: center;">
|
||||
<Spinner style="margin: 10px; width: 50px; height: 50px"/>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Dialog ref="deleteInProgressDialog" modal :title="$t('filemanager.deleteInProgress')">
|
||||
<div style="text-align: center;">
|
||||
<Spinner style="margin: 10px; width: 50px; height: 50px"/>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<InputDialog ref="inputDialog" />
|
||||
</template>
|
||||
<template #header>
|
||||
<TopBar class="navbar">
|
||||
<template #left>
|
||||
<Button icon="fa-solid fa-arrow-rotate-right" :loading="busyRefresh" @click="onRefresh()" secondary plain tool/>
|
||||
<Breadcrumb :home="breadcrumbHomeItem" :items="breadcrumbItems" :activate-handler="onActivateBreadcrumb"/>
|
||||
</template>
|
||||
<template #right>
|
||||
<Button icon="fa-solid fa-plus" :menu="createMenuModel">{{ $t('filemanager.toolbar.new') }}</Button>
|
||||
<Button icon="fa-solid fa-upload" :menu="uploadMenuModel">{{ $t('filemanager.toolbar.upload') }}</Button>
|
||||
|
||||
<Button style="margin-left: 20px;" :title="$t('filemanager.toolbar.restartApp')" secondary tool :loading="busyRestart" icon="fa-solid fa-arrows-rotate" @click="onRestartApp" v-show="resourceType === 'app'"/>
|
||||
<Button :href="'/terminal.html?id=' + resourceId" target="_blank" v-show="resourceType === 'app'" secondary tool icon="fa-solid fa-terminal" :title="$t('terminal.title')" />
|
||||
<Button :href="'/logs.html?appId=' + resourceId" target="_blank" v-show="resourceType === 'app'" secondary tool icon="fa-solid fa-align-left" :title="$t('logs.title')" />
|
||||
</template>
|
||||
</TopBar>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="main-view">
|
||||
<div class="main-view-col">
|
||||
<DirectoryView
|
||||
class="directory-view"
|
||||
:busy="busy"
|
||||
:show-owner="true"
|
||||
:show-size="true"
|
||||
:show-modified="true"
|
||||
@selection-changed="onSelectionChanged"
|
||||
@item-activated="onItemActivated"
|
||||
:delete-handler="deleteHandler"
|
||||
:rename-handler="renameHandler"
|
||||
:change-owner-handler="changeOwnerHandler"
|
||||
:paste-handler="pasteHandler"
|
||||
:download-handler="downloadHandler"
|
||||
:extract-handler="extractHandler"
|
||||
:new-file-handler="onNewFile"
|
||||
:new-folder-handler="onNewFolder"
|
||||
:upload-file-handler="onUploadFile"
|
||||
:upload-folder-handler="onUploadFolder"
|
||||
:drop-handler="onDrop"
|
||||
:items="items"
|
||||
:owners-model="ownersModel"
|
||||
:fallback-icon="fallbackIcon"
|
||||
:tr="$t"
|
||||
/>
|
||||
</div>
|
||||
<div class="main-view-col" style="max-width: 300px;">
|
||||
<div class="side-bar-title">
|
||||
<a v-show="appLink" :href="appLink" target="_blank">{{ title }}</a>
|
||||
<span v-show="!appLink">{{ title }}</span>
|
||||
</div>
|
||||
<PreviewPanel :item="activeItem || activeDirectoryItem" :fallback-icon="fallbackIcon"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<FileUploader
|
||||
ref="fileUploader"
|
||||
:upload-handler="uploadHandler"
|
||||
:cancel-handler="onCancelUpload"
|
||||
@finished="onUploadFinished"
|
||||
:tr="$t"
|
||||
/>
|
||||
<BottomBar>
|
||||
<div v-html="footerContent" class="bottom-bar-content"></div>
|
||||
</BottomBar>
|
||||
</template>
|
||||
</MainLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { marked } from 'marked';
|
||||
@@ -577,6 +480,103 @@ export default {
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainLayout>
|
||||
<template #dialogs>
|
||||
<Notification />
|
||||
|
||||
<Dialog ref="fatalErrorDialog" modal title="Error">
|
||||
<p>{{ fatalError }}</p>
|
||||
</Dialog>
|
||||
|
||||
<Dialog ref="extractInProgressDialog" modal :title="$t('filemanager.extractionInProgress')">
|
||||
<div style="text-align: center;">
|
||||
<Spinner style="margin: 10px; width: 50px; height: 50px"/>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Dialog ref="pasteInProgressDialog" modal :title="$t('filemanager.pasteInProgress')">
|
||||
<div style="text-align: center;">
|
||||
<Spinner style="margin: 10px; width: 50px; height: 50px"/>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Dialog ref="deleteInProgressDialog" modal :title="$t('filemanager.deleteInProgress')">
|
||||
<div style="text-align: center;">
|
||||
<Spinner style="margin: 10px; width: 50px; height: 50px"/>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<InputDialog ref="inputDialog" />
|
||||
</template>
|
||||
<template #header>
|
||||
<TopBar class="navbar">
|
||||
<template #left>
|
||||
<Button icon="fa-solid fa-arrow-rotate-right" :loading="busyRefresh" @click="onRefresh()" secondary plain tool/>
|
||||
<Breadcrumb :home="breadcrumbHomeItem" :items="breadcrumbItems" :activate-handler="onActivateBreadcrumb"/>
|
||||
</template>
|
||||
<template #right>
|
||||
<Button icon="fa-solid fa-plus" :menu="createMenuModel">{{ $t('filemanager.toolbar.new') }}</Button>
|
||||
<Button icon="fa-solid fa-upload" :menu="uploadMenuModel">{{ $t('filemanager.toolbar.upload') }}</Button>
|
||||
|
||||
<Button style="margin-left: 20px;" :title="$t('filemanager.toolbar.restartApp')" secondary tool :loading="busyRestart" icon="fa-solid fa-arrows-rotate" @click="onRestartApp" v-show="resourceType === 'app'"/>
|
||||
<Button :href="'/terminal.html?id=' + resourceId" target="_blank" v-show="resourceType === 'app'" secondary tool icon="fa-solid fa-terminal" :title="$t('terminal.title')" />
|
||||
<Button :href="'/logs.html?appId=' + resourceId" target="_blank" v-show="resourceType === 'app'" secondary tool icon="fa-solid fa-align-left" :title="$t('logs.title')" />
|
||||
</template>
|
||||
</TopBar>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="main-view">
|
||||
<div class="main-view-col">
|
||||
<DirectoryView
|
||||
class="directory-view"
|
||||
:busy="busy"
|
||||
:show-owner="true"
|
||||
:show-size="true"
|
||||
:show-modified="true"
|
||||
@selection-changed="onSelectionChanged"
|
||||
@item-activated="onItemActivated"
|
||||
:delete-handler="deleteHandler"
|
||||
:rename-handler="renameHandler"
|
||||
:change-owner-handler="changeOwnerHandler"
|
||||
:paste-handler="pasteHandler"
|
||||
:download-handler="downloadHandler"
|
||||
:extract-handler="extractHandler"
|
||||
:new-file-handler="onNewFile"
|
||||
:new-folder-handler="onNewFolder"
|
||||
:upload-file-handler="onUploadFile"
|
||||
:upload-folder-handler="onUploadFolder"
|
||||
:drop-handler="onDrop"
|
||||
:items="items"
|
||||
:owners-model="ownersModel"
|
||||
:fallback-icon="fallbackIcon"
|
||||
:tr="$t"
|
||||
/>
|
||||
</div>
|
||||
<div class="main-view-col" style="max-width: 300px;">
|
||||
<div class="side-bar-title">
|
||||
<a v-show="appLink" :href="appLink" target="_blank">{{ title }}</a>
|
||||
<span v-show="!appLink">{{ title }}</span>
|
||||
</div>
|
||||
<PreviewPanel :item="activeItem || activeDirectoryItem" :fallback-icon="fallbackIcon"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<FileUploader
|
||||
ref="fileUploader"
|
||||
:upload-handler="uploadHandler"
|
||||
:cancel-handler="onCancelUpload"
|
||||
@finished="onUploadFinished"
|
||||
:tr="$t"
|
||||
/>
|
||||
<BottomBar>
|
||||
<div v-html="footerContent" class="bottom-bar-content"></div>
|
||||
</BottomBar>
|
||||
</template>
|
||||
</MainLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.main-view {
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
<template>
|
||||
<div>
|
||||
<Notification />
|
||||
<AppsView v-if="view === VIEWS.APPS" />
|
||||
<AppstoreView v-if="view === VIEWS.APPSTORE" />
|
||||
<ProfileView v-if="view === VIEWS.PROFILE" />
|
||||
<SupportView v-if="view === VIEWS.SUPPORT" />
|
||||
<VolumesView v-if="view === VIEWS.VOLUMES" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { Notification } from 'pankow';
|
||||
@@ -89,6 +78,13 @@ export default {
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<Notification />
|
||||
<AppsView v-if="view === VIEWS.APPS" />
|
||||
<AppstoreView v-if="view === VIEWS.APPSTORE" />
|
||||
<ProfileView v-if="view === VIEWS.PROFILE" />
|
||||
<SupportView v-if="view === VIEWS.SUPPORT" />
|
||||
<VolumesView v-if="view === VIEWS.VOLUMES" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,51 +1,3 @@
|
||||
<template>
|
||||
<div class="layout-root" v-show="ready">
|
||||
<div class="layout-left">
|
||||
<img width="128" height="128" class="icon" :src="iconUrl"/>
|
||||
</div>
|
||||
<div class="layout-right">
|
||||
<small>{{ $t('login.loginTo') }}</small>
|
||||
<h1>{{ name }}</h1>
|
||||
<br/>
|
||||
<div :html="note"></div>
|
||||
|
||||
<p class="has-error" v-show="passwordError">{{ $t('login.errorIncorrectCredentials') }}</p>
|
||||
<p class="has-error" v-show="internalError">{{ $t('login.errorInternal') }}</p>
|
||||
|
||||
<form @submit.prevent="onSubmit" v-show="!totpTokenRequired">
|
||||
<input type="submit" style="display: none;"/>
|
||||
|
||||
<div class="form-element">
|
||||
<label for="inputUsername">{{ $t('login.username') }}</label>
|
||||
<TextInput id="inputUsername" v-model="username" autofocus required/>
|
||||
</div>
|
||||
|
||||
<div class="form-element">
|
||||
<label for="inputPassword">{{ $t('login.password') }}</label>
|
||||
<PasswordInput id="inputPassword" v-model="password" required/>
|
||||
</div>
|
||||
|
||||
<Button id="loginSubmitButton" style="margin-top: 12px" @click.prevent="onSubmit" :loading="busy">{{ $t('login.signInAction') }}</Button>
|
||||
<a href="/passwordreset.html" style="margin-left: 10px;">{{ $t('login.resetPasswordAction') }}</a>
|
||||
</form>
|
||||
|
||||
<form @submit.prevent="onSubmit" v-show="totpTokenRequired">
|
||||
<input type="submit" style="display: none;"/>
|
||||
|
||||
<div class="form-element">
|
||||
<label for="inputTotpToken">{{ $t('login.2faToken') }}</label>
|
||||
<TextInput id="inputTotpToken" v-model="totpToken"/>
|
||||
<p class="has-error" v-show="totpError">{{ $t('login.errorIncorrect2FAToken') }}</p>
|
||||
</div>
|
||||
|
||||
<Button id="totpTokenSubmitButton" style="margin-top: 12px" type="submit" @click.prevent="onSubmit" :loading="busy">{{ $t('login.signInAction') }}</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer v-show="footer" v-html="footer"></footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { Button, PasswordInput, TextInput, fetcher } from 'pankow';
|
||||
@@ -139,6 +91,54 @@ export default {
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layout-root" v-show="ready">
|
||||
<div class="layout-left">
|
||||
<img width="128" height="128" class="icon" :src="iconUrl"/>
|
||||
</div>
|
||||
<div class="layout-right">
|
||||
<small>{{ $t('login.loginTo') }}</small>
|
||||
<h1>{{ name }}</h1>
|
||||
<br/>
|
||||
<div :html="note"></div>
|
||||
|
||||
<p class="has-error" v-show="passwordError">{{ $t('login.errorIncorrectCredentials') }}</p>
|
||||
<p class="has-error" v-show="internalError">{{ $t('login.errorInternal') }}</p>
|
||||
|
||||
<form @submit.prevent="onSubmit" v-show="!totpTokenRequired">
|
||||
<input type="submit" style="display: none;"/>
|
||||
|
||||
<div class="form-element">
|
||||
<label for="inputUsername">{{ $t('login.username') }}</label>
|
||||
<TextInput id="inputUsername" v-model="username" autofocus required/>
|
||||
</div>
|
||||
|
||||
<div class="form-element">
|
||||
<label for="inputPassword">{{ $t('login.password') }}</label>
|
||||
<PasswordInput id="inputPassword" v-model="password" required/>
|
||||
</div>
|
||||
|
||||
<Button id="loginSubmitButton" style="margin-top: 12px" @click.prevent="onSubmit" :loading="busy">{{ $t('login.signInAction') }}</Button>
|
||||
<a href="/passwordreset.html" style="margin-left: 10px;">{{ $t('login.resetPasswordAction') }}</a>
|
||||
</form>
|
||||
|
||||
<form @submit.prevent="onSubmit" v-show="totpTokenRequired">
|
||||
<input type="submit" style="display: none;"/>
|
||||
|
||||
<div class="form-element">
|
||||
<label for="inputTotpToken">{{ $t('login.2faToken') }}</label>
|
||||
<TextInput id="inputTotpToken" v-model="totpToken"/>
|
||||
<p class="has-error" v-show="totpError">{{ $t('login.errorIncorrect2FAToken') }}</p>
|
||||
</div>
|
||||
|
||||
<Button id="totpTokenSubmitButton" style="margin-top: 12px" type="submit" @click.prevent="onSubmit" :loading="busy">{{ $t('login.signInAction') }}</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer v-show="footer" v-html="footer"></footer>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
||||
.icon {
|
||||
|
||||
@@ -1,30 +1,3 @@
|
||||
<template>
|
||||
<MainLayout>
|
||||
<template #dialogs>
|
||||
<InputDialog ref="inputDialog" />
|
||||
</template>
|
||||
<template #header>
|
||||
<TopBar class="navbar">
|
||||
<template #left>
|
||||
<span class="title">{{ name }}</span>
|
||||
</template>
|
||||
<template #right>
|
||||
<Button icon="fa-solid fa-eraser" @click="onClear()" style="margin-right: 5px">{{ $t('logs.clear') }}</Button>
|
||||
<Button :href="downloadUrl" target="_blank" icon="fa-solid fa-download">{{ $t('logs.download') }}</Button>
|
||||
|
||||
<Button style="margin-left: 20px;" :title="$t('filemanager.toolbar.restartApp')" v-show="showRestart" secondary tool :loading="busyRestart" icon="fa-solid fa-arrows-rotate" @click="onRestartApp"/>
|
||||
<Button :href="'/terminal.html?id=' + id" target="_blank" v-show="showTerminal" secondary tool icon="fa-solid fa-terminal" :title="$t('terminal.title')" />
|
||||
<Button :href="'/filemanager.html#/home/app/' + id" target="_blank" v-show="showFilemanager" secondary tool icon="fa-solid fa-folder" :title="$t('filemanager.title')" />
|
||||
</template>
|
||||
</TopBar>
|
||||
</template>
|
||||
<template #body>
|
||||
<div ref="linesContainer"></div>
|
||||
<div class="bottom-spacer"></div>
|
||||
</template>
|
||||
</MainLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { Button, InputDialog, TopBar, MainLayout } from 'pankow';
|
||||
@@ -149,9 +122,9 @@ export default {
|
||||
|
||||
const tmp = document.getElementsByClassName('pankow-main-layout-body')[0];
|
||||
setInterval(() => {
|
||||
newLogLines = newLogLines.slice(-maxLines)
|
||||
newLogLines = newLogLines.slice(-maxLines);
|
||||
|
||||
for (let line of newLogLines) {
|
||||
for (const line of newLogLines) {
|
||||
if (lines < maxLines) ++lines;
|
||||
else this.$refs.linesContainer.removeChild(this.$refs.linesContainer.firstChild);
|
||||
|
||||
@@ -171,12 +144,39 @@ export default {
|
||||
newLogLines.push({ time, html });
|
||||
}, function (error) {
|
||||
newLogLines.push({ time: error.time, html: error.html });
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainLayout>
|
||||
<template #dialogs>
|
||||
<InputDialog ref="inputDialog" />
|
||||
</template>
|
||||
<template #header>
|
||||
<TopBar class="navbar">
|
||||
<template #left>
|
||||
<span class="title">{{ name }}</span>
|
||||
</template>
|
||||
<template #right>
|
||||
<Button icon="fa-solid fa-eraser" @click="onClear()" style="margin-right: 5px">{{ $t('logs.clear') }}</Button>
|
||||
<Button :href="downloadUrl" target="_blank" icon="fa-solid fa-download">{{ $t('logs.download') }}</Button>
|
||||
|
||||
<Button style="margin-left: 20px;" :title="$t('filemanager.toolbar.restartApp')" v-show="showRestart" secondary tool :loading="busyRestart" icon="fa-solid fa-arrows-rotate" @click="onRestartApp"/>
|
||||
<Button :href="'/terminal.html?id=' + id" target="_blank" v-show="showTerminal" secondary tool icon="fa-solid fa-terminal" :title="$t('terminal.title')" />
|
||||
<Button :href="'/filemanager.html#/home/app/' + id" target="_blank" v-show="showFilemanager" secondary tool icon="fa-solid fa-folder" :title="$t('filemanager.title')" />
|
||||
</template>
|
||||
</TopBar>
|
||||
</template>
|
||||
<template #body>
|
||||
<div ref="linesContainer"></div>
|
||||
<div class="bottom-spacer"></div>
|
||||
</template>
|
||||
</MainLayout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
||||
body {
|
||||
|
||||
@@ -1,73 +1,3 @@
|
||||
<template>
|
||||
|
||||
<div class="layout-root">
|
||||
<div class="layout-left">
|
||||
<img width="128" height="128" class="icon" :src="'/api/v1/cloudron/avatar'"/>
|
||||
</div>
|
||||
|
||||
<div class="layout-right">
|
||||
|
||||
<div v-show="mode === 'passwordReset'">
|
||||
<h2>{{ $t('passwordReset.title') }}</h2>
|
||||
|
||||
<form name="passwordResetForm" @submit.prevent="onPasswordReset()">
|
||||
<input type="submit" style="display: none;"/>
|
||||
|
||||
<div class="form-element">
|
||||
<label for="inputPasswordResetIdentifier">{{ $t('passwordReset.usernameOrEmail') }}</label>
|
||||
<TextInput id="inputPasswordResetIdentifier" name="passwordResetIdentifier" v-model="passwordResetIdentifier" :disabled="busy" autofocus required />
|
||||
</div>
|
||||
|
||||
<Button style="margin-top: 12px" @click="onPasswordReset()" :disabled="busy || !passwordResetIdentifier" :loading="busy">{{ $t('passwordReset.resetAction') }}</Button>
|
||||
<a href="/" style="margin-left: 10px;">{{ $t('passwordReset.backToLoginAction') }}</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-show="mode === 'passwordResetDone'">
|
||||
<h2 v-show="!error">{{ $t('passwordReset.emailSent.title') }}</h2>
|
||||
<h4 v-show="error" class="has-error">{{ error }}</h4>
|
||||
<Button href="/">{{ $t('passwordReset.backToLoginAction') }}</Button>
|
||||
</div>
|
||||
|
||||
<div v-show="mode === 'newPassword'">
|
||||
<h2>{{ $t('passwordReset.newPassword.title') }}</h2>
|
||||
|
||||
<p class="has-error" v-show="error">{{ error }}</p>
|
||||
|
||||
<form name="newPasswordForm" @submit.prevent="onNewPassword()">
|
||||
<input type="submit" style="display: none;"/>
|
||||
<input type="password" style="display: none;"/>
|
||||
|
||||
<div class="form-element" :class="{'has-error': newPasswordRepeat && newPassword !== newPasswordRepeat}">
|
||||
<label for="inputNewPassword">{{ $t('passwordReset.newPassword.password') }}</label>
|
||||
<PasswordInput id="inputNewPassword" v-model="newPassword" autofocus required />
|
||||
</div>
|
||||
|
||||
<div class="form-element" :class="{'has-error': newPasswordRepeat && newPassword !== newPasswordRepeat}">
|
||||
<label for="inputNewPasswordRepeat">{{ $t('passwordReset.newPassword.passwordRepeat') }}</label>
|
||||
<PasswordInput id="inputNewPasswordRepeat" v-model="newPasswordRepeat" required />
|
||||
</div>
|
||||
|
||||
<div class="form-element">
|
||||
<label for="inputPasswordResetTotpToken">{{ $t('login.2faToken') }}</label>
|
||||
<TextInput id="inputPasswordResetTotpToken" v-model="totpToken" :disabled="busy" />
|
||||
</div>
|
||||
|
||||
<Button style="margin-top: 12px" @click="onNewPassword()" :disabled="busy || !newPassword || newPassword !== newPasswordRepeat" :loading="busy">{{ $t('passwordReset.passwordChanged.submitAction') }}</Button>
|
||||
<a href="/" style="margin-left: 10px;">{{ $t('passwordReset.backToLoginAction') }}</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-show="mode === 'newPasswordDone'">
|
||||
<h2>{{ $t('passwordReset.success.title') }}</h2>
|
||||
<Button href="/">{{ $t('passwordReset.success.openDashboardAction') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer v-show="footer" v-html="footer"></footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
||||
@@ -169,6 +99,75 @@ export default {
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layout-root">
|
||||
<div class="layout-left">
|
||||
<img width="128" height="128" class="icon" :src="'/api/v1/cloudron/avatar'"/>
|
||||
</div>
|
||||
|
||||
<div class="layout-right">
|
||||
|
||||
<div v-show="mode === 'passwordReset'">
|
||||
<h2>{{ $t('passwordReset.title') }}</h2>
|
||||
|
||||
<form name="passwordResetForm" @submit.prevent="onPasswordReset()">
|
||||
<input type="submit" style="display: none;"/>
|
||||
|
||||
<div class="form-element">
|
||||
<label for="inputPasswordResetIdentifier">{{ $t('passwordReset.usernameOrEmail') }}</label>
|
||||
<TextInput id="inputPasswordResetIdentifier" name="passwordResetIdentifier" v-model="passwordResetIdentifier" :disabled="busy" autofocus required />
|
||||
</div>
|
||||
|
||||
<Button style="margin-top: 12px" @click="onPasswordReset()" :disabled="busy || !passwordResetIdentifier" :loading="busy">{{ $t('passwordReset.resetAction') }}</Button>
|
||||
<a href="/" style="margin-left: 10px;">{{ $t('passwordReset.backToLoginAction') }}</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-show="mode === 'passwordResetDone'">
|
||||
<h2 v-show="!error">{{ $t('passwordReset.emailSent.title') }}</h2>
|
||||
<h4 v-show="error" class="has-error">{{ error }}</h4>
|
||||
<Button href="/">{{ $t('passwordReset.backToLoginAction') }}</Button>
|
||||
</div>
|
||||
|
||||
<div v-show="mode === 'newPassword'">
|
||||
<h2>{{ $t('passwordReset.newPassword.title') }}</h2>
|
||||
|
||||
<p class="has-error" v-show="error">{{ error }}</p>
|
||||
|
||||
<form name="newPasswordForm" @submit.prevent="onNewPassword()">
|
||||
<input type="submit" style="display: none;"/>
|
||||
<input type="password" style="display: none;"/>
|
||||
|
||||
<div class="form-element" :class="{'has-error': newPasswordRepeat && newPassword !== newPasswordRepeat}">
|
||||
<label for="inputNewPassword">{{ $t('passwordReset.newPassword.password') }}</label>
|
||||
<PasswordInput id="inputNewPassword" v-model="newPassword" autofocus required />
|
||||
</div>
|
||||
|
||||
<div class="form-element" :class="{'has-error': newPasswordRepeat && newPassword !== newPasswordRepeat}">
|
||||
<label for="inputNewPasswordRepeat">{{ $t('passwordReset.newPassword.passwordRepeat') }}</label>
|
||||
<PasswordInput id="inputNewPasswordRepeat" v-model="newPasswordRepeat" required />
|
||||
</div>
|
||||
|
||||
<div class="form-element">
|
||||
<label for="inputPasswordResetTotpToken">{{ $t('login.2faToken') }}</label>
|
||||
<TextInput id="inputPasswordResetTotpToken" v-model="totpToken" :disabled="busy" />
|
||||
</div>
|
||||
|
||||
<Button style="margin-top: 12px" @click="onNewPassword()" :disabled="busy || !newPassword || newPassword !== newPasswordRepeat" :loading="busy">{{ $t('passwordReset.passwordChanged.submitAction') }}</Button>
|
||||
<a href="/" style="margin-left: 10px;">{{ $t('passwordReset.backToLoginAction') }}</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-show="mode === 'newPasswordDone'">
|
||||
<h2>{{ $t('passwordReset.success.title') }}</h2>
|
||||
<Button href="/">{{ $t('passwordReset.success.openDashboardAction') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer v-show="footer" v-html="footer"></footer>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
||||
.icon {
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
<template>
|
||||
<div v-for="ports in [ tcpPorts, udpPorts ]" :key="ports">
|
||||
<FormGroup v-for="(port, key) in ports" :key="key">
|
||||
<Checkbox :label="port.title" v-model="port.enabled" />
|
||||
<small>{{ port.description + '.' + (port.portCount >=1 ? (port.portCount + ' ports. ') : '') }}</small>
|
||||
<small v-show="port.readOnly">{{ $t('appstore.installDialog.portReadOnly') }}</small>
|
||||
<small class="has-error" v-show="error.port === port.value">Port already taken</small>
|
||||
<NumberInput v-model="port.value" :disabled="!port.enabled" :min="1"/>
|
||||
<!-- TODO <p class="text-small text-warning text-bold" ng-show="appInstall.domain.provider === 'cloudflare'">{{ 'appstore.installDialog.cloudflarePortWarning' | tr }} </p> -->
|
||||
</FormGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { FormGroup, Checkbox, NumberInput } from 'pankow';
|
||||
@@ -33,5 +20,15 @@ for (const p in props.udpPorts) {
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
<template>
|
||||
<div v-for="ports in [ tcpPorts, udpPorts ]" :key="ports">
|
||||
<FormGroup v-for="(port, key) in ports" :key="key">
|
||||
<Checkbox :label="port.title" v-model="port.enabled" />
|
||||
<small>{{ port.description + '.' + (port.portCount >=1 ? (port.portCount + ' ports. ') : '') }}</small>
|
||||
<small v-show="port.readOnly">{{ $t('appstore.installDialog.portReadOnly') }}</small>
|
||||
<small class="has-error" v-show="error.port === port.value">Port already taken</small>
|
||||
<NumberInput v-model="port.value" :disabled="!port.enabled" :min="1"/>
|
||||
<!-- TODO <p class="text-small text-warning text-bold" ng-show="appInstall.domain.provider === 'cloudflare'">{{ 'appstore.installDialog.cloudflarePortWarning' | tr }} </p> -->
|
||||
</FormGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
<template>
|
||||
<div class="preview-panel">
|
||||
<img :src="item.previewUrl || item.icon" :alt="item.name" :class="{'shadow': item.previewUrl }" @error="iconError($event)"/>
|
||||
<p>{{ item.name }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
@@ -22,6 +15,13 @@ export default {
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="preview-panel">
|
||||
<img :src="item.previewUrl || item.icon" :alt="item.name" :class="{'shadow': item.previewUrl }" @error="iconError($event)"/>
|
||||
<p>{{ item.name }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.preview-panel {
|
||||
|
||||
@@ -1,90 +1,3 @@
|
||||
<template>
|
||||
<div class="content">
|
||||
<InputDialog ref="inputDialog" />
|
||||
|
||||
<Dialog ref="twoFADialog"
|
||||
:title="$t('profile.enable2FA.title')">
|
||||
<div style="text-align: center; max-width: 420px">
|
||||
<p v-show="mandatory2FAHelp">{{ $t('profile.enable2FA.description') }}</p>
|
||||
<p v-html="$t('profile.enable2FA.authenticatorAppDescription', { googleAuthenticatorPlayStoreLink: 'https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2', googleAuthenticatorITunesLink: 'https://itunes.apple.com/us/app/google-authenticator/id388497605', freeOTPPlayStoreLink: 'https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp', freeOTPITunesLink: 'https://itunes.apple.com/us/app/freeotp-authenticator/id872559395'})"></p>
|
||||
<img :src="twoFAQRCode" style="border-radius: 10px; margin-bottom: 10px"/>
|
||||
<small>{{ twoFASecret }}</small>
|
||||
<br/>
|
||||
<br/>
|
||||
<p class="has-error" v-show="twoFAEnableError">{{ twoFAEnableError }} </p>
|
||||
<form @submit.prevent="onTwoFAEnable()">
|
||||
<input type="submit" style="display: none;" :disabled="!twoFATotpToken"/>
|
||||
<FormGroup>
|
||||
<label for="totpTokenInput">{{ $t('profile.enable2FA.token') }}</label>
|
||||
<TextInput v-model="twoFATotpToken" id="totpTokenInput" />
|
||||
</FormGroup>
|
||||
<Button @click="onTwoFAEnable()" :disabled="!twoFATotpToken">{{ $t('profile.enable2FA.enable') }}</Button>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Section :title="$t('profile.title')">
|
||||
<template #header-buttons>
|
||||
<Button @click="profileModel.logout()" icon="fa fa-sign-out">{{ $t('main.logout') }}</Button>
|
||||
</template>
|
||||
|
||||
<div style="display: flex;">
|
||||
<div style="width: 150px;">
|
||||
<input type="file" ref="avatarFileInput" style="display: none" accept="image/*" @change="onAvatarChanged()"/>
|
||||
<div class="settings-avatar" :style="`background-image: url('${user.avatarUrl}');`" @click="avatarFileInput.click()">
|
||||
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex-grow: 1;">
|
||||
<table style="width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-muted">{{ $t('main.username') }}</td>
|
||||
<td style="width: 100px; height: 34px;">{{ user.username }}</td>
|
||||
<td style="width: 32px"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">{{ $t('main.displayName') }}</td>
|
||||
<td style="white-space: nowrap;">{{ user.displayName }}</td>
|
||||
<td><Button small tool outline @click="onChangeDisplayName(user.displayName)" v-show="!user.source && !config.profileLocked" icon="fa fa-edit text-small" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">{{ $t('profile.primaryEmail') }}</td>
|
||||
<td style="white-space: nowrap;">{{ user.email }}</td>
|
||||
<td><Button small tool outline @click="onChangeEmail(user.email)" v-show="!user.source && !config.profileLocked" icon="fa fa-edit text-small" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">{{ $t('profile.passwordRecoveryEmail') }}</td>
|
||||
<td style="white-space: nowrap;">{{ user.fallbackEmail }}</td>
|
||||
<td><Button small tool outline @click="onChangeFallbackEmail(user.fallbackEmail)" v-show="!user.source && !config.profileLocked" icon="fa fa-edit text-small" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">{{ $t('profile.language') }}</td>
|
||||
<td colspan="2" class="text-right"><Dropdown small tool outline v-model="language" :options="languages" option-label="display" option-key="id" @select="onSelectLanguage"/></td>
|
||||
</tr>
|
||||
<tr v-show="!user.source">
|
||||
<td colspan="3" class="text-right">
|
||||
<!-- <Button tool @click="onPasswordReset()">{{ $t('profile.passwordResetAction') }}</Button> -->
|
||||
<Button tool @click="onPasswordChange()">{{ $t('profile.changePasswordAction') }}</Button>
|
||||
<Button tool v-show="!user.source && !config.external2FA" @click="user.twoFactorAuthenticationEnabled ? onTwoFADisable() : onOpenTwoFASetupDialog()">{{ $t(user.twoFactorAuthenticationEnabled ? 'profile.disable2FAAction' : 'profile.enable2FAAction') }}</Button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<AppPasswords/>
|
||||
<ApiTokens v-show="user.isAtLeastAdmin"/>
|
||||
|
||||
<Section :title="$t('profile.loginTokens.title')">
|
||||
<p>{{ $t('profile.loginTokens.description', { webadminTokenCount: webadminTokens.length, cliTokenCount: cliTokens.length }) }}</p>
|
||||
<Button danger :loading="revokeTokensBusy" :disabled="revokeTokensBusy" @click="onRevokeAllWebAndCliTokens()">{{ $t('profile.loginTokens.logoutAll') }}</Button>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
||||
@@ -312,5 +225,91 @@ onMounted(async () => {
|
||||
cliTokens.value = tokens.filter(function (c) { return c.clientId === TOKEN_TYPES.ID_CLI; });
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content">
|
||||
<InputDialog ref="inputDialog" />
|
||||
|
||||
<Dialog ref="twoFADialog"
|
||||
:title="$t('profile.enable2FA.title')">
|
||||
<div style="text-align: center; max-width: 420px">
|
||||
<p v-show="mandatory2FAHelp">{{ $t('profile.enable2FA.description') }}</p>
|
||||
<p v-html="$t('profile.enable2FA.authenticatorAppDescription', { googleAuthenticatorPlayStoreLink: 'https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2', googleAuthenticatorITunesLink: 'https://itunes.apple.com/us/app/google-authenticator/id388497605', freeOTPPlayStoreLink: 'https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp', freeOTPITunesLink: 'https://itunes.apple.com/us/app/freeotp-authenticator/id872559395'})"></p>
|
||||
<img :src="twoFAQRCode" style="border-radius: 10px; margin-bottom: 10px"/>
|
||||
<small>{{ twoFASecret }}</small>
|
||||
<br/>
|
||||
<br/>
|
||||
<p class="has-error" v-show="twoFAEnableError">{{ twoFAEnableError }} </p>
|
||||
<form @submit.prevent="onTwoFAEnable()">
|
||||
<input type="submit" style="display: none;" :disabled="!twoFATotpToken"/>
|
||||
<FormGroup>
|
||||
<label for="totpTokenInput">{{ $t('profile.enable2FA.token') }}</label>
|
||||
<TextInput v-model="twoFATotpToken" id="totpTokenInput" />
|
||||
</FormGroup>
|
||||
<Button @click="onTwoFAEnable()" :disabled="!twoFATotpToken">{{ $t('profile.enable2FA.enable') }}</Button>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<Section :title="$t('profile.title')">
|
||||
<template #header-buttons>
|
||||
<Button @click="profileModel.logout()" icon="fa fa-sign-out">{{ $t('main.logout') }}</Button>
|
||||
</template>
|
||||
|
||||
<div style="display: flex;">
|
||||
<div style="width: 150px;">
|
||||
<input type="file" ref="avatarFileInput" style="display: none" accept="image/*" @change="onAvatarChanged()"/>
|
||||
<div class="settings-avatar" :style="`background-image: url('${user.avatarUrl}');`" @click="avatarFileInput.click()">
|
||||
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex-grow: 1;">
|
||||
<table style="width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-muted">{{ $t('main.username') }}</td>
|
||||
<td style="width: 100px; height: 34px;">{{ user.username }}</td>
|
||||
<td style="width: 32px"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">{{ $t('main.displayName') }}</td>
|
||||
<td style="white-space: nowrap;">{{ user.displayName }}</td>
|
||||
<td><Button small tool outline @click="onChangeDisplayName(user.displayName)" v-show="!user.source && !config.profileLocked" icon="fa fa-edit text-small" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">{{ $t('profile.primaryEmail') }}</td>
|
||||
<td style="white-space: nowrap;">{{ user.email }}</td>
|
||||
<td><Button small tool outline @click="onChangeEmail(user.email)" v-show="!user.source && !config.profileLocked" icon="fa fa-edit text-small" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">{{ $t('profile.passwordRecoveryEmail') }}</td>
|
||||
<td style="white-space: nowrap;">{{ user.fallbackEmail }}</td>
|
||||
<td><Button small tool outline @click="onChangeFallbackEmail(user.fallbackEmail)" v-show="!user.source && !config.profileLocked" icon="fa fa-edit text-small" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">{{ $t('profile.language') }}</td>
|
||||
<td colspan="2" class="text-right"><Dropdown small tool outline v-model="language" :options="languages" option-label="display" option-key="id" @select="onSelectLanguage"/></td>
|
||||
</tr>
|
||||
<tr v-show="!user.source">
|
||||
<td colspan="3" class="text-right">
|
||||
<!-- <Button tool @click="onPasswordReset()">{{ $t('profile.passwordResetAction') }}</Button> -->
|
||||
<Button tool @click="onPasswordChange()">{{ $t('profile.changePasswordAction') }}</Button>
|
||||
<Button tool v-show="!user.source && !config.external2FA" @click="user.twoFactorAuthenticationEnabled ? onTwoFADisable() : onOpenTwoFASetupDialog()">{{ $t(user.twoFactorAuthenticationEnabled ? 'profile.disable2FAAction' : 'profile.enable2FAAction') }}</Button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<AppPasswords/>
|
||||
<ApiTokens v-show="user.isAtLeastAdmin"/>
|
||||
|
||||
<Section :title="$t('profile.loginTokens.title')">
|
||||
<p>{{ $t('profile.loginTokens.description', { webadminTokenCount: webadminTokens.length, cliTokenCount: cliTokens.length }) }}</p>
|
||||
<Button danger :loading="revokeTokensBusy" :disabled="revokeTokensBusy" @click="onRevokeAllWebAndCliTokens()">{{ $t('profile.loginTokens.logoutAll') }}</Button>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Section',
|
||||
props: {
|
||||
title: String,
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="section-header">
|
||||
@@ -11,16 +22,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Section',
|
||||
props: {
|
||||
title: String,
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
<template>
|
||||
<div class="content">
|
||||
<h1 class="section-header">{{ $t('support.title') }}</h1>
|
||||
|
||||
<Section :title="$t('support.help.title')">
|
||||
<div v-html="description"></div>
|
||||
</Section>
|
||||
|
||||
<Section :title="$t('support.remoteSupport.title')">
|
||||
<h2 class="text-center" v-show="!ready"><i class="fa fa-circle-notch fa-spin"></i></h2>
|
||||
<div v-show="ready">
|
||||
<p>{{ $t('support.remoteSupport.description') }}</p>
|
||||
<b>{{ $t('support.remoteSupport.warning') }}</b>
|
||||
<br/>
|
||||
<br/>
|
||||
<b class="pull-left text-danger text-bold" v-show="toggleSshSupportError">{{ toggleSshSupportError }}</b>
|
||||
<Button :danger="sshSupportEnabled ? true : null" @click="toggleSshSupport()">{{ sshSupportEnabled ? $t('support.remoteSupport.disableAction') : $t('support.remoteSupport.enableAction') }}</Button>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { fetcher, Button } from 'pankow';
|
||||
@@ -71,3 +49,25 @@ export default {
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content">
|
||||
<h1 class="section-header">{{ $t('support.title') }}</h1>
|
||||
|
||||
<Section :title="$t('support.help.title')">
|
||||
<div v-html="description"></div>
|
||||
</Section>
|
||||
|
||||
<Section :title="$t('support.remoteSupport.title')">
|
||||
<h2 class="text-center" v-show="!ready"><i class="fa fa-circle-notch fa-spin"></i></h2>
|
||||
<div v-show="ready">
|
||||
<p>{{ $t('support.remoteSupport.description') }}</p>
|
||||
<b>{{ $t('support.remoteSupport.warning') }}</b>
|
||||
<br/>
|
||||
<br/>
|
||||
<b class="pull-left text-danger text-bold" v-show="toggleSshSupportError">{{ toggleSshSupportError }}</b>
|
||||
<Button :danger="sshSupportEnabled ? true : null" @click="toggleSshSupport()">{{ sshSupportEnabled ? $t('support.remoteSupport.disableAction') : $t('support.remoteSupport.enableAction') }}</Button>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,51 +1,3 @@
|
||||
<template>
|
||||
<MainLayout :gap="false">
|
||||
<template #dialogs>
|
||||
<Dialog ref="fatalErrorDialog" modal title="Error">
|
||||
<p>{{ fatalError }}</p>
|
||||
</Dialog>
|
||||
|
||||
<InputDialog ref="inputDialog" />
|
||||
<a id="fileDownloadLink" :href="downloadFileDownloadUrl" target="_blank"></a>
|
||||
</template>
|
||||
<template #header>
|
||||
<TopBar class="navbar">
|
||||
<template #left>
|
||||
<span class="title">{{ name }}</span>
|
||||
</template>
|
||||
<template #right>
|
||||
<!-- Scheduler/cron tasks -->
|
||||
<Button success :menu="schedulerMenuModel" v-show="usesAddon('scheduler')" @click="onSchedulerMenu">{{ $t('terminal.scheduler') }}</Button>
|
||||
|
||||
<!-- addon actions -->
|
||||
<Button success @click="terminalInject('mysql')" v-show="usesAddon('mysql')" :disabled="!connected">MySQL</Button>
|
||||
<Button success @click="terminalInject('postgresql')" v-show="usesAddon('postgresql')" :disabled="!connected">Postgres</Button>
|
||||
<Button success @click="terminalInject('mongodb')" v-show="usesAddon('mongodb')" :disabled="!connected">MongoDB</Button>
|
||||
<Button success @click="terminalInject('redis')" v-show="usesAddon('redis')" :disabled="!connected">Redis</Button>
|
||||
|
||||
<!-- upload/download actions -->
|
||||
<Button style="margin-left: 20px;" :disabled="!connected" @click="onUpload" icon="fa-solid fa-upload">{{ $t('terminal.uploadTo', { path: '/app/data/' }) }}</Button>
|
||||
<Button :disabled="!connected" @click="onDownload" icon="fa-solid fa-download">{{ $t('terminal.downloadAction') }}</Button>
|
||||
|
||||
<Button style="margin-left: 20px;" :title="$t('filemanager.toolbar.restartApp')" secondary tool :loading="busyRestart" icon="fa-solid fa-arrows-rotate" @click="onRestartApp"/>
|
||||
<Button v-show="showFilemanager" :href="'/filemanager.html#/home/app/' + id" target="_blank" secondary tool icon="fa-solid fa-folder" :title="$t('filemanager.title')" />
|
||||
<Button :href="'/logs.html?appId=' + id" target="_blank" secondary tool icon="fa-solid fa-align-left" :title="$t('logs.title')" />
|
||||
</template>
|
||||
</TopBar>
|
||||
</template>
|
||||
<template #body>
|
||||
<!-- terminal will be injected here -->
|
||||
</template>
|
||||
<template #footer>
|
||||
<FileUploader
|
||||
ref="fileUploader"
|
||||
:upload-handler="uploadHandler"
|
||||
:tr="$t"
|
||||
/>
|
||||
</template>
|
||||
</MainLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { fetcher, Button, Dialog, FileUploader, InputDialog, MainLayout, TopBar } from 'pankow';
|
||||
@@ -319,6 +271,54 @@ export default {
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MainLayout :gap="false">
|
||||
<template #dialogs>
|
||||
<Dialog ref="fatalErrorDialog" modal title="Error">
|
||||
<p>{{ fatalError }}</p>
|
||||
</Dialog>
|
||||
|
||||
<InputDialog ref="inputDialog" />
|
||||
<a id="fileDownloadLink" :href="downloadFileDownloadUrl" target="_blank"></a>
|
||||
</template>
|
||||
<template #header>
|
||||
<TopBar class="navbar">
|
||||
<template #left>
|
||||
<span class="title">{{ name }}</span>
|
||||
</template>
|
||||
<template #right>
|
||||
<!-- Scheduler/cron tasks -->
|
||||
<Button success :menu="schedulerMenuModel" v-show="usesAddon('scheduler')" @click="onSchedulerMenu">{{ $t('terminal.scheduler') }}</Button>
|
||||
|
||||
<!-- addon actions -->
|
||||
<Button success @click="terminalInject('mysql')" v-show="usesAddon('mysql')" :disabled="!connected">MySQL</Button>
|
||||
<Button success @click="terminalInject('postgresql')" v-show="usesAddon('postgresql')" :disabled="!connected">Postgres</Button>
|
||||
<Button success @click="terminalInject('mongodb')" v-show="usesAddon('mongodb')" :disabled="!connected">MongoDB</Button>
|
||||
<Button success @click="terminalInject('redis')" v-show="usesAddon('redis')" :disabled="!connected">Redis</Button>
|
||||
|
||||
<!-- upload/download actions -->
|
||||
<Button style="margin-left: 20px;" :disabled="!connected" @click="onUpload" icon="fa-solid fa-upload">{{ $t('terminal.uploadTo', { path: '/app/data/' }) }}</Button>
|
||||
<Button :disabled="!connected" @click="onDownload" icon="fa-solid fa-download">{{ $t('terminal.downloadAction') }}</Button>
|
||||
|
||||
<Button style="margin-left: 20px;" :title="$t('filemanager.toolbar.restartApp')" secondary tool :loading="busyRestart" icon="fa-solid fa-arrows-rotate" @click="onRestartApp"/>
|
||||
<Button v-show="showFilemanager" :href="'/filemanager.html#/home/app/' + id" target="_blank" secondary tool icon="fa-solid fa-folder" :title="$t('filemanager.title')" />
|
||||
<Button :href="'/logs.html?appId=' + id" target="_blank" secondary tool icon="fa-solid fa-align-left" :title="$t('logs.title')" />
|
||||
</template>
|
||||
</TopBar>
|
||||
</template>
|
||||
<template #body>
|
||||
<!-- terminal will be injected here -->
|
||||
</template>
|
||||
<template #footer>
|
||||
<FileUploader
|
||||
ref="fileUploader"
|
||||
:upload-handler="uploadHandler"
|
||||
:tr="$t"
|
||||
/>
|
||||
</template>
|
||||
</MainLayout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
||||
body {
|
||||
|
||||
@@ -1,115 +1,3 @@
|
||||
<template>
|
||||
<div class="content">
|
||||
<InputDialog ref="inputDialog" />
|
||||
|
||||
<Dialog ref="volumeDialog"
|
||||
:title="volumeDialogData.mode === 'edit' ? $t('volumes.editVolumeDialog.title', { name: volumeDialogData.name }) : $t('volumes.addVolumeDialog.title')"
|
||||
:reject-label="$t('main.dialog.cancel')"
|
||||
:confirm-label="$t('main.dialog.save')"
|
||||
confirm-style="success"
|
||||
:confirm-active="volumeDialogValid"
|
||||
:confirm-busy="volumeDialogData.busy"
|
||||
@confirm="submitVolumeDialog()"
|
||||
>
|
||||
<form @submit="submitVolumeDialog()" autocomplete="off">
|
||||
<fieldset :disabled="volumeDialogData.busy">
|
||||
<input style="display: none;" type="submit" :disabled="!volumeDialogValid" />
|
||||
|
||||
<p class="has-error" v-show="volumeDialogData.error">{{ volumeDialogData.error }}</p>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mode === 'new'">
|
||||
<label for="volumeName">{{ $t('volumes.name') }}</label>
|
||||
<TextInput id="volumeName" v-model="volumeDialogData.name" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label for="volumeMountType">{{ $t('volumes.mountType') }}</label>
|
||||
<Dropdown id="volumeMountType" v-model="volumeDialogData.mountType" :options="mountTypeOptions" option-label="name" option-key="value" :disabled="volumeDialogData.mode === 'edit'"/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'filesystem' || volumeDialogData.mountType === 'mountpoint'">
|
||||
<label for="volumeHostPath">{{ $t('volumes.localDirectory') }}</label>
|
||||
<TextInput id="volumeHostPath" v-model="volumeDialogData.hostPath" :placeholder="volumeDialogData.mountType === 'filesystem' ? '/srv/shared' : '/mnt/data'" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'ext4' || volumeDialogData.mountType === 'xfs'">
|
||||
<label for="volumeDiskPath">{{ $t('volumes.addVolumeDialog.diskPath') }}</label>
|
||||
<Dropdown id="volumeMountType" v-if="volumeDialogData.mountType === 'ext4'" v-model="volumeDialogData.diskPath" :options="volumeDialogData.ext4BlockDevices" option-label="label" option-key="path" :disabled="volumeDialogData.mode === 'edit'"/>
|
||||
<Dropdown id="volumeMountType" v-if="volumeDialogData.mountType === 'xfs'" v-model="volumeDialogData.diskPath" :options="volumeDialogData.xfsBlockDevices" option-label="label" option-key="path" :disabled="volumeDialogData.mode === 'edit'"/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'cifs' || volumeDialogData.mountType === 'nfs' || volumeDialogData.mountType === 'sshfs'">
|
||||
<label for="volumeHost">{{ $t('volumes.addVolumeDialog.server') }}</label>
|
||||
<TextInput v-model="volumeDialogData.host" id="volumeHost"/>
|
||||
</FormGroup>
|
||||
|
||||
<Checkbox v-if="volumeDialogData.mountType === 'cifs'" v-model="volumeDialogData.seal" :label="$t('backups.configureBackupStorage.cifsSealSupport')" />
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'sshfs'">
|
||||
<label for="volumePort">{{ $t('volumes.addVolumeDialog.port') }}</label>
|
||||
<NumberInput v-model="volumeDialogData.port" id="volumePort" min="0"/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'cifs' || volumeDialogData.mountType === 'nfs' || volumeDialogData.mountType === 'sshfs'">
|
||||
<label for="volumeRemoteDir">{{ $t('volumes.addVolumeDialog.remoteDirectory') }}</label>
|
||||
<TextInput v-model="volumeDialogData.remoteDir" id="volumeRemoteDir" placeholder="/share" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'cifs'">
|
||||
<label for="volumeUsername">{{ $t('volumes.addVolumeDialog.username') }}</label>
|
||||
<TextInput v-model="volumeDialogData.username" id="volumeUsername" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'cifs'">
|
||||
<label for="volumePassword">{{ $t('volumes.addVolumeDialog.password') }}</label>
|
||||
<PasswordInput v-model="volumeDialogData.password" id="volumePassword" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'sshfs'">
|
||||
<label for="volumeUser">{{ $t('volumes.addVolumeDialog.user') }}</label>
|
||||
<TextInput v-model="volumeDialogData.user" id="volumeAddUser" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'sshfs'">
|
||||
<label for="volumePrivateKey">{{ $t('volumes.addVolumeDialog.privateKey') }}</label>
|
||||
<textarea v-model="volumeDialogData.privateKey" id="volumePrivateKey"></textarea>
|
||||
</FormGroup>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
</Dialog>
|
||||
|
||||
<Section :title="$t('volumes.title')">
|
||||
<template #header-buttons>
|
||||
<Button @click="openVolumeDialog()" icon="fa fa-plus">{{ $t('volumes.addVolumeAction') }}</Button>
|
||||
</template>
|
||||
|
||||
<div v-html="$t('volumes.description')"></div>
|
||||
<br/>
|
||||
<TableView :columns="columns" :model="volumes" :busy="busy">
|
||||
<template #target="slotProps">
|
||||
{{ (slotProps.mountType === 'mountpoint' || slotProps.mountType === 'filesystem') ? slotProps.hostPath : (slotProps.mountOptions.host || slotProps.mountOptions.diskPath || slotProps.hostPath) + (slotProps.mountOptions.remoteDir || '') }}
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<div style="text-align: center;" v-tooltip="slotProps.message">
|
||||
<i class="fa fa-circle" :style="{ color: slotProps.state === 'active' ? '#27CE65' : '#d9534f' }"></i>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions="slotProps">
|
||||
<div class="table-actions">
|
||||
<ButtonGroup>
|
||||
<Button tool secondary outline small icon="fa fa-sync-alt" v-if="slotProps.mountType === 'sshfs' || slotProps.mountType === 'cifs' || slotProps.mountType === 'nfs' || slotProps.mountType === 'ext4' || slotProps.mountType === 'xfs'" v-tooltip="$t('volumes.remountActionTooltip')" @click="remount(slotProps)"></Button>
|
||||
<Button tool secondary outline small icon="fa fa-pencil-alt" v-if="slotProps.mountType === 'sshfs' || slotProps.mountType === 'cifs' || slotProps.mountType === 'nfs'" v-tooltip="$t('volumes.editActionTooltip')" @click="openVolumeDialog(slotProps)"></Button>
|
||||
<Button tool secondary outline small icon="fas fa-folder" v-tooltip="$t('volumes.openFileManagerActionTooltip')" :href="'/filemanager.html#/home/volume/' + slotProps.id" target="_blank"></Button>
|
||||
</ButtonGroup>
|
||||
<Button tool danger outline small icon="far fa-trash-alt" v-tooltip="$t('volumes.removeVolumeActionTooltip')" @click="onRemove(slotProps)"></Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { Button, ButtonGroup, Checkbox, Dialog, Dropdown, FormGroup, InputDialog, NumberInput, PasswordInput, TableView, TextInput } from 'pankow';
|
||||
@@ -312,3 +200,115 @@ export default {
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content">
|
||||
<InputDialog ref="inputDialog" />
|
||||
|
||||
<Dialog ref="volumeDialog"
|
||||
:title="volumeDialogData.mode === 'edit' ? $t('volumes.editVolumeDialog.title', { name: volumeDialogData.name }) : $t('volumes.addVolumeDialog.title')"
|
||||
:reject-label="$t('main.dialog.cancel')"
|
||||
:confirm-label="$t('main.dialog.save')"
|
||||
confirm-style="success"
|
||||
:confirm-active="volumeDialogValid"
|
||||
:confirm-busy="volumeDialogData.busy"
|
||||
@confirm="submitVolumeDialog()"
|
||||
>
|
||||
<form @submit="submitVolumeDialog()" autocomplete="off">
|
||||
<fieldset :disabled="volumeDialogData.busy">
|
||||
<input style="display: none;" type="submit" :disabled="!volumeDialogValid" />
|
||||
|
||||
<p class="has-error" v-show="volumeDialogData.error">{{ volumeDialogData.error }}</p>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mode === 'new'">
|
||||
<label for="volumeName">{{ $t('volumes.name') }}</label>
|
||||
<TextInput id="volumeName" v-model="volumeDialogData.name" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label for="volumeMountType">{{ $t('volumes.mountType') }}</label>
|
||||
<Dropdown id="volumeMountType" v-model="volumeDialogData.mountType" :options="mountTypeOptions" option-label="name" option-key="value" :disabled="volumeDialogData.mode === 'edit'"/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'filesystem' || volumeDialogData.mountType === 'mountpoint'">
|
||||
<label for="volumeHostPath">{{ $t('volumes.localDirectory') }}</label>
|
||||
<TextInput id="volumeHostPath" v-model="volumeDialogData.hostPath" :placeholder="volumeDialogData.mountType === 'filesystem' ? '/srv/shared' : '/mnt/data'" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'ext4' || volumeDialogData.mountType === 'xfs'">
|
||||
<label for="volumeDiskPath">{{ $t('volumes.addVolumeDialog.diskPath') }}</label>
|
||||
<Dropdown id="volumeMountType" v-if="volumeDialogData.mountType === 'ext4'" v-model="volumeDialogData.diskPath" :options="volumeDialogData.ext4BlockDevices" option-label="label" option-key="path" :disabled="volumeDialogData.mode === 'edit'"/>
|
||||
<Dropdown id="volumeMountType" v-if="volumeDialogData.mountType === 'xfs'" v-model="volumeDialogData.diskPath" :options="volumeDialogData.xfsBlockDevices" option-label="label" option-key="path" :disabled="volumeDialogData.mode === 'edit'"/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'cifs' || volumeDialogData.mountType === 'nfs' || volumeDialogData.mountType === 'sshfs'">
|
||||
<label for="volumeHost">{{ $t('volumes.addVolumeDialog.server') }}</label>
|
||||
<TextInput v-model="volumeDialogData.host" id="volumeHost"/>
|
||||
</FormGroup>
|
||||
|
||||
<Checkbox v-if="volumeDialogData.mountType === 'cifs'" v-model="volumeDialogData.seal" :label="$t('backups.configureBackupStorage.cifsSealSupport')" />
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'sshfs'">
|
||||
<label for="volumePort">{{ $t('volumes.addVolumeDialog.port') }}</label>
|
||||
<NumberInput v-model="volumeDialogData.port" id="volumePort" min="0"/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'cifs' || volumeDialogData.mountType === 'nfs' || volumeDialogData.mountType === 'sshfs'">
|
||||
<label for="volumeRemoteDir">{{ $t('volumes.addVolumeDialog.remoteDirectory') }}</label>
|
||||
<TextInput v-model="volumeDialogData.remoteDir" id="volumeRemoteDir" placeholder="/share" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'cifs'">
|
||||
<label for="volumeUsername">{{ $t('volumes.addVolumeDialog.username') }}</label>
|
||||
<TextInput v-model="volumeDialogData.username" id="volumeUsername" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'cifs'">
|
||||
<label for="volumePassword">{{ $t('volumes.addVolumeDialog.password') }}</label>
|
||||
<PasswordInput v-model="volumeDialogData.password" id="volumePassword" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'sshfs'">
|
||||
<label for="volumeUser">{{ $t('volumes.addVolumeDialog.user') }}</label>
|
||||
<TextInput v-model="volumeDialogData.user" id="volumeAddUser" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="volumeDialogData.mountType === 'sshfs'">
|
||||
<label for="volumePrivateKey">{{ $t('volumes.addVolumeDialog.privateKey') }}</label>
|
||||
<textarea v-model="volumeDialogData.privateKey" id="volumePrivateKey"></textarea>
|
||||
</FormGroup>
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
</Dialog>
|
||||
|
||||
<Section :title="$t('volumes.title')">
|
||||
<template #header-buttons>
|
||||
<Button @click="openVolumeDialog()" icon="fa fa-plus">{{ $t('volumes.addVolumeAction') }}</Button>
|
||||
</template>
|
||||
|
||||
<div v-html="$t('volumes.description')"></div>
|
||||
<br/>
|
||||
<TableView :columns="columns" :model="volumes" :busy="busy">
|
||||
<template #target="slotProps">
|
||||
{{ (slotProps.mountType === 'mountpoint' || slotProps.mountType === 'filesystem') ? slotProps.hostPath : (slotProps.mountOptions.host || slotProps.mountOptions.diskPath || slotProps.hostPath) + (slotProps.mountOptions.remoteDir || '') }}
|
||||
</template>
|
||||
<template #status="slotProps">
|
||||
<div style="text-align: center;" v-tooltip="slotProps.message">
|
||||
<i class="fa fa-circle" :style="{ color: slotProps.state === 'active' ? '#27CE65' : '#d9534f' }"></i>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions="slotProps">
|
||||
<div class="table-actions">
|
||||
<ButtonGroup>
|
||||
<Button tool secondary outline small icon="fa fa-sync-alt" v-if="slotProps.mountType === 'sshfs' || slotProps.mountType === 'cifs' || slotProps.mountType === 'nfs' || slotProps.mountType === 'ext4' || slotProps.mountType === 'xfs'" v-tooltip="$t('volumes.remountActionTooltip')" @click="remount(slotProps)"></Button>
|
||||
<Button tool secondary outline small icon="fa fa-pencil-alt" v-if="slotProps.mountType === 'sshfs' || slotProps.mountType === 'cifs' || slotProps.mountType === 'nfs'" v-tooltip="$t('volumes.editActionTooltip')" @click="openVolumeDialog(slotProps)"></Button>
|
||||
<Button tool secondary outline small icon="fas fa-folder" v-tooltip="$t('volumes.openFileManagerActionTooltip')" :href="'/filemanager.html#/home/volume/' + slotProps.id" target="_blank"></Button>
|
||||
</ButtonGroup>
|
||||
<Button tool danger outline small icon="far fa-trash-alt" v-tooltip="$t('volumes.removeVolumeActionTooltip')" @click="onRemove(slotProps)"></Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
</Section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user