Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch config and datasets from backend + run sync #25

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions backend/maelstro/core/clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,87 @@ def gn_dst(self) -> GnApi:
def gs_dst(self) -> RestService:
return self.geo_hnd.get_gs_service(self.dst_name, is_source=False)

def involved_resources(
self,
copy_meta: bool,
copy_layers: bool,
copy_styles: bool,
) -> dict[str, list[dict[str, Any]]]:
self.copy_meta = copy_meta
self.copy_layers = copy_layers
self.copy_styles = copy_styles

with get_georchestra_handler() as geo_hnd:
self.geo_hnd = geo_hnd

zipdata = self.gn_src.get_record_zip(self.uuid).read()
self.meta = Meta(zipdata)

resources: dict[str, list[dict[str, Any]]] = {
"metadata": [],
"data": [],
}

src_gn_info = self.geo_hnd.get_service_info(
self.src_name, is_source=True, is_geonetwork=True
)
src_gn_url = src_gn_info["url"]
dst_gn_info = self.geo_hnd.get_service_info(
self.dst_name, is_source=False, is_geonetwork=True
)
dst_gn_url = dst_gn_info["url"]

resources["metadata"].append(
{
"src": src_gn_url,
"dst": dst_gn_url,
"metadata": (
[
{
"title": self.meta.get_title(),
}
]
if self.copy_meta
else []
),
}
)

dst_gs_info = self.geo_hnd.get_service_info(
self.dst_name, is_source=False, is_geonetwork=False
)
dst_gs_url = dst_gs_info["url"]

geoservers = self.meta.get_gs_layers(config.get_gs_sources())
for server_url, layer_names in geoservers.items():
styles: set[str] = set()
for layer_name in layer_names:

gs_src = self.geo_hnd.get_gs_service(server_url, True)
layers = {}
for layer_name in layer_names:
resp = gs_src.rest_client.get(f"/rest/layers/{layer_name}.json")
raise_for_status(resp)
layers[layer_name] = resp.json()

for layer in layers.values():
styles.update(self.get_styles_from_layer(layer).keys())

resources["data"].append(
{
"src": server_url,
"dst": dst_gs_url,
"layers": (
[str(layer_name) for layer_name in layer_names]
if self.copy_layers
else []
),
"styles": list(styles) if self.copy_styles else [],
}
)

return resources

