Skip to content
2 changes: 1 addition & 1 deletion pb_tool.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ python_files: __init__.py pygeoapi_config.py pygeoapi_config_dialog.py
main_dialog: pygeoapi_config_dialog_base.ui

# Other ui files for dialogs you create (these will be compiled)
compiled_ui_files:
compiled_ui_files: server_config_dialog.ui

# Resource file(s) that will be compiled
resource_files: resources.qrc
Expand Down
1 change: 1 addition & 0 deletions pygeoapi_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

# Import the code for the dialog
from .pygeoapi_config_dialog import PygeoapiConfigDialog

import os.path


Expand Down
184 changes: 172 additions & 12 deletions pygeoapi_config_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@
"""

from copy import deepcopy
from datetime import datetime, timezone
from datetime import date, datetime, timezone
import os
from wsgiref import headers
import requests
import yaml

from .utils.data_diff import diff_yaml_dict

from .ui_widgets.utils import get_url_status

from .server_config_dialog import Ui_serverDialog

from .models.top_level.providers.records import ProviderTypes
from .ui_widgets.providers.NewProviderWindow import NewProviderWindow
Expand Down Expand Up @@ -72,13 +75,50 @@
except:
pass

headers = {
'accept': '*/*',
'Content-Type': 'application/json'
}

def preprocess_for_json(d):
"""Recursively converts datetime/date objects in a dict to ISO strings."""
if isinstance(d, dict):
return {k: preprocess_for_json(v) for k, v in d.items()}
elif isinstance(d, list):
return [preprocess_for_json(i) for i in d]
elif isinstance(d, (datetime, date)):
return d.isoformat()
return d

class ServerConfigDialog(QDialog, Ui_serverDialog):
"""
Logic for the Server Configuration Dialog.
Inherits from QDialog (functionality) and Ui_serverDialog (layout).
"""
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self) # Builds the UI defined in Designer

# Optional: Set default values based on current config if needed
# self.ServerHostlineEdit.setText("localhost")

def get_server_data(self):
"""
Retrieve the server configuration data entered by the user.
:return: A dictionary with 'host' and 'port' keys.
"""
host = self.ServerHostlineEdit.text()
port = self.ServerSpinBox.value()
protocol = 'http' if self.radioHttp.isChecked() else 'https'
return {'host': host, 'port': port, 'protocol': protocol}


# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer
FORM_CLASS, _ = uic.loadUiType(
os.path.join(os.path.dirname(__file__), "pygeoapi_config_dialog_base.ui")
)


class PygeoapiConfigDialog(QtWidgets.QDialog, FORM_CLASS):

config_data: ConfigData
Expand Down Expand Up @@ -144,22 +184,142 @@ def on_button_clicked(self, button):
# You can also check the standard button type
if button == self.buttonBox.button(QDialogButtonBox.Save):
if self._set_validate_ui_data()[0]:
file_path, _ = QFileDialog.getSaveFileName(
self, "Save File", "", "YAML Files (*.yml);;All Files (*)"
)

# before saving, show diff with "Procced" and "Cancel" options
if file_path and self._diff_original_and_current_data():
self.save_to_file(file_path)
if self.serverRadio.isChecked():
self.server_config(save=True)
else:
file_path, _ = QFileDialog.getSaveFileName(
self, "Save File", "", "YAML Files (*.yml);;All Files (*)"
)

# before saving, show diff with "Procced" and "Cancel" options
if file_path and self._diff_original_and_current_data():
self.save_to_file(file_path)

elif button == self.buttonBox.button(QDialogButtonBox.Open):
file_name, _ = QFileDialog.getOpenFileName(
self, "Open File", "", "YAML Files (*.yml);;All Files (*)"
)
self.open_file(file_name)
if self.serverRadio.isChecked():
self.server_config(save=False)
else:
file_name, _ = QFileDialog.getOpenFileName(
self, "Open File", "", "YAML Files (*.yml);;All Files (*)"
)
self.open_file(file_name)

elif button == self.buttonBox.button(QDialogButtonBox.Close):
self.reject()
return

def server_config(self, save):

dialog = ServerConfigDialog(self)

if dialog.exec_():
data = dialog.get_server_data()
url = f"{data['protocol']}://{data['host']}:{data['port']}/admin/config"
if save == True:
self.push_to_server(url)
else:
self.pull_from_server(url)

def push_to_server(self, url):

QMessageBox.information(
self,
"Information",
f"Pushing configuration to: {url}",
)

config_dict = self.config_data.asdict_enum_safe(self.config_data)

# Pre-process the dictionary to handle datetime objects
processed_config_dict = preprocess_for_json(config_dict)

# TODO: support authentication through the QT framework
try:
# Send the PUT request to Admin API
response = requests.put(url, headers=headers, json=processed_config_dict)
response.raise_for_status()

QgsMessageLog.logMessage(
f"Success! Status Code: {response.status_code}")

QMessageBox.information(
self,
"Information",
f"Success! Status Code: {response.status_code}",
)

except requests.exceptions.RequestException as e:
QgsMessageLog.logMessage(f"An error occurred: {e}")
QMessageBox.critical(
self,
"Error",
f"An error occurred pulling the configuration from the server: {e}",
)


def pull_from_server(self, url):

QMessageBox.information(
self,
"Information",
f"Pulling configuration from: {url}",
)

# TODO: support authentication through the QT framework
try:
# Send the GET request to Admin API
response = requests.get(url, headers=headers)
response.raise_for_status()

QgsMessageLog.logMessage(
f"Success! Status Code: {response.status_code}")

QMessageBox.information(
self,
"Information",
f"Success! Status Code: {response.status_code}",
)

QgsMessageLog.logMessage(
f"Response: {response.text}")

data_dict = response.json()

self.config_data = ConfigData()
self.config_data.set_data_from_yaml(data_dict)
self.ui_setter.set_ui_from_data()

# log messages about missing or mistyped values during deserialization
QgsMessageLog.logMessage(
f"Errors during deserialization: {self.config_data.error_message}"
)
QgsMessageLog.logMessage(
f"Default values used for missing YAML fields: {self.config_data.defaults_message}"
)

# summarize all properties missing/overwitten with defaults
# atm, warning with the full list of properties
all_missing_props = self.config_data.all_missing_props
QgsMessageLog.logMessage(
f"All missing or replaced properties: {self.config_data.all_missing_props}"
)
if len(all_missing_props) > 0:
ReadOnlyTextDialog(
self,
"Warning",
f"All missing or replaced properties (check logs for more details): {self.config_data.all_missing_props}",
).exec_()

except requests.exceptions.RequestException as e:
QgsMessageLog.logMessage(f"An error occurred: {e}")

QMessageBox.critical(
self,
"Error",
f"An error occurred pulling the configuration from the server: {e}",
)


def save_to_file(self, file_path):

Expand Down
Loading