Port apps view to composition api
This commit is contained in:
@@ -1,17 +1,20 @@
|
||||
<script>
|
||||
<script setup>
|
||||
|
||||
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? import.meta.env.VITE_API_ORIGIN : window.location.origin;
|
||||
|
||||
import { ref, computed, useTemplateRef, onMounted, onUnmounted } from 'vue';
|
||||
import { Button, ButtonGroup, Dropdown, Icon, TableView, TextInput } from 'pankow';
|
||||
import { APP_TYPES, HSTATES, ISTATES, RSTATES } from '../constants.js';
|
||||
import AppsModel from '../models/AppsModel.js';
|
||||
import ApplinksModel from '../models/ApplinksModel.js';
|
||||
import DomainsModel from '../models/DomainsModel.js';
|
||||
import ProfileModel from '../models/ProfileModel.js';
|
||||
import ApplinkDialog from '../components/ApplinkDialog.vue';
|
||||
|
||||
const appsModel = AppsModel.create();
|
||||
const domainsModel = DomainsModel.create();
|
||||
const applinksModel = ApplinksModel.create();
|
||||
const profileModel = ProfileModel.create();
|
||||
|
||||
const VIEW_TYPE = {
|
||||
LIST: 'list',
|
||||
@@ -20,183 +23,174 @@ const VIEW_TYPE = {
|
||||
|
||||
let refreshInterval;
|
||||
|
||||
export default {
|
||||
name: 'AppsView',
|
||||
components: {
|
||||
ApplinkDialog,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Dropdown,
|
||||
Icon,
|
||||
TableView,
|
||||
TextInput,
|
||||
const ready = ref(false);
|
||||
const filter = ref('');
|
||||
// TODO maybe replace with app.provide and inject
|
||||
const profile = ref({});
|
||||
const apps = ref([]);
|
||||
const viewType = ref((localStorage.appsView && (localStorage.appsView === VIEW_TYPE.GRID || localStorage.appsView === VIEW_TYPE.LIST)) ? localStorage.appsView : VIEW_TYPE.GRID);
|
||||
const tagFilter = ref('');
|
||||
const tagFilterOptions = ref([{
|
||||
id: '',
|
||||
name: 'All Tags',
|
||||
}]);
|
||||
const domainFilter = ref('');
|
||||
const domainFilterOptions = ref([{
|
||||
id: '',
|
||||
domain: 'All Domains',
|
||||
}]);
|
||||
const stateFilter = ref('');
|
||||
const stateFilterOptions = [
|
||||
{ id: '', label: 'All States' },
|
||||
{ id: 'running', label: 'Running' },
|
||||
{ id: 'stopped', label: 'Stopped' },
|
||||
{ id: 'update_available', label: 'Update Available' },
|
||||
{ id: 'not_responding', label: 'Not Responding' },
|
||||
];
|
||||
const listColumns = {
|
||||
icon: {
|
||||
width: '32px'
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
API_ORIGIN,
|
||||
APP_TYPES,
|
||||
VIEW_TYPE,
|
||||
ready: false,
|
||||
filter: '',
|
||||
profile: this.$root.profile,
|
||||
apps: [],
|
||||
viewType: (localStorage.appsView && (localStorage.appsView === VIEW_TYPE.GRID || localStorage.appsView === VIEW_TYPE.LIST)) ? localStorage.appsView : VIEW_TYPE.GRID,
|
||||
tagFilter: '',
|
||||
tagFilterOptions: [{
|
||||
id: '',
|
||||
name: 'All Tags',
|
||||
}],
|
||||
domainFilter: '',
|
||||
domainFilterOptions: [{
|
||||
id: '',
|
||||
domain: 'All Domains',
|
||||
}],
|
||||
stateFilter: '',
|
||||
stateFilterOptions: [
|
||||
{ id: '', label: 'All States' },
|
||||
{ id: 'running', label: 'Running' },
|
||||
{ id: 'stopped', label: 'Stopped' },
|
||||
{ id: 'update_available', label: 'Update Available' },
|
||||
{ id: 'not_responding', label: 'Not Responding' },
|
||||
],
|
||||
listColumns: {
|
||||
icon: {
|
||||
width: '32px'
|
||||
},
|
||||
label: {
|
||||
label: 'Label',
|
||||
sort: true
|
||||
},
|
||||
domain: {
|
||||
label: 'Location',
|
||||
sort: true
|
||||
},
|
||||
status: {},
|
||||
appTitle: {
|
||||
label: 'App Title',
|
||||
sort: true
|
||||
},
|
||||
sso: {
|
||||
label: 'Login',
|
||||
sort: true
|
||||
},
|
||||
actions: {}
|
||||
},
|
||||
};
|
||||
label: {
|
||||
label: 'Label',
|
||||
sort: true
|
||||
},
|
||||
computed: {
|
||||
filteredApps() {
|
||||
return this.apps.filter(a => {
|
||||
return a.fqdn.indexOf(this.filter) !== -1;
|
||||
}).filter(a => {
|
||||
if (!this.domainFilter) return true;
|
||||
return a.domain === this.domainFilter;
|
||||
}).filter(a => {
|
||||
if (!this.tagFilter) return true;
|
||||
return a.tags.indexOf(this.tagFilter) !== -1;
|
||||
}).filter(a => {
|
||||
if (!this.stateFilter) return true;
|
||||
|
||||
if (this.stateFilter === 'running') return a.runState === RSTATES.RUNNING && a.health === HSTATES.HEALTHY && a.installationState === ISTATES.INSTALLED;
|
||||
if (this.stateFilter === 'stopped') return a.runState === RSTATES.STOPPED;
|
||||
|
||||
// TODO implement this
|
||||
// if (this.stateFilter === 'update_available') return !!(Client.getConfig().update[a.id] && Client.getConfig().update[a.id].manifest.version && Client.getConfig().update[a.id].manifest.version !== a.manifest.version);
|
||||
if (this.stateFilter === 'update_available') return false;
|
||||
|
||||
return a.runState === RSTATES.RUNNING && (a.health !== HSTATES.HEALTHY || a.installationState !== ISTATES.INSTALLED); // not responding
|
||||
});
|
||||
},
|
||||
domain: {
|
||||
label: 'Location',
|
||||
sort: true
|
||||
},
|
||||
methods: {
|
||||
installationStateLabel: AppsModel.installationStateLabel,
|
||||
installationActive: AppsModel.installationActive,
|
||||
appProgressMessage: AppsModel.appProgressMessage,
|
||||
openAppEdit(app) {
|
||||
if (app.type === APP_TYPES.LINK) this.$refs.applinkDialog.open(app);
|
||||
else window.location.href = `#/app/${app.id}/info`;
|
||||
},
|
||||
onOpenApp(app, event) {
|
||||
function stopEvent() {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// TODO
|
||||
// if (app.pendingPostInstallConfirmation && $scope.appPostInstallConfirm) {
|
||||
// $scope.appPostInstallConfirm.show(app);
|
||||
// return stopEvent();
|
||||
// }
|
||||
},
|
||||
isOperator(app) {
|
||||
return app.accessLevel === 'operator' || app.accessLevel === 'admin';
|
||||
},
|
||||
async refreshApps() {
|
||||
const [error, apps] = await appsModel.list();
|
||||
if (error) return console.error(error);
|
||||
|
||||
const applinks = await applinksModel.list();
|
||||
|
||||
// amend properties to mimick full app
|
||||
for (const applink of applinks) {
|
||||
applink.type = APP_TYPES.LINK;
|
||||
applink.fqdn = applink.upstreamUri;
|
||||
applink.manifest = { addons: {}};
|
||||
applink.installationState = ISTATES.INSTALLED;
|
||||
applink.runState = RSTATES.RUNNING;
|
||||
applink.health = HSTATES.HEALTHY;
|
||||
applink.iconUrl = `/api/v1/applinks/${applink.id}/icon?access_token=${localStorage.token}&ts=${applink.ts}`;
|
||||
applink.accessLevel = this.$root.profile.isAtLeastAdmin ? 'admin' : 'user';
|
||||
|
||||
apps.push(applink);
|
||||
}
|
||||
|
||||
this.apps = apps;
|
||||
|
||||
// gets all tags used by all apps, flattens the arrays and new Set() will dedupe
|
||||
const tags = [...new Set(this.apps.map(a => a.tags).flat())].map(t => { return { id: t, name: t }; });
|
||||
this.tagFilterOptions = [{ id: '', name: 'All Tags', }].concat(tags);
|
||||
},
|
||||
toggleView() {
|
||||
this.viewType = this.viewType === VIEW_TYPE.LIST ? VIEW_TYPE.GRID : VIEW_TYPE.LIST;
|
||||
localStorage.appsView = this.viewType;
|
||||
},
|
||||
status: {},
|
||||
appTitle: {
|
||||
label: 'App Title',
|
||||
sort: true
|
||||
},
|
||||
async mounted() {
|
||||
await this.refreshApps();
|
||||
|
||||
const [error, domains] = await domainsModel.list();
|
||||
if (error) return console.error(error);
|
||||
|
||||
this.domainFilterOptions = this.domainFilterOptions.concat(domains.map(d => { d.id = d.domain; return d; }));
|
||||
this.domainFilter = this.domainFilterOptions[0].id;
|
||||
|
||||
this.stateFilter = this.stateFilterOptions[0].id;
|
||||
this.tagFilter = this.tagFilterOptions[0].id;
|
||||
|
||||
this.ready = true;
|
||||
|
||||
refreshInterval = setInterval(this.refreshApps, 5000);
|
||||
sso: {
|
||||
label: 'Login',
|
||||
sort: true
|
||||
},
|
||||
async unmounted() {
|
||||
clearInterval(refreshInterval);
|
||||
}
|
||||
actions: {}
|
||||
};
|
||||
|
||||
const filteredApps = computed(() => {
|
||||
return apps.value.filter(a => {
|
||||
return a.fqdn.indexOf(filter.value) !== -1;
|
||||
}).filter(a => {
|
||||
if (!domainFilter.value) return true;
|
||||
return a.domain === domainFilter.value;
|
||||
}).filter(a => {
|
||||
if (!tagFilter.value) return true;
|
||||
return a.tags.indexOf(tagFilter.value) !== -1;
|
||||
}).filter(a => {
|
||||
if (!stateFilter.value) return true;
|
||||
|
||||
if (stateFilter.value === 'running') return a.runState === RSTATES.RUNNING && a.health === HSTATES.HEALTHY && a.installationState === ISTATES.INSTALLED;
|
||||
if (stateFilter.value === 'stopped') return a.runState === RSTATES.STOPPED;
|
||||
|
||||
// TODO implement this
|
||||
// if (stateFilter.value === 'update_available') return !!(Client.getConfig().update[a.id] && Client.getConfig().update[a.id].manifest.version && Client.getConfig().update[a.id].manifest.version !== a.manifest.version);
|
||||
if (stateFilter.value === 'update_available') return false;
|
||||
|
||||
return a.runState === RSTATES.RUNNING && (a.health !== HSTATES.HEALTHY || a.installationState !== ISTATES.INSTALLED); // not responding
|
||||
});
|
||||
});
|
||||
|
||||
const installationStateLabel = AppsModel.installationStateLabel;
|
||||
const installationActive = AppsModel.installationActive;
|
||||
const appProgressMessage = AppsModel.appProgressMessage;
|
||||
|
||||
const applinkDialog = useTemplateRef('applinkDialog');
|
||||
|
||||
function openAppEdit(app) {
|
||||
if (app.type === APP_TYPES.LINK) applinkDialog.value.open(app);
|
||||
else window.location.href = `#/app/${app.id}/info`;
|
||||
}
|
||||
|
||||
function onOpenApp(app, event) {
|
||||
function stopEvent() {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (app.installationState !== ISTATES.INSTALLED) {
|
||||
if (app.installationState === ISTATES.ERROR && 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 (isOperator(app)) window.location.href = `#/app/${app.id}/repair`;
|
||||
return stopEvent();
|
||||
}
|
||||
|
||||
// TODO
|
||||
// if (app.pendingPostInstallConfirmation && $scope.appPostInstallConfirm) {
|
||||
// $scope.appPostInstallConfirm.show(app);
|
||||
// return stopEvent();
|
||||
// }
|
||||
}
|
||||
|
||||
function isOperator(app) {
|
||||
return app.accessLevel === 'operator' || app.accessLevel === 'admin';
|
||||
}
|
||||
|
||||
async function refreshApps() {
|
||||
const [error, result] = await appsModel.list();
|
||||
if (error) return console.error(error);
|
||||
|
||||
const applinks = await applinksModel.list();
|
||||
|
||||
// amend properties to mimick full app
|
||||
for (const applink of applinks) {
|
||||
applink.type = APP_TYPES.LINK;
|
||||
applink.fqdn = applink.upstreamUri;
|
||||
applink.manifest = { addons: {}};
|
||||
applink.installationState = ISTATES.INSTALLED;
|
||||
applink.runState = RSTATES.RUNNING;
|
||||
applink.health = HSTATES.HEALTHY;
|
||||
applink.iconUrl = `/api/v1/applinks/${applink.id}/icon?access_token=${localStorage.token}&ts=${applink.ts}`;
|
||||
applink.accessLevel = profile.value.isAtLeastAdmin ? 'admin' : 'user';
|
||||
|
||||
result.push(applink);
|
||||
}
|
||||
|
||||
apps.value = result;
|
||||
|
||||
// gets all tags used by all apps, flattens the arrays and new Set() will dedupe
|
||||
const tags = [...new Set(apps.value.map(a => a.tags).flat())].map(t => { return { id: t, name: t }; });
|
||||
tagFilterOptions.value = [{ id: '', name: 'All Tags', }].concat(tags);
|
||||
}
|
||||
|
||||
function toggleView() {
|
||||
viewType.value = viewType.value === VIEW_TYPE.LIST ? VIEW_TYPE.GRID : VIEW_TYPE.LIST;
|
||||
localStorage.appsView = viewType.value;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
profile.value = await profileModel.get();
|
||||
|
||||
await refreshApps();
|
||||
|
||||
const [error, domains] = await domainsModel.list();
|
||||
if (error) return console.error(error);
|
||||
|
||||
domainFilterOptions.value = domainFilterOptions.value.concat(domains.map(d => { d.id = d.domain; return d; }));
|
||||
domainFilter.value = domainFilterOptions.value[0].id;
|
||||
|
||||
stateFilter.value = stateFilterOptions[0].id;
|
||||
tagFilter.value = tagFilterOptions.value[0].id;
|
||||
|
||||
ready.value = true;
|
||||
|
||||
refreshInterval = setInterval(refreshApps, 5000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(refreshInterval);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user