Add list view to apps listing
This commit is contained in:
@@ -2,29 +2,72 @@
|
||||
<div class="content">
|
||||
<h1 class="section-header">
|
||||
{{ $t('apps.title') }}
|
||||
<TextInput v-model="filter" placeholder="Filter ..." />
|
||||
<div>
|
||||
<TextInput v-model="filter" placeholder="Filter ..." />
|
||||
<Button tool @click="toggleView()" :icon="viewType === VIEW_TYPE.GRID ? 'fas fa-list' : 'fas fa-grip'"></Button>
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<TransitionGroup name="grid-animation" tag="div" class="grid">
|
||||
<a v-for="app in filteredApps" :key="app.id" class="item" :href="'https://' + app.fqdn" target="_blank" v-tooltip="app.fqdn">
|
||||
<img :src="API_ORIGIN + app.iconUrl"/>
|
||||
<div class="label">{{ app.label || app.subdomain || app.fqdn }}</div>
|
||||
<a class="config" :href="`#/app/${app.id}/info`"><Icon icon="fa-solid fa-cog" /></a>
|
||||
</a>
|
||||
</TransitionGroup>
|
||||
<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">
|
||||
<img :src="API_ORIGIN + app.iconUrl"/>
|
||||
<div class="label">{{ app.label || app.subdomain || app.fqdn }}</div>
|
||||
<a class="config" v-show="isOperator(app)" :href="`#/app/${app.id}/info`"><Icon icon="fa-solid fa-cog" /></a>
|
||||
</a>
|
||||
</TransitionGroup>
|
||||
|
||||
<!-- TODO check user permissions -->
|
||||
<div class="empty-placeholder" v-if="filteredApps.length === 0">
|
||||
<!-- for admins -->
|
||||
<div v-if="false">
|
||||
<h4><i class="fa fa-cloud-download fa-fw"></i> {{ $t('apps.noApps.title') }}</h4>
|
||||
<h5 v-html="$t('apps.noApps.description', { appStoreLink: '#/appstore' })"></h5>
|
||||
<div class="list" v-if="viewType === VIEW_TYPE.LIST">
|
||||
<TableView :columns="listColumns" :model="filteredApps">
|
||||
<template #icon="slotProps">
|
||||
<img class="list-icon" :src="API_ORIGIN + slotProps.iconUrl"/>
|
||||
</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">
|
||||
{{ slotProps.fqdn }}
|
||||
</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 :href="`#/app/${slotProps.id}/info`" icon="fa-solid fa-cog"></Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
</div>
|
||||
|
||||
<!-- for non-admins -->
|
||||
<div v-if="true">
|
||||
<h4>{{ $t('apps.noAccess.title') }}</h4>
|
||||
<h5>{{ $t('apps.noAccess.description') }}</h5>
|
||||
<!-- TODO check user permissions -->
|
||||
<div class="empty-placeholder" v-if="filteredApps.length === 0">
|
||||
<!-- for admins -->
|
||||
<div v-if="false">
|
||||
<h4><i class="fa fa-cloud-download fa-fw"></i> {{ $t('apps.noApps.title') }}</h4>
|
||||
<h5 v-html="$t('apps.noApps.description', { appStoreLink: '#/appstore' })"></h5>
|
||||
</div>
|
||||
|
||||
<!-- for non-admins -->
|
||||
<div v-if="true">
|
||||
<h4>{{ $t('apps.noAccess.title') }}</h4>
|
||||
<h5>{{ $t('apps.noAccess.description') }}</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,8 +75,9 @@
|
||||
|
||||
<script>
|
||||
|
||||
import { Button, Icon, TextInput } from 'pankow';
|
||||
import { Button, ButtonGroup, Icon, TableView, TextInput } from 'pankow';
|
||||
|
||||
import { APP_TYPES, HSTATES, ISTATES, RSTATES } from '../constants.js';
|
||||
import AppsModel from '../models/AppsModel.js';
|
||||
|
||||
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
||||
@@ -41,19 +85,55 @@ const accessToken = localStorage.token;
|
||||
|
||||
const appsModel = AppsModel.create(API_ORIGIN, accessToken);
|
||||
|
||||
const VIEW_TYPE = {
|
||||
LIST: 'list',
|
||||
GRID: 'grid',
|
||||
};
|
||||
|
||||
let refreshInterval;
|
||||
|
||||
export default {
|
||||
name: 'AppsView',
|
||||
components: {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Icon,
|
||||
TableView,
|
||||
TextInput,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
API_ORIGIN,
|
||||
APP_TYPES,
|
||||
VIEW_TYPE,
|
||||
ready: false,
|
||||
filter: '',
|
||||
apps: [],
|
||||
viewType: (localStorage.appsView && (localStorage.appsView === VIEW_TYPE.GRID || localStorage.appsView === VIEW_TYPE.LIST)) ? localStorage.appsView : VIEW_TYPE.GRID,
|
||||
listColumns: {
|
||||
icon: {
|
||||
width: '32px'
|
||||
},
|
||||
label: {
|
||||
label: 'Label',
|
||||
sort: true
|
||||
},
|
||||
domain: {
|
||||
label: 'Location',
|
||||
sort: true
|
||||
},
|
||||
appTitle: {
|
||||
label: 'App Title',
|
||||
sort: true
|
||||
},
|
||||
sso: {
|
||||
label: 'Login',
|
||||
sort: true
|
||||
},
|
||||
actions: {
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -64,11 +144,53 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpenApp(app, event) {
|
||||
function stopEvent() {
|
||||
event.stopPropagation();
|
||||
// event.preventDefault();
|
||||
}
|
||||
|
||||
console.log(event)
|
||||
|
||||
if (app.installationState !== ISTATES.INSTALLED) {
|
||||
if (app.installationState === ISTATES.ERROR && this.isOperator(app)) window.location.href = `#/app/${app.id}/repair`;
|
||||
return stopEvent();
|
||||
}
|
||||
|
||||
// app.health can also be null to indicate insufficient data
|
||||
if (!app.health) return stopEvent();
|
||||
if (app.runState === RSTATES.STOPPED) return stopEvent();
|
||||
|
||||
if (app.health === HSTATES.UNHEALTHY || app.health === HSTATES.ERROR || app.health === HSTATES.DEAD) {
|
||||
if (this.isOperator(app)) window.location.href = `#/app/${app.id}/repair`;
|
||||
return stopEvent();
|
||||
}
|
||||
|
||||
|
||||
// if (app.pendingPostInstallConfirmation && $scope.appPostInstallConfirm) {
|
||||
// $scope.appPostInstallConfirm.show(app);
|
||||
// return stopEvent();
|
||||
// }
|
||||
},
|
||||
isOperator(app) {
|
||||
return app.accessLevel === 'operator' || app.accessLevel === 'admin';
|
||||
},
|
||||
async refreshApps() {
|
||||
this.apps = await appsModel.list();
|
||||
},
|
||||
toggleView() {
|
||||
this.viewType = this.viewType === VIEW_TYPE.LIST ? VIEW_TYPE.GRID : VIEW_TYPE.LIST;
|
||||
localStorage.appsView = this.viewType;
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.apps = await appsModel.list();
|
||||
console.log(this.apps)
|
||||
this.ready = true;
|
||||
|
||||
refreshInterval = setInterval(this.refreshApps, 5000);
|
||||
},
|
||||
async unmounted() {
|
||||
clearInterval(refreshInterval);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,14 +198,34 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
|
||||
.grid-animation-move,
|
||||
.grid-animation-enter-active,
|
||||
.grid-animation-leave-active {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.grid-animation-enter-from,
|
||||
|
||||
.grid-animation-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
.grid-animation-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(30px);
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
|
||||
.list-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: right;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
tr:hover .actions {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.grid {
|
||||
@@ -96,7 +238,7 @@ export default {
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.item {
|
||||
.grid-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -110,14 +252,14 @@ export default {
|
||||
background-color: var(--card-background);
|
||||
}
|
||||
|
||||
.item:focus,
|
||||
.item:hover {
|
||||
.grid-item:focus,
|
||||
.grid-item:hover {
|
||||
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
|
||||
background-color: var(--pankow-color-background-hover);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.item img {
|
||||
.grid-item img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
@@ -129,8 +271,8 @@ export default {
|
||||
color: var(--pankow-text-color);
|
||||
}
|
||||
|
||||
.item:focus .label,
|
||||
.item:hover .label {
|
||||
.grid-item:focus .label,
|
||||
.grid-item:hover .label {
|
||||
text-decoration: none;
|
||||
color: var(--accent-color);;
|
||||
}
|
||||
@@ -158,8 +300,8 @@ export default {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.item:focus .config,
|
||||
.item:hover .config {
|
||||
.grid-item:focus .config,
|
||||
.grid-item:hover .config {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user