Implement info view
This commit is contained in:
@@ -1,16 +1,145 @@
|
||||
<script setup>
|
||||
|
||||
import { onMounted } from 'vue';
|
||||
import { onMounted, ref, useTemplateRef } from 'vue';
|
||||
import { Button } from 'pankow';
|
||||
import { prettyDate } from 'pankow/utils';
|
||||
import { marked } from 'marked';
|
||||
import AppsModel from '../../models/AppsModel.js';
|
||||
|
||||
const appsModel = AppsModel.create();
|
||||
|
||||
const props = defineProps([ 'app' ]);
|
||||
const emit = defineEmits([ 'changed' ]);
|
||||
|
||||
const id = ref('');
|
||||
const notesTextarea = useTemplateRef('notesTextarea');
|
||||
const showDoneChecklist = ref(false);
|
||||
const hasOldChecklist = ref(false);
|
||||
const checklist = ref([]);
|
||||
const manifest = ref({});
|
||||
const editing = ref(false);
|
||||
const busy = ref(false);
|
||||
const placeholder = 'Add admin notes here...';
|
||||
const noteContent = ref('');
|
||||
|
||||
async function onAckChecklistItem(item, key) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
busy.value = true;
|
||||
|
||||
// skip saving if unchanged from postInstall
|
||||
if (noteContent.value === props.app.manifest.postInstallMessage) {
|
||||
busy.value = false;
|
||||
editing.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const [error] = await appsModel.configure(id.value, 'notes', { notes: noteContent.value });
|
||||
if (error) {
|
||||
busy.value = false;
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
// let main view know about this
|
||||
emit('changed');
|
||||
|
||||
editing.value = false;
|
||||
busy.value = false;
|
||||
}
|
||||
|
||||
function onEdit() {
|
||||
editing.value = true;
|
||||
setTimeout(() => notesTextarea.value.focus(), 200);
|
||||
}
|
||||
|
||||
function onDismiss() {
|
||||
editing.value = false;
|
||||
noteContent.value = props.app.notes === null ? props.app.manifest.postInstallMessage : props.app.notes;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log(props.app)
|
||||
const app = props.app;
|
||||
manifest.value = app.manifest || {};
|
||||
id.value = app.id;
|
||||
checklist.value = app.checklist;
|
||||
hasOldChecklist.value = !!Object.keys(app.checklist).find((k) => { return app.checklist[k].acknowledged; });
|
||||
noteContent.value = app.notes === null ? app.manifest.postInstallMessage : app.notes;
|
||||
editing.value = false;
|
||||
busy.value = false;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
Info {{ app.id }}
|
||||
<label class="control-label">{{ $t('app.updates.info.title') }}</label>
|
||||
|
||||
<div class="actionable" @click="info.showDoneChecklist = true" v-show="hasOldChecklist && !showDoneChecklist">Show Checklist</div>
|
||||
<div class="actionable" @click="info.showDoneChecklist = false" v-show="showDoneChecklist">Hide Checklist</div>
|
||||
|
||||
<div v-for="(item, key) in checklist" :key="key">
|
||||
<div class="checklist-item" v-if="!item.acknowledged">
|
||||
<span v-html="marked.parse(item.message)"></span>
|
||||
<button class="btn btn-xs btn-default" style="margin-left: 10px;" @click="onAckChecklistItem(item, key)">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="(item, key) in checklist" :key="key" v-show="showDoneChecklist">
|
||||
<div class="checklist-item checklist-item-acknowledged" v-if="item.acknowledged">
|
||||
<span v-html="marked.parse(item.message)"></span>
|
||||
<span class="text-muted text-small">{{ item.changedBy }} {{ prettyDate(item.changedAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 10px"></div>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('app.updates.info.description') }}</div>
|
||||
<div class="info-value" v-if="app.appStoreId">{{ manifest.title }} {{ app.upstreamVersion }}</div>
|
||||
<div class="info-value" v-else>{{ manifest.dockerImage }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('app.updates.info.appId') }}</div>
|
||||
<div class="info-value">{{ app.id }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('app.updates.info.packageVersion') }}</div>
|
||||
<div class="info-value" v-if="app.appStoreId"><a :href="`/#/appstore/${manifest.id}?version=${manifest.version}`">{{ manifest.id }}@{{ manifest.version }}</a></div>
|
||||
<div class="info-value" v-else>{{ manifest.version }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('app.updates.info.installedAt') }}</div>
|
||||
<div class="info-value">{{ prettyDate(app.creationTime) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('app.updates.info.lastUpdated') }}</div>
|
||||
<div class="info-value">{{ prettyDate(app.updateTime) }}</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>
|
||||
<label class="control-label">{{ $t('app.info.notes.title') }}</label><i v-show="!editing" class="info-edit-indicator fa fa-pencil-alt" @click="onEdit()"></i>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<div v-show="!editing">
|
||||
<div v-if="noteContent" v-html="marked.parse(noteContent)"></div>
|
||||
<div v-else class="text-muted hand" @click="onEdit()">{{ placeholder }}</div>
|
||||
</div>
|
||||
<div v-show="editing">
|
||||
<textarea ref="notesTextarea" style="white-space: pre-wrap; margin-bottom: 5px; width: 100%" v-model="noteContent" rows="10"></textarea>
|
||||
<div style="display: flex; gap: 5px">
|
||||
<Button secondary @click="onDismiss()" v-show="!busy">{{ $t('main.dialog.cancel') }}</Button>
|
||||
<Button @click="onSubmit()" :disabled="busy" :loading="busy">{{ $t('app.display.saveAction') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -188,7 +188,18 @@ function create() {
|
||||
|
||||
await sleep(2000);
|
||||
}
|
||||
}
|
||||
},
|
||||
async configure(id, setting, data) {
|
||||
let result;
|
||||
try {
|
||||
result = await fetcher.post(`${origin}/api/v1/apps/${id}/configure/${setting}`, data, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
return [e];
|
||||
}
|
||||
|
||||
if (result.status !== 200 && result.status !== 202) return [result];
|
||||
return [null];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,14 +6,15 @@ import { useI18n } from 'vue-i18n';
|
||||
const i18n = useI18n();
|
||||
const t = i18n.t;
|
||||
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, useTemplateRef } from 'vue';
|
||||
import { Button, ButtonGroup, TabView } from 'pankow';
|
||||
import Section from '../components/Section.vue';
|
||||
import Info from '../components/app/Info.vue';
|
||||
import AppsModel from '../models/AppsModel.js';
|
||||
import { APP_TYPES } from '../constants.js';
|
||||
|
||||
const appsModel = AppsModel.create();
|
||||
|
||||
const tabView = useTemplateRef('tabView');
|
||||
const tabs = ref({
|
||||
info: t('app.infoTabTitle'),
|
||||
display: t('app.displayTabTitle'),
|
||||
@@ -33,7 +34,6 @@ const tabs = ref({
|
||||
eventlog: t('app.eventlogTabTitle'),
|
||||
uninstall: t('app.uninstallTabTitle'),
|
||||
});
|
||||
const view = ref('info');
|
||||
const id = ref('');
|
||||
const app = ref({});
|
||||
const link = ref('');
|
||||
@@ -103,22 +103,23 @@ onMounted(async () => {
|
||||
if (parts.length !== 2) return;
|
||||
|
||||
id.value = parts[0];
|
||||
view.value = parts[1];
|
||||
|
||||
await refresh();
|
||||
|
||||
tabView.value.open(parts[1] || 'info');
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="content">
|
||||
<div style="display: flex; margin-bottom: 20px; justify-content: space-between;">
|
||||
<div style="display: flex;">
|
||||
<img :src="API_ORIGIN + app.iconUrl" v-fallback-image="API_ORIGIN + '/img/appicon_fallback.png'" style="width: 64px"/>
|
||||
<h2>{{ app.label || app.fqdn }}</h2>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 10px; align-items: center; margin-right: 20px;">
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<Button outline tool
|
||||
@click="onToggleRunState()"
|
||||
:disabled="app.taskId || app.error || app.installationState === 'pending_start' || app.installationState === 'pending_stop'"
|
||||
@@ -136,7 +137,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TabView :tabs="tabs" default-active="info">
|
||||
<TabView ref="tabView" :tabs="tabs">
|
||||
<template #info><Info :app="app"/></template>
|
||||
<template #display>Display</template>
|
||||
<template #location></template>
|
||||
|
||||
Reference in New Issue
Block a user