223 lines
7.9 KiB
Vue
223 lines
7.9 KiB
Vue
<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';
|
|
|
|
import ApplinksModel from '../models/ApplinksModel.js';
|
|
import UsersModel from '../models/UsersModel.js';
|
|
import GroupsModel from '../models/GroupsModel.js';
|
|
|
|
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
|
const accessToken = localStorage.token;
|
|
|
|
const applinksModel = ApplinksModel.create(API_ORIGIN, accessToken);
|
|
const usersModel = UsersModel.create(API_ORIGIN, accessToken);
|
|
const groupsModel = GroupsModel.create(API_ORIGIN, accessToken);
|
|
|
|
export default {
|
|
name: 'ApplinkDialog',
|
|
components: {
|
|
Dialog,
|
|
FormGroup,
|
|
InputDialog,
|
|
MultiSelect,
|
|
Radiobutton,
|
|
TagInput,
|
|
TextInput,
|
|
},
|
|
emits: ['success'],
|
|
data() {
|
|
return {
|
|
API_ORIGIN,
|
|
users: [],
|
|
groups: [],
|
|
busy: false,
|
|
error: {},
|
|
mode: '',
|
|
id: '',
|
|
upstreamUri: '',
|
|
label: '',
|
|
tags: [],
|
|
icon: {},
|
|
iconUrl: '',
|
|
accessRestrictionOption: '',
|
|
accessRestriction: {
|
|
users: [],
|
|
groups: [],
|
|
},
|
|
};
|
|
},
|
|
computed: {
|
|
isValid() {
|
|
if (!this.label) return false;
|
|
if (!this.upstreamUri) return false;
|
|
|
|
return true;
|
|
},
|
|
iconSrc() {
|
|
if (this.icon.data === '__original__') { // user clicked reset
|
|
// https://png-pixel.com/ white pixel placeholder
|
|
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=';
|
|
} else if (this.icon.data) { // user uploaded icon
|
|
return this.icon.data;
|
|
} else if (this.iconUrl) { // current icon
|
|
return API_ORIGIN + this.iconUrl;
|
|
} else {
|
|
return API_ORIGIN + 'img/appicon_fallback.png';
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
resetCustomIcon() {
|
|
this.icon.data = '__original__';
|
|
},
|
|
showCustomIconSelector() {
|
|
this.$refs.iconFileInput.click();
|
|
},
|
|
onIconFileInputChanged(event) {
|
|
const fr = new FileReader();
|
|
fr.onload = () => this.icon.data = fr.result;
|
|
fr.readAsDataURL(event.target.files[0]);
|
|
},
|
|
async open(applink) {
|
|
this.mode = applink ? 'edit' : 'new';
|
|
this.id = applink ? applink.id : '';
|
|
this.upstreamUri = applink ? applink.upstreamUri : '';
|
|
this.label = applink ? applink.label : '';
|
|
this.icon = applink ? {} : { data: '__original__' };
|
|
this.iconUrl = applink ? applink.iconUrl : '';
|
|
this.tags = applink ? applink.tags : [];
|
|
this.accessRestrictionOption = applink && applink.accessRestriction ? 'groups' : 'any';
|
|
this.accessRestriction = applink && applink.accessRestriction ? applink.accessRestriction : { users: [], groups: [] };
|
|
|
|
// fetch users and groups
|
|
this.users = await usersModel.list();
|
|
this.groups = await groupsModel.list();
|
|
|
|
this.$refs.applinkDialog.open();
|
|
},
|
|
async onSubmit() {
|
|
this.busy = true;
|
|
|
|
const data = {
|
|
label: this.label,
|
|
upstreamUri: this.upstreamUri,
|
|
tags: this.tags,
|
|
};
|
|
|
|
data.accessRestriction = null;
|
|
if (this.accessRestrictionOption === 'groups') {
|
|
data.accessRestriction = { users: [], groups: [] };
|
|
data.accessRestriction.users = this.accessRestriction.users.map(function (u) { return u.id; });
|
|
data.accessRestriction.groups = this.accessRestriction.groups.map(function (g) { return g.id; });
|
|
}
|
|
|
|
if (this.icon.data === '__original__') { // user reset the icon
|
|
data.icon = '';
|
|
} else if (this.icon.data) { // user loaded custom icon
|
|
data.icon = this.icon.data.replace(/^data:image\/[a-z]+;base64,/, '');
|
|
}
|
|
|
|
try {
|
|
if (this.mode === 'edit') await applinksModel.update(this.id, data);
|
|
else await applinksModel.add(data);
|
|
} catch (e) {
|
|
console.error('Failed to edit or add applink', e);
|
|
this.busy = false;
|
|
return;
|
|
}
|
|
|
|
this.busy = false;
|
|
|
|
this.$emit('success');
|
|
this.$refs.applinkDialog.close();
|
|
},
|
|
async onRemove() {
|
|
const yes = await this.$refs.inputDialog.confirm({
|
|
message: `Really remove applink?`,
|
|
confirmStyle: 'danger',
|
|
confirmLabel: this.$t('main.dialog.yes'),
|
|
rejectLabel: this.$t('main.dialog.cancel')
|
|
});
|
|
|
|
if (!yes) return;
|
|
|
|
console.log('remove', this.id)
|
|
// await volumesModel.remove(volume.id);
|
|
// await this.refresh();
|
|
}
|
|
},
|
|
};
|
|
|
|
</script>
|