Port apps view to composition api

This commit is contained in:
Johannes Zellner
2025-02-03 14:50:06 +01:00
parent dffef6f839
commit 21888829cc

View File

@@ -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>