Files
dougal-software/lib/www/client/source/src/views/ProjectSettings.vue

577 lines
17 KiB
Vue
Raw Normal View History

2023-10-29 15:14:15 +01:00
<template>
<v-container fluid>
<v-overlay :value="loading && !configuration" absolute>
<v-progress-circular
indeterminate
size="64"
></v-progress-circular>
</v-overlay>
<v-overlay :value="!configuration && !loading" absolute opacity="0.8">
<v-row justify="center">
<v-alert
type="error"
>
The configuration could not be loaded.
</v-alert>
</v-row>
<v-row justify="center">
<v-btn color="primary" @click="getConfiguration">Retry</v-btn>
</v-row>
</v-overlay>
<v-row>
<v-col cols="4">
<v-toolbar
dense
flat
>
<v-toolbar-title>
Survey configuration
</v-toolbar-title>
</v-toolbar>
<v-switch
dense
label="Active"
title="Inactive surveys do not take new data, but changes to the logs can still be made."
:disabled="!configuration"
v-model="surveyState"
></v-switch>
<v-treeview
dense
activatable
hoverable
:multiple-active="false"
:active.sync="active"
:open.sync="open"
:items="treeview"
style="cursor:pointer;"
>
</v-treeview>
</v-col>
<v-col cols="8" v-if="activeComponent">
<component :is="activeComponent" :value="activeValues" @input="save" @close="deselect"></component>
</v-col>
<v-col cols="8" v-else>
<v-card>
<v-card-text>
<p>Select a configuration section to change its settings. When clicking <v-btn small color="primary">Save</v-btn> the changes are immediately saved to the server and start to take effect.</p>
<v-divider class="my-5"></v-divider>
<v-alert border="left" type="warning">
Be careful when changing configuration settings! It is rather easy to break things.
</v-alert>
<v-alert border="left" type="info">
On the first save, the survey will be switched to <em>inactive</em> if not already so. This means that no new data will be read. Remember to switch back to <strong>active</strong> when satisfied with your changes.
</v-alert>
<v-divider class="my-5"></v-divider>
<p>It is recommended that you download a backup of your configuration before making any changes.</p>
<v-divider class="my-5"></v-divider>
</v-card-text>
<v-card-actions>
<v-btn outlined
color="primary"
title="Any unsaved changes you might have will be lost."
@click="getConfiguration"
>
<v-icon small left>mdi-cloud-refresh</v-icon>
Refresh
</v-btn>
<v-spacer></v-spacer>
<v-btn
class="ml-2"
color="primary"
:disabled="!configuration"
:href="`/api/project/${this.$route.params.project}/configuration?mime=application/yaml`"
:download="`${this.$route.params.project}.yaml`"
>
<v-icon small left>mdi-cloud-download</v-icon>
Download
</v-btn>
<v-dialog
max-width="400px"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
class="ml-2"
color="warning"
v-bind="attrs"
v-on="on"
>
<v-icon small left>mdi-cloud-upload</v-icon>
Upload
</v-btn>
</template>
<v-card flat>
<v-card-text class="pt-5">
<v-file-input
class="mt-4"
show-size
accept="application/json"
label="Select configuration file"
append-outer-icon="mdi-cloud-upload"
@click-append-outer="upload"
></v-file-input>
</v-card-text>
</v-card>
</v-dialog>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import DougalProjectSettingsNameId from '@/components/project-settings/name-id';
import DougalProjectSettingsGroups from '@/components/project-settings/groups';
import DougalProjectSettingsGeodetics from '@/components/project-settings/geodetics';
import DougalProjectSettingsBinning from '@/components/project-settings/binning';
import DougalProjectSettingsFilePath from '@/components/project-settings/file-path';
import DougalProjectSettingsPreplots from '@/components/project-settings/preplots';
import DougalProjectSettingsRawP111 from '@/components/project-settings/input-raw-p111';
import DougalProjectSettingsFinalP111 from '@/components/project-settings/input-final-p111';
import DougalProjectSettingsRawNTBP from '@/components/project-settings/input-raw-ntbp';
import DougalProjectSettingsFinalPending from '@/components/project-settings/input-final-pending';
import DougalProjectSettingsSmartsource from '@/components/project-settings/input-smartsource';
import DougalProjectSettingsPlanner from '@/components/project-settings/planner';
import DougalProjectSettingsOnlineLineNameFormat from '@/components/project-settings/online-line-name-format';
import DougalProjectSettingsASAQC from '@/components/project-settings/asaqc';
import DougalProjectSettingsProduction from '@/components/project-settings/production';
// Temporary placeholder component
import DougalProjectSettingsNotImplemented from '@/components/project-settings/not-implemented';
const components = {
name_id: DougalProjectSettingsNameId,
groups: DougalProjectSettingsGroups,
geodetics: DougalProjectSettingsGeodetics,
binning: DougalProjectSettingsBinning,
input_files: DougalProjectSettingsFilePath,
preplots: DougalProjectSettingsPreplots,
//raw_data: DougalProjectSettingsNotImplemented,
raw_data_p111: DougalProjectSettingsRawP111,
raw_data_smartsource: DougalProjectSettingsSmartsource,
raw_data_ntbp: DougalProjectSettingsRawNTBP,
//final_data: DougalProjectSettingsNotImplemented,
final_data_p111: DougalProjectSettingsFinalP111,
final_data_pending: DougalProjectSettingsFinalPending,
line_name_format: DougalProjectSettingsOnlineLineNameFormat,
planner_settings: DougalProjectSettingsPlanner,
logging: DougalProjectSettingsNotImplemented,
logging_preset_comments: DougalProjectSettingsNotImplemented,
logging_labels: DougalProjectSettingsNotImplemented,
asaqc: DougalProjectSettingsASAQC,
production: DougalProjectSettingsProduction
}
export default {
name: "DougalProjectSettings",
data () {
return {
configuration: null,
active: [],
open: [],
treeview: [
/*
{
id: 0,
name: "Archive",
values: (cfg) => cfg.archived,
save: (data, cfg) => {
cfg.archived = data.archived;
return cfg;
}
},
*/
{
id: "name_id",
name: "Name and ID",
values: (obj) => ({
id: obj?.id,
name: obj?.name
}),
save: async (data, cfg) => {
await this.patch({
id: data.id,
name: data.name,
});
}
},
{
id: "groups",
name: "Groups",
values: (obj) => ({
groups: obj?.groups
}),
save: async (data, cfg) => {
await this.patch({
groups: data.groups,
});
}
},
{
id: "geodetics",
name: "Geodetics",
values: (obj) => ({
epsg: obj?.epsg
}),
save: async (data, cfg) => {
await this.patch({
epsg: data.epsg
});
}
},
{
id: "binning",
name: "Binning",
values: (obj) => {
const data = {...obj.binning};
data.origin = {...obj.binning.origin};
return data;
},
save: async (data, cfg) => {
await this.patch({binning: {...data}});
}
},
{
id: "input_files",
name: "Input files",
values: obj => ({ path: obj.rootPath}),
save: async (data, cfg) => {
await this.patch({rootPath: data.path});
},
children: [
{
id: "preplots",
name: "Preplots",
values: (obj) => ({
preplots: obj.preplots,
rootPath: obj.rootPath
}),
save: async (data, cfg) => {
await this.patch({preplots: {...data.preplots}})
}
},
{
id: "raw_data",
name: "Raw data",
children: [
{
id: "raw_data_p111",
name: "P1/11",
values: (obj) => ({
rootPath: obj.rootPath,
globs: [...obj.raw.p111.globs],
paths: [...obj.raw.p111.paths],
pattern: structuredClone(obj.raw.p111.pattern)
}),
save: async (data, cfg) => {
await this.patch({raw: {p111: {...data}}});
}
},
{
id: "raw_data_smartsource",
name: "Smartsource",
values: (obj) => ({
rootPath: obj.rootPath,
globs: [...obj.raw.smsrc.globs],
paths: [...obj.raw.smsrc.paths],
pattern: structuredClone(obj.raw.smsrc.pattern)
}),
save: async (data, cfg) => {
await this.patch({raw: {smsrc: {...data}}});
}
},
{
id: "raw_data_ntbp",
name: "NTBP detection",
values: (obj) => ({
regex: obj.raw.ntbp?.pattern?.regex,
flags: obj.raw.ntbp?.pattern?.flags
}),
save: async (data, cfg) => {
await this.patch({
raw:{
ntbp: {
pattern: {
regex: data.regex,
flags: data.flags
}
}
}
})
}
}
]
},
{
id: "final_data",
name: "Final data",
children: [
{
id: "final_data_p111",
name: "P1/11",
values: (obj) => ({
rootPath: obj.rootPath,
globs: [...obj.final.p111.globs],
paths: [...obj.final.p111.paths],
pattern: structuredClone(obj.final.p111.pattern)
}),
save: async (data, cfg) => {
await this.patch({final: {p111: {...data}}});
}
},
{
id: "final_data_pending",
name: "Pending line detection",
values: (obj) => ({
regex: obj.final.pending?.pattern?.regex,
flags: obj.final.pending?.pattern?.flags
}),
save: async (data, cfg) => {
await this.patch({
final:{
pending: {
pattern: {
regex: data.regex,
flags: data.flags
}
}
}
})
}
}
]
},
]
},
{
id: "line_name_format",
name: "Line name format",
values: (obj) => ({...obj.online?.line?.pattern}),
save: async (data, cfg) => {
await this.patch({
online: {
line: {
pattern: {
...data
}
}
}
});
}
},
{
id: "planner_settings",
name: "Planner settings",
values: (obj) => ({...obj.planner}),
save: async (data, cfg) => {
await this.patch({
planner: {...data}
});
}
},
{
id: "production",
name: "Production settings",
values: (obj) => ({...obj.production}),
save: async (data, cfg) => {
await this.patch({
production: {...data}
});
}
},
{
id: "logging",
name: "Logging",
children: [
{
id: "logging_preset_comments",
name: "Preset comments"
},
{
id: "logging_labels",
name: "Labels"
}
]
},
{
id: "cloud_apis",
name: "Cloud APIs",
children: [
{
id: "asaqc",
name: "ASAQC",
values: (obj) => ({...obj.asaqc}),
save: async (data, cfg) => {
await this.patch({asaqc: {...cfg}});
}
}
]
}
],
dialog: false
};
},
watch: {
active (cur, prev) {
if (cur == prev) {
return;
}
console.log("Current", cur);
console.log("Item", this.activeItem);
console.log("Component", this.activeComponent);
if (!this.activeComponent && this.activeItem?.children?.length) {
// Automatically expand this item
if (!this.open.includes(cur)) {
this.open.push(cur);
}
this.$nextTick( () => {
const idx = this.active.findIndex(i => i == cur);
if (idx != -1) {
this.active.push(this.activeItem.children[0].id);
this.active.splice(cur, 1);
}
});
}
}
},
computed: {
activeComponent () {
return components[this.active[0]];
},
activeItem () {
function walk (tree) {
const list = [];
for (const leaf of tree) {
if (leaf.children) {
list.push(...walk(leaf.children));
}
list.push(leaf);
}
return list;
}
return walk(this.treeview).find(i => i.id === this.active[0]);
},
activeValues () {
return this.activeItem?.values &&
this.activeItem.values(this.configuration);
},
surveyState: {
get () {
return this.configuration && !this.configuration.archived;
},
async set (value) {
if (this.configuration) {
await this.patch({archived: !value});
// this.configuration.archived = !value;
}
}
},
...mapGetters(['user', 'writeaccess', 'loading', 'serverEvent'])
},
methods: {
async getConfiguration () {
this.configuration = null;
const url = `/project/${this.$route.params.project}/configuration/`;
this.configuration = await this.api([url]);
},
makeTree (obj, id=0) {
const isObject = typeof obj === "object" && !Array.isArray(obj) && obj !== null;
return isObject
? Object.keys(obj).map(k => {
const children = this.makeTree(obj[k], id); //.filter(i => i !== null);
return {
id: id++,
name: k,
children
}
})
: null;
},
deselect () {
this.active.pop();
},
async save (data) {
console.log("SAVING", data);
if (this.activeItem?.save) {
await this.activeItem.save(data, this.configuration);
/*
const cfg = this.activeItem.save(data, this.configuration);
this.configuration = {...cfg};
this.surveyState = false;
*/
// TODO And now push to the server
}
},
async patch (data) {
const url = `/project/${this.$route.params.project}/configuration/`;
const init = {
method: "PATCH",
body: data
};
const callback = async (err, res) => {
if (!err && res.ok) {
this.showSnack(["Configuration saved", "success"]);
}
};
const refreshedConfiguration = await this.api([url, init, callback]);
if (refreshedConfiguration) {
this.configuration == refreshedConfiguration;
}
},
upload () {
console.log("UPLOAD");
},
closeDialog () {
},
...mapActions(["api", "showSnack"])
},
async mounted () {
this.getConfiguration();
},
}
</script>