Add WeatherAPI config page

This commit is contained in:
Jeppe Bundgaard
2026-03-12 09:06:55 +01:00
parent 655bb622f5
commit 4860e9bdef
9 changed files with 275 additions and 2 deletions
+24
View File
@@ -0,0 +1,24 @@
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
version = 1
name = "pleno-vue"
[setup]
script = '''
npm ci
npx playwright install
'''
[[actions]]
name = "Run dev"
icon = "run"
command = "npm run dev"
[[actions]]
name = "Run all tests"
icon = "test"
command = "npm run test:all"
[[actions]]
name = "Run debug"
icon = "debug"
command = "npx playwright test --debug"
@@ -148,6 +148,13 @@ const menu_items = ref([
children: [], children: [],
hidden: false hidden: false
}, },
{
label: SessionUser.superUser.modules.weatherapi.meta.title,
value: SessionUser.superUser.modules.weatherapi.meta.config_endpoint,
icon: SessionUser.superUser.modules.weatherapi.meta.icon,
children: [],
hidden: false
},
], ],
onSelect: async (value) => { onSelect: async (value) => {
SessionUser.functions.redirectTo.superUser(value); SessionUser.functions.redirectTo.superUser(value);
@@ -219,4 +226,4 @@ const selectDepartment = (department) => {
<style scoped> <style scoped>
</style> </style>
@@ -43,6 +43,7 @@ const items = computed<NavigationItemProps[]>(() => [
{ label: firstToUpperCase(SessionUser.superUser.modules.motorapi.meta.labels.multiple), to: '/superuser/configuration/motorapi'}, { label: firstToUpperCase(SessionUser.superUser.modules.motorapi.meta.labels.multiple), to: '/superuser/configuration/motorapi'},
{ label: firstToUpperCase(SessionUser.superUser.modules.stripe.meta.labels.multiple), to: '/superuser/configuration/stripe'}, { label: firstToUpperCase(SessionUser.superUser.modules.stripe.meta.labels.multiple), to: '/superuser/configuration/stripe'},
{ label: firstToUpperCase(SessionUser.superUser.modules.fxratesapi.meta.labels.multiple), to: '/superuser/configuration/fxratesapi'}, { label: firstToUpperCase(SessionUser.superUser.modules.fxratesapi.meta.labels.multiple), to: '/superuser/configuration/fxratesapi'},
{ label: firstToUpperCase(SessionUser.superUser.modules.weatherapi.meta.labels.multiple), to: '/superuser/configuration/weatherapi'},
{ label: firstToUpperCase(SessionUser.superUser.modules.gatewayapi.meta.labels.multiple), to: '/superuser/configuration/gatewayapi'}, { label: firstToUpperCase(SessionUser.superUser.modules.gatewayapi.meta.labels.multiple), to: '/superuser/configuration/gatewayapi'},
{ label: firstToUpperCase(SessionUser.superUser.modules.entra.meta.labels.multiple), to: '/superuser/configuration/entra'}, { label: firstToUpperCase(SessionUser.superUser.modules.entra.meta.labels.multiple), to: '/superuser/configuration/entra'},
{ label: firstToUpperCase(SessionUser.superUser.modules.limble.meta.labels.multiple), to: '/superuser/configuration/limble'}, { label: firstToUpperCase(SessionUser.superUser.modules.limble.meta.labels.multiple), to: '/superuser/configuration/limble'},
@@ -0,0 +1,51 @@
<script>
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
import { handleEconomicError } from "@/components/request/HandleEconomicError.vue";
/**
* The WeatherAPI -> Config object
*/
export const Config = {
get: async (variable) => {
return authenticatedRequest(
"/weatherapi/config?variable=" + variable,
"GET"
);
},
get_all: async () => {
return authenticatedRequest(
"/weatherapi/config",
"GET"
);
},
set: async (variable, value) => {
return authenticatedRequest(
"/weatherapi/config",
"POST",
{
variable: variable,
value: value
}
);
},
keys: {
secret_key: {
get: async () => {
return Config.get("secret_key");
},
set: async (value) => {
return Config.set("secret_key", value);
},
},
},
enabled: {
get: async () => {
return Config.get("enabled");
},
set: async (enabled) => {
return Config.set("enabled", enabled);
},
},
};
</script>
@@ -0,0 +1,45 @@
<script>
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
import { handleEconomicError } from "@/components/request/HandleEconomicError.vue";
import { Config } from "@/components/session/token/superUser/modules/weatherapi/Config.vue";
/**
* The WeatherAPI object
*/
export const WeatherAPI = {
meta: {
title: "WeatherAPI",
icon: "fas fa-cloud-sun",
description: "The WeatherAPI integration",
endpoint: "/modules/weatherapi",
config_endpoint: "/configuration/weatherapi",
labels: {
single: "weatherapi",
multiple: "weatherapi",
}
},
config: Config,
functions: {
current: async (query) => {
return authenticatedRequest(
"/modules/weatherapi/current?q=" + encodeURIComponent(query),
"GET"
);
},
forecast: async (query, days = null) => {
const daysParameter = Number.isInteger(days) ? "&days=" + days : "";
return authenticatedRequest(
"/modules/weatherapi/forecast?q=" + encodeURIComponent(query) + daysParameter,
"GET"
);
},
search: async (query) => {
return authenticatedRequest(
"/modules/weatherapi/search?q=" + encodeURIComponent(query),
"GET"
);
},
}
};
</script>
@@ -10,6 +10,7 @@ import { Backups } from "@/components/session/token/superUser/modules/Backups/Ba
import { MotorAPI } from "@/components/session/token/superUser/modules/motorapi/MotorAPI.vue"; import { MotorAPI } from "@/components/session/token/superUser/modules/motorapi/MotorAPI.vue";
import { Stripe } from "@/components/session/token/superUser/modules/stripe/Stripe.vue"; import { Stripe } from "@/components/session/token/superUser/modules/stripe/Stripe.vue";
import { FXRatesAPI } from "@/components/session/token/superUser/modules/fxratesapi/FXRatesAPI.vue"; import { FXRatesAPI } from "@/components/session/token/superUser/modules/fxratesapi/FXRatesAPI.vue";
import { WeatherAPI } from "@/components/session/token/superUser/modules/weatherapi/WeatherAPI.vue";
import { GatewayAPI } from "@/components/session/token/superUser/modules/gatewayapi/GatewayAPI.vue"; import { GatewayAPI } from "@/components/session/token/superUser/modules/gatewayapi/GatewayAPI.vue";
import { XLVask } from "@/components/session/token/superUser/modules/xlvask/XLVask.vue"; import { XLVask } from "@/components/session/token/superUser/modules/xlvask/XLVask.vue";
import { Entra } from "@/components/session/token/superUser/modules/entra/Entra.vue"; import { Entra } from "@/components/session/token/superUser/modules/entra/Entra.vue";
@@ -39,6 +40,7 @@ export const SuperUserObject = {
motorapi: MotorAPI, motorapi: MotorAPI,
stripe: Stripe, stripe: Stripe,
fxratesapi: FXRatesAPI, fxratesapi: FXRatesAPI,
weatherapi: WeatherAPI,
gatewayapi: GatewayAPI, gatewayapi: GatewayAPI,
xlvask: XLVask, xlvask: XLVask,
entra: Entra, entra: Entra,
+7
View File
@@ -90,6 +90,7 @@ import GuestHome from "@/views/guest/GuestHome.vue";
import GuestBookExteriorWash from "@/views/guest/book/GuestBookExteriorWash.vue"; import GuestBookExteriorWash from "@/views/guest/book/GuestBookExteriorWash.vue";
import DepartmentProfile from "@/views/dashboards/superUserDashboard/department/DepartmentProfile.vue"; import DepartmentProfile from "@/views/dashboards/superUserDashboard/department/DepartmentProfile.vue";
import ConfigurationFXRatesAPI from "@/views/dashboards/superUserDashboard/configuration/ConfigurationFXRatesAPI.vue"; import ConfigurationFXRatesAPI from "@/views/dashboards/superUserDashboard/configuration/ConfigurationFXRatesAPI.vue";
import ConfigurationWeatherAPI from "@/views/dashboards/superUserDashboard/configuration/ConfigurationWeatherAPI.vue";
import ConfigurationGatewayAPI from "@/views/dashboards/superUserDashboard/configuration/ConfigurationGatewayAPI.vue"; import ConfigurationGatewayAPI from "@/views/dashboards/superUserDashboard/configuration/ConfigurationGatewayAPI.vue";
import UserWashSubscriptions from "@/views/dashboards/superUserDashboard/user/UserWashSubscriptions.vue"; import UserWashSubscriptions from "@/views/dashboards/superUserDashboard/user/UserWashSubscriptions.vue";
import UserProfile from "@/views/dashboards/userDashboard/profile/UserProfile.vue"; import UserProfile from "@/views/dashboards/userDashboard/profile/UserProfile.vue";
@@ -778,6 +779,12 @@ export const router = createRouter({
component: ConfigurationFXRatesAPI, component: ConfigurationFXRatesAPI,
meta: { middleware: superUserMiddleware } meta: { middleware: superUserMiddleware }
}, },
{
name: 'configurationWeatherAPI',
path: '/superuser/configuration/weatherapi',
component: ConfigurationWeatherAPI,
meta: { middleware: superUserMiddleware }
},
{ {
name: 'configurationGatewayAPI', name: 'configurationGatewayAPI',
path: '/superuser/configuration/gatewayapi', path: '/superuser/configuration/gatewayapi',
@@ -0,0 +1,135 @@
<script setup>
import PageTitle from "@/components/global/PageTitle.vue";
import RestrictedPageWrapper from "@/components/page/wrappers/RestrictedPageWrapper.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { ref } from 'vue';
import ConfigurationSubPageWrapper
from "@/views/dashboards/superUserDashboard/configuration/ConfigurationSubPageWrapper.vue";
import ConfigurationCategory from "@/components/displays/superuser/configuration/ConfigurationCategory.vue";
import ConfigurationSwitch from "@/components/displays/superuser/configuration/ConfigurationSwitch.vue";
import ConfigurationSecretKey from "@/components/displays/superuser/configuration/ConfigurationSecretKey.vue";
import Swal from "sweetalert2";
const module_config = ref([]);
const getModuleConfig = async () => {
await SessionUser.superUser.modules.weatherapi.config.get_all().then((response) => {
const tmp_module_config = response.data.data;
const tmp_module_config_array = [];
for (const value of Object.values(tmp_module_config)) {
tmp_module_config_array.push({
variable: value.variable,
value: value.value,
});
}
module_config.value = tmp_module_config_array;
}).catch((error) => {
console.log(error);
});
};
const getModuleConfigValue = (variable) => {
const config = module_config.value.find((entry) => entry.variable === variable);
return config ? config.value : '';
};
const load = async () => {
await getModuleConfig();
};
load();
const showCurrentWeather = () => {
Swal.fire({
title: "Lookup current weather",
input: "text",
inputValue: "Copenhagen",
inputLabel: "Location query",
inputPlaceholder: "e.g. Copenhagen, 10001, 55.6761,12.5683",
showCancelButton: true,
confirmButtonText: "Lookup",
showLoaderOnConfirm: true,
preConfirm: (query) => {
if (!query || query.trim() === "") {
Swal.showValidationMessage("A location query is required.");
return false;
}
return SessionUser.superUser.modules.weatherapi.functions.current(query.trim())
.then((response) => response.data.data)
.catch((error) => {
console.log(error);
Swal.showValidationMessage(`Request failed: ${error}`);
});
},
allowOutsideClick: () => !Swal.isLoading()
}).then((result) => {
if (!result.isConfirmed || !result.value) {
return;
}
Swal.fire({
title: "Current weather result",
html: "<pre>" + JSON.stringify(result.value, null, 2) + "</pre>",
width: "50rem",
confirmButtonText: "Close",
});
});
};
</script>
<template>
<RestrictedPageWrapper :hasPermission="SessionUser.canAccessSuperUser()">
<ConfigurationSubPageWrapper>
<template #title>
<PageTitle title="WeatherAPI configuration" subtitle="Configuration of the WeatherAPI integration" />
</template>
<template v-if="module_config.length > 0" #content>
<ConfigurationCategory
class="mt-2"
module="WeatherAPI"
title="General settings"
description="General settings for the WeatherAPI integration."
icon="fas fa-cloud-sun"
>
<ConfigurationSwitch
class="mt-2"
module="WeatherAPI"
title="Enable WeatherAPI"
description="Enable or disable the WeatherAPI integration."
icon="fas fa-cloud-sun"
:value="getModuleConfigValue('enabled') === true"
:on-switch="SessionUser.superUser.modules.weatherapi.config.enabled.set"
/>
</ConfigurationCategory>
<ConfigurationCategory
class="mt-2"
module="WeatherAPI"
title="API connection settings"
description="The API connection settings used for WeatherAPI requests."
icon="fas fa-key"
>
<ConfigurationSecretKey
class="mt-2"
module="WeatherAPI"
title="Secret key (API key)"
description="The secret key used to authenticate requests to WeatherAPI."
icon="fas fa-key"
:isSet="getModuleConfigValue('secret_key') !== ''"
:on-save="SessionUser.superUser.modules.weatherapi.config.keys.secret_key.set"
/>
</ConfigurationCategory>
<button class="button is-dark" @click="showCurrentWeather">
<span class="icon">
<i class="fas fa-cloud-sun"></i>
</span>
<span>Test current weather lookup</span>
</button>
</template>
</ConfigurationSubPageWrapper>
</RestrictedPageWrapper>
</template>
<style scoped>
</style>
@@ -15,6 +15,7 @@ const tabs = [
{ name: 'MotorAPI', path: '/superuser/configuration/motorapi' }, { name: 'MotorAPI', path: '/superuser/configuration/motorapi' },
{ name: 'Stripe', path: '/superuser/configuration/stripe' }, { name: 'Stripe', path: '/superuser/configuration/stripe' },
{ name: 'FXRatesAPI', path: '/superuser/configuration/fxratesapi' }, { name: 'FXRatesAPI', path: '/superuser/configuration/fxratesapi' },
{ name: 'WeatherAPI', path: '/superuser/configuration/weatherapi' },
{ name: 'GatewayAPI', path: '/superuser/configuration/gatewayapi' }, { name: 'GatewayAPI', path: '/superuser/configuration/gatewayapi' },
{ name: 'XLVask', path: '/superuser/configuration/xlvask' }, { name: 'XLVask', path: '/superuser/configuration/xlvask' },
{ name: 'Entra', path: '/superuser/configuration/entra'}, { name: 'Entra', path: '/superuser/configuration/entra'},
@@ -53,4 +54,4 @@ const changeTab = (index) => {
</template> </template>
<style scoped> <style scoped>
</style> </style>