def clone_dataset(
self,
copy_meta: bool,
Expand Down
13 changes: 13 additions & 0 deletions backend/maelstro/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,19 @@ def get_layers(src_name: str, uuid: str) -> list[dict[str, str]]:
return meta.get_ogc_geoserver_layers()


@app.get("/involved_resources")
def get_involved_resources(
src_name: str,
dst_name: str,
metadataUuid: str,
copy_meta: bool = True,
copy_layers: bool = True,
copy_styles: bool = True,
) -> dict[str, Any]:
clone_ds = CloneDataset(src_name, dst_name, metadataUuid)
return clone_ds.involved_resources(copy_meta, copy_layers, copy_styles)


@app.put(
"/copy",
responses={
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ services:
- georchestra_datadir:/etc/georchestra
environment:
MAELSTRO_CONFIG: /app/dev_config.yaml
DEMO_LOGIN: testadmin
DEMO_PASSWORD: testadmin
LOCAL_LOGIN: testadmin
LOCAL_PASSWORD: testadmin
healthcheck:
Expand Down
17 changes: 17 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"vue-router": "^4.5.0"
},
"devDependencies": {
"@pinia/testing": "^0.1.7",
"@tsconfig/node22": "^22.0.0",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.13.1",
Expand Down
49 changes: 49 additions & 0 deletions frontend/src/components/LogsReport.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script setup lang="ts">
import type { Log } from '@/services/synchronize.service'

defineProps<{ logs: Log[] }>()

const logClass = (status_code: number) => {
if (status_code >= 200 && status_code < 300) {
return 'log-success'
} else {
return 'log-error'
}
}
</script>

<template>
<div class="mb-4 font-semibold">Logs</div>
<div v-for="(log, index) in logs" :key="index">
<div v-if="['POST', 'PUT'].includes(log.method!)" :class="logClass(log.status_code!)">
{{ log.method }} {{ log.url }}
</div>
<div v-if="log.operation" class="log-info">{{ log.operation }}</div>
</div>
</template>

<style scoped>
.log-info {
background-color: #e0f7fa; /* Bleu clair */
border-left: 5px solid #039be5;
padding: 10px;
margin-bottom: 5px;
border-radius: 5px;
}

.log-success {
background-color: #e8f5e9; /* Vert pâle */
border-left: 5px solid #43a047; /* Bordure vert plus foncé */
padding: 10px;
margin-bottom: 5px;
border-radius: 5px;
}

.log-error {
background-color: #ffebee; /* Rouge clair */
border-left: 5px solid #d32f2f;
padding: 10px;
margin-bottom: 5px;
border-radius: 5px;
}
</style>
20 changes: 20 additions & 0 deletions frontend/src/components/__tests__/LogsReport.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import LogsReport from '../LogsReport.vue'

describe('LogsReport', () => {
it('renders properly', () => {
const wrapper = mount(LogsReport, {
props: {
logs: [
{
method: 'PUT',
status_code: 200,
url: 'http://proxy:8080/geoserver/rest/styles/point.sld',
},
],
},
})
expect(wrapper.text()).toContain('http://proxy:8080/geoserver/rest/styles/point.sld')
})
})
12 changes: 11 additions & 1 deletion frontend/src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,15 @@
"User guide": "Guide d'utilisation",
"Metadata": "Métadonnées",
"Layers": "Couches",
"Styles": "Styles"
"Styles": "Styles",
"Dataset is mandatory": "Le champ métadonnées est obligatoire",
"Destination is mandatory": "Le champ destination est obligatoire",
"Source platform": "Plateforme source",
"Source:": "Source :",
"Destination:": "Destination :",
"Metadata:": "Métadonnées :",
"Layers:": "Couches :",
"Styles:": "Styles :",
"Go back to form": "Retourner au formulaire",
"Confirm": "Confirmer"
}
12 changes: 2 additions & 10 deletions frontend/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Aura from '@primevue/themes/aura'
import { createPinia } from 'pinia'
import PrimeVue, { usePrimeVue, type PrimeVueLocaleOptions } from 'primevue/config'
import { createApp, watch } from 'vue'
import PrimeVue, { type PrimeVueLocaleOptions } from 'primevue/config'
import { createApp } from 'vue'
import App from './App.vue'
import './assets/main.css'
import router from './router'
Expand Down Expand Up @@ -51,12 +51,4 @@ app.use(PrimeVue, {
locale: primeLocales[i18n.global.locale],
})

watch(
() => i18n.global.locale, // Observe la langue actuelle de Vue I18n
(newLocale) => {
const primevue = usePrimeVue()
primevue.config.locale = primeLocales[newLocale] || primeLocales.en
},
)

app.mount('#app')
4 changes: 4 additions & 0 deletions frontend/src/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router'
import FormView from '../views/FormView.vue'
import BaseLayout from '@/layouts/BaseLayout.vue'
import { useConfigStore } from '@/stores/config.store'

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
Expand All @@ -21,6 +22,9 @@ const router = createRouter({
path: 'synchronize',
name: 'synchronize',
component: FormView,
beforeEnter: async () => {
await useConfigStore().fetchConfig()
},
},
],
},
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/services/config.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type Source = {
name: string
url: string
}

export type Destination = {
name: string
gn_url: string
gs_url: string
}

export const configService = {
async getSources(): Promise<Source[]> {
const response = await fetch('/maelstro-backend/sources')
return await response.json()
},

async getDestinations(): Promise<Destination[]> {
const response = await fetch('/maelstro-backend/destinations')
return await response.json()
},
}
81 changes: 81 additions & 0 deletions frontend/src/services/geonetworkSearch.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
export type SearchResult = {
uuid: string
resourceTitleObject: {
default: string
}
resourceAbstractObject: {
default: string
}
}

interface GnSource {
uuid: string
resourceTitleObject: {
default: string
}
resourceAbstractObject: {
default: string
}
}

interface GnHit {
_source: GnSource
}

export const geonetworkSearchService = {
async search(sourceName: string, query: string): Promise<SearchResult[]> {
// const url =
// 'https://demo.georchestra.org/geonetwork/srv/api/search/records/_search?bucket=bucket'

const url = `/maelstro-backend/search/${sourceName}`

const searchResponse = await fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: {
bool: {
must: [
{ terms: { isTemplate: ['n'] } },
{
multi_match: {
query: query,
type: 'bool_prefix',
fields: [
'resourceTitleObject.*',
'resourceAbstractObject.*',
'tag',
'resourceIdentifier',
],
},
},
],
must_not: [
{
terms: {
resourceType: ['service', 'map', 'map/static', 'mapDigital'],
},
},
{ term: { isHarvested: true } },
],
},
},
_source: ['resourceTitleObject', 'resourceAbstractObject', 'uuid'],
from: 0,
size: 20,
}),
})

const hits = (await searchResponse.json()).hits.hits
if (!hits) return []

return hits.map((hit: GnHit) => ({
uuid: hit._source.uuid,
resourceTitleObject: hit._source.resourceTitleObject,
resourceAbstractObject: hit._source.resourceAbstractObject,
}))
},
}
Loading