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 >
2023-11-13 23:01:44 +01:00
< v-window v-model = "viewMode" >
< v-window-item >
2023-10-29 15:14:15 +01:00
< v-row >
2023-11-13 23:26:59 +01:00
< v-col cols = "4" max -height = " 100 % " >
2023-10-29 15:14:15 +01:00
< v-toolbar
dense
flat
2023-11-13 23:01:44 +01:00
@ contextmenu = "showContextMenu"
2023-10-29 15:14:15 +01:00
>
< 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" >
2023-11-13 21:00:07 +01:00
< component
: is = "activeComponent"
v - bind = "activeValues"
@ merge = "merge"
@ close = "deselect"
> < / component >
2023-10-29 15:14:15 +01:00
< / 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 >
2023-11-13 23:26:59 +01:00
< v-btn
outlined
2023-10-29 15:14:15 +01:00
color = "primary"
2023-11-13 23:26:59 +01:00
title = "Fetch the configuration from the server anew. Any unsaved changes you might have will be lost."
2023-10-29 15:14:15 +01:00
@ click = "getConfiguration"
>
< v-icon small left > mdi - cloud - refresh < / v-icon >
Refresh
< / v-btn >
< v-spacer > < / v-spacer >
< v-btn
2023-11-13 23:26:59 +01:00
outlined
2023-10-29 15:14:15 +01:00
class = "ml-2"
color = "primary"
: disabled = "!configuration"
2023-11-13 23:18:55 +01:00
@ click = "saveToFile"
title = "Save the current configuration to a file, including any changes you might have made but not yet sent to the server."
2023-10-29 15:14:15 +01:00
>
2023-11-13 23:26:59 +01:00
< v-icon small left > mdi - content - save - outline < / v-icon >
Save
2023-10-29 15:14:15 +01:00
< / v-btn >
2023-11-13 23:26:59 +01:00
2023-10-29 15:14:15 +01:00
< v-dialog
max - width = "400px"
2023-11-13 23:24:26 +01:00
v - model . sync = "fileLoadDialog"
2023-10-29 15:14:15 +01:00
>
< template v -slot : activator = "{ on, attrs }" >
< v-btn
2023-11-13 23:26:59 +01:00
outlined
2023-10-29 15:14:15 +01:00
class = "ml-2"
2023-11-13 23:26:59 +01:00
color = "primary lighten-2"
title = "Load the configuration from a file. It will overwrite any changes that you might have made so far but it won't upload the configuration to the server. You can review the configuration and make changes before using the upload button."
2023-10-29 15:14:15 +01:00
v - bind = "attrs"
v - on = "on"
>
2023-11-13 23:26:59 +01:00
< v-icon small left > mdi - folder - open - outline < / v-icon >
Load
2023-10-29 15:14:15 +01:00
< / v-btn >
< / template >
< v-card flat >
< v-card-text class = "pt-5" >
< v-file-input
2023-11-13 23:24:26 +01:00
v - model = "files"
2023-10-29 15:14:15 +01:00
class = "mt-4"
show - size
2023-11-13 23:24:26 +01:00
accept = "application/json,application/yaml,.json,.yaml"
2023-10-29 15:14:15 +01:00
label = "Select configuration file"
2023-11-13 23:24:26 +01:00
append - outer - icon = "mdi-folder-open-outline"
: error - messages = "fileInputErrors"
@ click : append - outer = "loadFromFile"
2023-10-29 15:14:15 +01:00
> < / v-file-input >
< / v-card-text >
< / v-card >
< / v-dialog >
2023-11-13 23:25:34 +01:00
< v-spacer > < / v-spacer >
< v-btn
class = "ml-2"
color = "warning"
: disabled = "!configuration"
title = "Save the configuration on the server. This will replace the existing configuration. The project will be set to INACTIVE. Change its state back to active once you're satisfied with your changes."
@ click = "upload"
>
< v-icon small left > mdi - cloud - upload < / v-icon >
Upload
< / v-btn >
2023-10-29 15:14:15 +01:00
< / v-card-actions >
< / v-card >
< / v-col >
< / v-row >
2023-11-13 23:01:44 +01:00
< / v-window-item >
< v-window-item >
< v-row >
< v-col cols = "12" >
< v-toolbar
dense
flat
@ contextmenu = "showContextMenu"
>
< v-toolbar-title >
Advanced survey configuration
< / v-toolbar-title >
< v-spacer / >
< v-btn small outlined @click ="viewMode=0" > Go to normal configuration < / v-btn >
< / v-toolbar >
< / v-col >
< / v-row >
< v-row >
< v-col cols = "12" >
< dougal-json-builder
name = "Dougal configuration"
v - model = "configuration"
> < / dougal-json-builder >
< / v-col >
< / v-row >
< / v-window-item >
< / v-window >
< v-menu
v - model = "contextMenu"
: position - x = "contextMenuX"
: position - y = "contextMenuY"
absolute
offset - y
>
< v-list dense >
< v-list-item >
< v-btn
small
outlined
color = "red"
title = "Not a good idea"
@ click = "viewMode=1"
> Advanced configuration … < / v-btn >
< / v-list-item >
< / v-list >
< / v-menu >
2023-10-29 15:14:15 +01:00
< / v-container >
< / template >
< script >
2023-11-13 23:18:55 +01:00
import YAML from 'yaml' ;
2023-10-29 15:14:15 +01:00
import { mapActions , mapGetters } from 'vuex' ;
2023-11-13 23:01:44 +01:00
import { deepSet } from '@/lib/utils' ;
import DougalJsonBuilder from '@/components/json-builder/json-builder' ;
2023-10-29 15:14:15 +01:00
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' ;
2023-11-13 22:34:15 +01:00
import DougalProjectSettingsSmartsourceHeader from '@/components/project-settings/input-smartsource-header' ;
2023-11-13 22:42:08 +01:00
import DougalProjectSettingsSmartsourceSegy from '@/components/project-settings/input-smartsource-segy' ;
2023-10-29 15:14:15 +01:00
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 ,
2023-11-13 22:34:15 +01:00
raw _data _smsrc _header : DougalProjectSettingsSmartsourceHeader ,
2023-11-13 22:42:08 +01:00
raw _data _smsrc _segy : DougalProjectSettingsSmartsourceSegy ,
2023-10-29 15:14:15 +01:00
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" ,
2023-11-13 23:01:44 +01:00
components : {
DougalJsonBuilder
} ,
2023-10-29 15:14:15 +01:00
data ( ) {
return {
configuration : null ,
active : [ ] ,
open : [ ] ,
2023-11-13 23:24:26 +01:00
files : [ ] ,
2023-10-29 15:14:15 +01:00
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
2023-11-13 21:00:07 +01:00
} )
2023-10-29 15:14:15 +01:00
} ,
{
id : "groups" ,
name : "Groups" ,
values : ( obj ) => ( {
groups : obj ? . groups
2023-11-13 21:00:07 +01:00
} )
2023-10-29 15:14:15 +01:00
} ,
{
id : "geodetics" ,
name : "Geodetics" ,
values : ( obj ) => ( {
epsg : obj ? . epsg
2023-11-13 21:00:07 +01:00
} )
2023-10-29 15:14:15 +01:00
} ,
{
id : "binning" ,
name : "Binning" ,
2023-11-13 21:00:07 +01:00
values : ( obj ) => ( {
... obj . binning
} )
2023-10-29 15:14:15 +01:00
} ,
{
id : "input_files" ,
name : "Input files" ,
2023-11-13 21:00:07 +01:00
values : obj => ( { rootPath : obj . rootPath } ) ,
2023-10-29 15:14:15 +01:00
children : [
{
id : "preplots" ,
name : "Preplots" ,
values : ( obj ) => ( {
2023-11-13 21:00:07 +01:00
preplots : structuredClone ( obj . preplots ) ,
2023-10-29 15:14:15 +01:00
rootPath : obj . rootPath
2023-11-13 21:00:07 +01:00
} )
2023-10-29 15:14:15 +01:00
} ,
{
id : "raw_data" ,
name : "Raw data" ,
children : [
{
id : "raw_data_p111" ,
name : "P1/11" ,
values : ( obj ) => ( {
rootPath : obj . rootPath ,
2023-11-13 21:00:07 +01:00
globs : obj . raw . p111 . globs ,
paths : obj . raw . p111 . paths ,
pattern : obj . raw ? . p111 ? . pattern ,
lineNameInfo : obj . raw ? . p111 ? . lineNameInfo
} )
2023-10-29 15:14:15 +01:00
} ,
{
2023-11-13 21:00:07 +01:00
id : "raw_data_smsrc" ,
2023-10-29 15:14:15 +01:00
name : "Smartsource" ,
2023-11-13 22:34:15 +01:00
children : [
{
id : "raw_data_smsrc_header" ,
name : "Headers" ,
values : ( obj ) => ( {
rootPath : obj . rootPath ,
globs : obj ? . raw ? . source ? . smsrc ? . header ? . globs ,
paths : obj ? . raw ? . source ? . smsrc ? . header ? . paths ,
pattern : obj ? . raw ? . source ? . smsrc ? . header ? . pattern ,
lineNameInfo : obj ? . raw ? . source ? . smsrc ? . header ? . lineNameInfo
} )
} ,
2023-11-13 22:42:08 +01:00
{
id : "raw_data_smsrc_segy" ,
name : "Hydrophone data" ,
values : ( obj ) => ( {
rootPath : obj . rootPath ,
globs : obj ? . raw ? . source ? . smsrc ? . segy ? . globs ,
paths : obj ? . raw ? . source ? . smsrc ? . segy ? . paths ,
pattern : obj ? . raw ? . source ? . smsrc ? . segy ? . pattern ,
lineNameInfo : obj ? . raw ? . source ? . smsrc ? . segy ? . lineNameInfo
} )
}
2023-11-13 22:34:15 +01:00
]
2023-10-29 15:14:15 +01:00
} ,
{
id : "raw_data_ntbp" ,
name : "NTBP detection" ,
values : ( obj ) => ( {
regex : obj . raw . ntbp ? . pattern ? . regex ,
flags : obj . raw . ntbp ? . pattern ? . flags
2023-11-13 21:00:07 +01:00
} )
2023-10-29 15:14:15 +01:00
}
]
} ,
{
id : "final_data" ,
name : "Final data" ,
children : [
{
id : "final_data_p111" ,
name : "P1/11" ,
values : ( obj ) => ( {
rootPath : obj . rootPath ,
2023-11-13 21:00:07 +01:00
globs : obj . final . p111 . globs ,
paths : obj . final . p111 . paths ,
pattern : obj . final . p111 . pattern
} )
2023-10-29 15:14:15 +01:00
} ,
{
id : "final_data_pending" ,
name : "Pending line detection" ,
values : ( obj ) => ( {
regex : obj . final . pending ? . pattern ? . regex ,
flags : obj . final . pending ? . pattern ? . flags
2023-11-13 21:00:07 +01:00
} )
2023-10-29 15:14:15 +01:00
}
]
} ,
]
} ,
{
id : "line_name_format" ,
name : "Line name format" ,
2023-11-13 21:00:07 +01:00
values : ( obj ) => ( {
lineNameInfo : obj ? . online ? . line ? . lineNameInfo
} )
2023-10-29 15:14:15 +01:00
} ,
{
id : "planner_settings" ,
name : "Planner settings" ,
2023-11-13 21:00:07 +01:00
values : ( obj ) => ( { planner : obj ? . planner } )
2023-10-29 15:14:15 +01:00
} ,
{
id : "production" ,
name : "Production settings" ,
2023-11-13 21:00:07 +01:00
values : ( obj ) => ( { production : obj ? . production } )
2023-10-29 15:14:15 +01:00
} ,
{
id : "cloud_apis" ,
name : "Cloud APIs" ,
children : [
{
id : "asaqc" ,
name : "ASAQC" ,
2023-11-13 21:00:07 +01:00
values : ( obj ) => ( { value : obj ? . cloud ? . asaqc } ) ,
2023-10-29 15:14:15 +01:00
}
]
}
] ,
2023-11-13 23:24:26 +01:00
fileLoadDialog : false ,
2023-11-13 23:01:44 +01:00
viewMode : 0 ,
dialog : false ,
contextMenu : false ,
contextMenuX : null ,
contextMenuY : null
2023-10-29 15:14:15 +01:00
} ;
} ,
watch : {
active ( cur , prev ) {
if ( cur == prev ) {
return ;
}
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;
}
}
} ,
2023-11-13 23:24:26 +01:00
fileInputErrors ( ) {
const messages = [ ] ;
const validTypes = [
"application/json" ,
"application/yaml" ,
"application/x-yaml"
] ;
if ( this . files instanceof File ) {
if ( ! validTypes . includes ( this . files . type ) ) {
messages . push ( ` Invalid file type: ${ this . files . type } ` ) ;
messages . push ( "Please select a JSON or YAML file" ) ;
} else if ( this . files . size < 32 ) { // 32 is an arbitrary small value
messages . push ( "File too small to be a valid Dougal configuration" ) ;
}
} else if ( this . files && this . files . length ) {
messages . push ( "Invalid file path" ) ;
}
return messages ;
} ,
2023-10-29 15:14:15 +01:00
... mapGetters ( [ 'user' , 'writeaccess' , 'loading' , 'serverEvent' ] )
} ,
methods : {
async getConfiguration ( ) {
this . configuration = null ;
2023-11-02 20:46:26 +01:00
const url = ` /project/ ${ this . $route . params . project } /configuration ` ;
2023-11-03 16:31:58 +01:00
const init = {
headers : {
"If-None-Match" : "" // Ensure we get a fresh response
}
} ;
this . configuration = await this . api ( [ url , init ] ) ;
2023-10-29 15:14:15 +01:00
} ,
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 ( ) ;
} ,
2023-11-13 21:00:07 +01:00
merge ( [ path , value ] ) {
deepSet ( this . configuration , path , value ) ;
2023-10-29 15:14:15 +01:00
} ,
2023-11-13 21:00:07 +01:00
// Use to change the project's archival status
2023-10-29 15:14:15 +01:00
async patch ( data ) {
2023-11-02 20:46:26 +01:00
const url = ` /project/ ${ this . $route . params . project } /configuration ` ;
2023-10-29 15:14:15 +01:00
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 ) {
2023-11-13 22:58:41 +01:00
this . configuration = refreshedConfiguration ;
2023-10-29 15:14:15 +01:00
}
} ,
2023-11-13 23:25:34 +01:00
async upload ( ) {
const url = ` /project/ ${ this . $route . params . project } /configuration ` ;
const init = {
method : "PUT" ,
headers : {
//"If-Match": "" // Ensure we're not overwriting someone else's changes
"Content-Type" : "application/json"
} ,
body : { ... this . configuration , archived : true }
} ;
const res = await this . api ( [ url , init ] ) ;
if ( res && res . id == this . configuration . id ) {
// In case the server decided to apply any changes
this . showSnack ( [ "Configuration uploaded to server" , "success" ] ) ;
this . $nextTick ( ( ) => this . configuration = res ) ;
}
} ,
2023-11-13 23:24:26 +01:00
async loadFromFile ( ) {
if ( ! this . fileInputErrors . length ) {
if ( this . files . type == "application/json" ) {
this . configuration = JSON . parse ( await this . files . text ( ) ) ;
this . showSnack ( [ "Configuration loaded from file" , "primary" ] ) ;
} else if ( this . files . type == "application/yaml" || this . files . type == "application/x-yaml" ) {
this . configuration = YAML . parse ( await this . files . text ( ) ) ;
this . showSnack ( [ "Configuration loaded from file" , "primary" ] ) ;
} else {
console . error ( "Unknown file format (shouldn't happen)" , this . files . type ) ;
}
this . fileLoadDialog = false ;
}
} ,
2023-11-13 23:18:55 +01:00
async saveToFile ( ) {
const payload = YAML . stringify ( this . configuration ) ;
const blob = new Blob ( [ payload ] , { type : "application/yaml" } ) ;
const url = URL . createObjectURL ( blob ) ;
const filename = ` ${ this . $route . params . project } -configuration.yaml ` ;
const element = document . createElement ( 'a' ) ;
element . download = filename ;
element . href = url ;
element . click ( ) ;
URL . revokeObjectURL ( url ) ;
2023-10-29 15:14:15 +01:00
} ,
closeDialog ( ) {
} ,
2023-11-13 23:01:44 +01:00
showContextMenu ( e ) {
e . preventDefault ( ) ;
this . contextMenu = false
this . contextMenuX = e . clientX
this . contextMenuY = e . clientY
this . $nextTick ( ( ) => {
this . contextMenu = true
} )
} ,
2023-10-29 15:14:15 +01:00
... mapActions ( [ "api" , "showSnack" ] )
} ,
async mounted ( ) {
this . getConfiguration ( ) ;
} ,
}
< / script >