Skip to content

Commit 3e5d779

Browse files
committed
adding admin interface
1 parent 1fc6c83 commit 3e5d779

15 files changed

Lines changed: 269 additions & 57 deletions

File tree

client/src/MapStore.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
ColorFilters,
88
Context,
99
Dataset,
10+
DisplayConfiguration,
1011
LayerCollection,
1112
NetCDFData,
1213
NetCDFLayer,
@@ -33,6 +34,8 @@ export default class MapStore {
3334

3435
public static proModeButtonEnabled = ref(true);
3536

37+
public static displayConfiguration: Ref<DisplayConfiguration> = ref({ default_displayed_layers: [], enabled_ui: ['Collections', 'Datasets', 'Metadata'], default_tab: 'Scenarios' });
38+
3639
// Ability to toggle proMode so Staff users can see what other users see.
3740
public static proMode = computed(() => MapStore.userIsStaff.value && MapStore.proModeButtonEnabled.value);
3841

@@ -103,6 +106,10 @@ export default class MapStore {
103106
MapStore.mapLayersByDataset[datasetId] = await UVdatApi.getDatasetLayers(datasetId);
104107
}
105108

109+
public static async getDisplayConfiguration() {
110+
MapStore.displayConfiguration.value = await UVdatApi.getDisplayConfiguration();
111+
}
112+
106113
public static mapLayerFeatureGraphs = computed(() => {
107114
const foundMapLayerFeatureGraphs: { name: string, id: number; graphs: VectorFeatureTableGraph[] }[] = [];
108115
MapStore.selectedVectorMapLayers.value.forEach((item) => {

client/src/api/UVDATApi.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ref } from 'vue';
33
import OauthClient from '@girder/oauth-client/dist/oauth-client';
44
import {
55
AbstractMapLayer,
6+
AbstractMapLayerListItem,
67
Chart,
78
Context,
89
ContextWithIds,
@@ -214,7 +215,8 @@ export default class UVdatApi {
214215
return (await UVdatApi.apiClient.delete(`/files/${fileItemId}/`)).data;
215216
}
216217

217-
public static async getGlobalDatasets(filter: { unconnected: boolean }): Promise<(Dataset & { contextCount: number })[]> {
218+
219+
public static async getGlobalDatasets(filter?: { unconnected: boolean }): Promise<(Dataset & { contextCount: number })[]> {
218220
return (await UVdatApi.apiClient.get('datasets', { params: { ...filter } })).data.results;
219221
}
220222

@@ -588,28 +590,32 @@ export default class UVdatApi {
588590
return (await UVdatApi.apiClient.get('/map-layers/', { params })).data;
589591
}
590592

593+
public static async getMapLayerAll(): Promise<AbstractMapLayerListItem[]> {
594+
return (await UVdatApi.apiClient.get('/map-layers/all')).data;
595+
}
596+
591597
public static async searchVectorFeatures(requestData: SearchableVectorDataRequest): Promise<SearchableVectorFeatureResponse[]> {
592598
return (await UVdatApi.apiClient.post('/map-layers/search-features/', requestData)).data;
593599
}
594600

595601
public static async getDisplayConfiguration(): Promise<DisplayConfiguration> {
596-
const response = await UVdatApi.apiClient.get('display_configuration/');
602+
const response = await UVdatApi.apiClient.get('display-configuration/');
597603
return response.data;
598604
}
599605

600606
// Fully update the display configuration (PUT /display_configuration/)
601607
public static async updateDisplayConfiguration(
602608
config: DisplayConfiguration,
603609
): Promise<DisplayConfiguration> {
604-
const response = await UVdatApi.apiClient.put('display_configuration/', config);
610+
const response = await UVdatApi.apiClient.put('display-configuration/', config);
605611
return response.data;
606612
}
607613

608614
// Partially update the display configuration (PATCH /display_configuration/)
609615
public static async partialUpdateDisplayConfiguration(
610616
config: Partial<DisplayConfiguration>,
611617
): Promise<DisplayConfiguration> {
612-
const response = await UVdatApi.apiClient.patch('display_configuration/', config);
618+
const response = await UVdatApi.apiClient.patch('display-configuration/', config);
613619
return response.data;
614620
}
615621
}

client/src/components/FeatureSelection/vectorFeatureGraphUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ const renderVectorFeatureGraph = (
230230
g.append('path')
231231
.datum(graph.movingAverage)
232232
.attr('fill', 'none')
233-
.attr('stroke', '#00FFFF')
233+
.attr('stroke', '#FFFF00')
234234
.attr('stroke-width', 5)
235235
.attr('d', line)
236236
.attr('class', `moving-average moving-average-${key}`);

client/src/router/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { RouterOptions, createWebHistory } from 'vue-router';
22
import HomePage from '../views/HomePage.vue';
3+
import DisplayAdmin from '../views/Admin/DisplayAdmin.vue';
34

45
function makeOptions(): RouterOptions {
56
return {
@@ -10,6 +11,12 @@ function makeOptions(): RouterOptions {
1011
// component: HomePage,
1112
component: HomePage,
1213
},
14+
{
15+
path: '/admin',
16+
// component: HomePage,
17+
component: DisplayAdmin,
18+
},
19+
1320
],
1421
};
1522
}

client/src/types.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,16 @@ export interface SearchableVectorFeatureResponse {
903903
}
904904

905905
export interface DisplayConfiguration {
906-
enabled_ui: string[];
907-
default_tab: string;
908-
default_displayed_layers: Array<{ type: string; [key: string]: string }>;
906+
enabled_ui: ('Scenarios' | 'Collections' | 'Datasets' | 'Metadata')[];
907+
default_tab: 'Scenarios' | 'Collections' | 'Datasets' | 'Metadata';
908+
default_displayed_layers: Array<{ type: AbstractMapLayer['type']; id: number; name: string }>;
909+
}
910+
911+
export interface AbstractMapLayerListItem {
912+
id: number;
913+
name: string;
914+
type: AbstractMapLayer['type'];
915+
datset_id: number;
916+
file_item: { id: number, name: string }[];
917+
processing_tasks?: null | ProcessingTask[]
909918
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<script lang="ts">
2+
import {
3+
Ref, computed, defineComponent, onMounted, ref, watch,
4+
} from 'vue';
5+
import {
6+
AbstractMapLayerListItem, DisplayConfiguration,
7+
} from '../../types';
8+
import UVdatApi from '../../api/UVDATApi';
9+
10+
export default defineComponent({
11+
name: 'DisplayConfigurationEditor',
12+
setup() {
13+
const config: Ref<DisplayConfiguration | null> = ref(null);
14+
const enabledUiOptions = ['Scenarios', 'Collections', 'Datasets', 'Metadata'];
15+
const layerTypes = ['netcdf', 'vector', 'raster'];
16+
const snackbar = ref({ show: false, text: '', color: '' });
17+
const layers: Ref<AbstractMapLayerListItem[]> = ref([]);
18+
const selectedLayers: Ref<string[]> = ref([]);
19+
onMounted(async () => {
20+
config.value = await UVdatApi.getDisplayConfiguration();
21+
layers.value = await UVdatApi.getMapLayerAll();
22+
selectedLayers.value = config.value.default_displayed_layers.map((item) => (`${item.type}_${item.id}`));
23+
});
24+
25+
watch(() => config.value?.enabled_ui, (newVal) => {
26+
if (config.value && newVal && !newVal?.includes(config.value.default_tab)) {
27+
config.value.default_tab = newVal[0] || '';
28+
}
29+
});
30+
31+
const availableLayers = computed(() => {
32+
if (layers.value) {
33+
return layers.value.map((item) => ({
34+
id: item.id, name: item.name, type: item.type, index: `${item.type}_${item.id}`,
35+
}));
36+
}
37+
return [];
38+
});
39+
40+
const saveConfig = async () => {
41+
try {
42+
if (config.value) {
43+
config.value.default_displayed_layers = [];
44+
selectedLayers.value.forEach((item) => {
45+
const data = availableLayers.value.find((layer) => layer.index === item);
46+
if (data) {
47+
config.value?.default_displayed_layers.push({ id: data.id, name: data.name, type: data.type });
48+
}
49+
});
50+
await UVdatApi.updateDisplayConfiguration(config.value);
51+
}
52+
snackbar.value = { show: true, text: 'Configuration updated successfully!', color: 'success' };
53+
} catch (error) {
54+
snackbar.value = { show: true, text: 'Failed to update configuration', color: 'error' };
55+
}
56+
};
57+
58+
return {
59+
config,
60+
enabledUiOptions,
61+
layerTypes,
62+
saveConfig,
63+
snackbar,
64+
availableLayers,
65+
selectedLayers,
66+
};
67+
},
68+
});
69+
</script>
70+
<template>
71+
<v-app-bar app>
72+
<v-btn to="/">
73+
<v-icon size="x-large" to="/">
74+
mdi-arrow-left
75+
</v-icon>
76+
</v-btn>
77+
</v-app-bar>
78+
<v-container v-if="config">
79+
<v-card>
80+
<v-card-title>Display Configuration</v-card-title>
81+
<v-card-text>
82+
<v-select
83+
v-model="config.enabled_ui"
84+
:items="enabledUiOptions"
85+
label="Enabled UI Features"
86+
multiple
87+
chips
88+
/>
89+
<v-select
90+
v-model="config.default_tab"
91+
:items="config.enabled_ui"
92+
label="Default Tab"
93+
/>
94+
<v-select
95+
v-model="selectedLayers"
96+
:items="availableLayers"
97+
item-title="name"
98+
item-value="index"
99+
label="Default Visible Layers"
100+
multiple
101+
clearable
102+
closable-chips
103+
>
104+
<template #chip="{ item }">
105+
<v-chip>
106+
{{ item.raw.name }}:{{ item.raw.type }}
107+
</v-chip>
108+
</template>
109+
<template #item="{ item, props }">
110+
<v-list-item v-bind="props">
111+
{{ item.raw.type }}
112+
</v-list-item>
113+
</template>
114+
</v-select>
115+
</v-card-text>
116+
<v-card-actions>
117+
<v-btn color="primary" @click="saveConfig">
118+
Save
119+
</v-btn>
120+
</v-card-actions>
121+
</v-card>
122+
<v-snackbar v-model="snackbar.show" :color="snackbar.color" timeout="3000">
123+
{{ snackbar.text }}
124+
</v-snackbar>
125+
</v-container>
126+
</template>
127+
128+
<style scoped>
129+
</style>

client/src/views/HomePage.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import {
3-
computed, defineComponent, inject, ref,
3+
computed, defineComponent, inject, onMounted, ref,
44
watch,
55
} from 'vue';
66
import OAuthClient from '@girder/oauth-client';
@@ -46,6 +46,8 @@ export default defineComponent({
4646
}
4747
};
4848
49+
onMounted(() => MapStore.getDisplayConfiguration());
50+
4951
watch(MapStore.userIsStaff, () => {
5052
if (!MapStore.userIsStaff.value) {
5153
MapStore.proModeButtonEnabled.value = false;
@@ -278,6 +280,9 @@ export default defineComponent({
278280
</template>
279281
</v-tooltip>
280282
<v-spacer />
283+
<v-btn v-if="userIsStaff" to="/admin">
284+
Admin
285+
</v-btn>
281286
<v-btn @click="logInOrOut">
282287
{{ loginText }}
283288
</v-btn>

client/src/views/SourceSelection.vue

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts">
22
import {
33
Ref, computed, defineComponent, onMounted, ref,
4+
watch,
45
} from 'vue';
56
import Collection from '../components/DataSelection/Collection.vue';
67
import Datasets from '../components/DataSelection/Datasets.vue';
@@ -19,12 +20,18 @@ export default defineComponent({
1920
},
2021
setup() {
2122
onMounted(() => MapStore.loadCollections()); // Load for tab display if collections exists
22-
const tab: Ref<'Scenarios' | 'Datasets' | 'Collections' | 'Metadata Filters'> = ref('Scenarios');
23+
const tab: Ref<'Scenarios' | 'Datasets' | 'Collections' | 'Metadata'> = ref('Scenarios');
2324
const selectedLayersCount = computed(() => MapStore.selectedMapLayers.value.length);
25+
watch(MapStore.displayConfiguration, () => {
26+
if (MapStore.displayConfiguration.value.default_tab) {
27+
tab.value = MapStore.displayConfiguration.value.default_tab;
28+
}
29+
})
2430
return {
2531
tab,
2632
selectedLayersCount,
2733
collectionList: MapStore.availableCollections,
34+
displayConfig: MapStore.displayConfiguration
2835
};
2936
},
3037
});
@@ -38,13 +45,13 @@ export default defineComponent({
3845
<v-tab value="Scenarios" class="tab">
3946
Scenarios
4047
</v-tab>
41-
<v-tab value="Datasets" class="tab">
48+
<v-tab v-if="displayConfig.enabled_ui.includes('Datasets')" value="Datasets" class="tab">
4249
Datasets
4350
</v-tab>
44-
<v-tab v-if="collectionList.length" value="Collections" class="tab">
51+
<v-tab v-if="collectionList.length && displayConfig.enabled_ui.includes('Collections')" value="Collections" class="tab">
4552
Collections
4653
</v-tab>
47-
<v-tab value="Metadata Filters" class="tab">
54+
<v-tab v-if="displayConfig.enabled_ui.includes('Metadata')" value="Metadata" class="tab">
4855
Metadata <v-icon>mdi-filter</v-icon>
4956
</v-tab>
5057
</v-tabs>
@@ -55,7 +62,7 @@ export default defineComponent({
5562
<collection
5663
v-else-if="tab === 'Collections'"
5764
/>
58-
<MetadataLayerFilter v-else-if="tab === 'Metadata Filters'" />
65+
<MetadataLayerFilter v-else-if="tab === 'Metadata'" />
5966
</div>
6067
<v-divider v-if="selectedLayersCount" />
6168
<selected />

uvdat/core/admin.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
VectorFeatureRowData,
2323
VectorFeatureTableData,
2424
VectorMapLayer,
25+
DisplayConfiguration,
2526
)
2627

2728

@@ -211,6 +212,14 @@ def get_map_layer_name(self, obj):
211212
class VectorFeatureRowDataAdmin(admin.ModelAdmin):
212213
list_display = ['id', 'vector_feature_table', 'row_data']
213214

215+
class DisplayConfigurationAdmin(admin.ModelAdmin):
216+
list_display = [
217+
'enabled_ui',
218+
'default_tab',
219+
'default_displayed_layers',
220+
]
221+
222+
214223

215224
admin.site.register(Context, ContextAdmin)
216225
admin.site.register(Dataset, DatasetAdmin)
@@ -233,3 +242,4 @@ class VectorFeatureRowDataAdmin(admin.ModelAdmin):
233242
admin.site.register(ProcessingTask, ProcessingTaskAdmin)
234243
admin.site.register(VectorFeatureTableData, VectorFeatureTableDataAdmin)
235244
admin.site.register(VectorFeatureRowData, VectorFeatureRowDataAdmin)
245+
admin.site.register(DisplayConfiguration, DisplayConfigurationAdmin)

0 commit comments

Comments
 (0)