Files
pleno-vue/src/views/dashboards/superUserDashboard/EdgeGatewaysWorkspacePage.vue
T

259 lines
8.0 KiB
Vue

<script setup>
import { computed, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import EdgeGatewayManager from "@/features/edgeGateways/EdgeGatewayManager.vue";
import EdgeGatewayFleetLanding from "@/features/edgeGateways/EdgeGatewayFleetLanding.vue";
import PageTitle from "@/components/global/PageTitle.vue";
import RestrictedPageWrapper from "@/components/page/wrappers/RestrictedPageWrapper.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import ConfigurationSubPageWrapper from "@/views/dashboards/superUserDashboard/configuration/ConfigurationSubPageWrapper.vue";
import { normalizeEdgeGatewayError } from "@/features/edgeGateways/edgeGatewayErrors.js";
const route = useRoute();
const router = useRouter();
const selectedGatewayId = computed(() => route.params.id ?? null);
const activeView = computed(() => {
if (route.name === "edgegatewaytasks") {
return "tasks";
}
if (route.name === "edgegatewaylogs") {
return "logs";
}
if (route.name === "edgegatewaystatistics") {
return "statistics";
}
if (route.name === "edgegatewayterminal") {
return "terminal";
}
if (route.name === "edgegatewayinventory") {
return "inventory";
}
if (route.name === "edgegatewaysettings") {
return "settings";
}
return "overview";
});
const moduleConfigEntries = ref([]);
const moduleConfigMap = ref({
enabled: true,
default_release_channel: "stable",
default_update_window: "02:00-04:00",
});
const moduleState = reactive({
loading: true,
saving: false,
unavailable: false,
error: null,
});
const canonicalPath = (gatewayId, view = "overview") =>
gatewayId
? `/superuser/configuration/edgegateway/${encodeURIComponent(String(gatewayId))}/${view}`
: "/superuser/configuration/edgegateway";
const handleNavigation = async ({ gatewayId, view }) => {
const nextPath = canonicalPath(gatewayId, view || "overview");
if (window.location.pathname === nextPath) {
return;
}
await router.push(nextPath);
};
const loadModuleConfig = async () => {
moduleState.loading = true;
moduleState.error = null;
moduleState.unavailable = false;
try {
const response = await SessionUser.superUser.modules.edgegateway.config.get_all();
moduleConfigEntries.value = response?.data?.data || [];
moduleConfigMap.value = {
enabled: response?.data?.config?.enabled !== false,
default_release_channel: response?.data?.config?.default_release_channel || "stable",
default_update_window: response?.data?.config?.default_update_window || "02:00-04:00",
};
} catch (error) {
moduleState.error = normalizeEdgeGatewayError(error);
moduleState.unavailable = Number(error?.response?.status) === 404;
} finally {
moduleState.loading = false;
}
};
const saveModuleConfig = async () => {
moduleState.saving = true;
moduleState.error = null;
try {
await SessionUser.superUser.modules.edgegateway.config.set_config({
enabled: Boolean(moduleConfigMap.value.enabled),
default_release_channel: moduleConfigMap.value.default_release_channel || "stable",
default_update_window: moduleConfigMap.value.default_update_window || "02:00-04:00",
});
await loadModuleConfig();
} catch (error) {
moduleState.error = normalizeEdgeGatewayError(error);
} finally {
moduleState.saving = false;
}
};
onMounted(async () => {
await loadModuleConfig();
});
</script>
<template>
<RestrictedPageWrapper :hasPermission="SessionUser.canAccessSuperUser()">
<ConfigurationSubPageWrapper>
<template #title>
<PageTitle
title="Edge gateway module"
subtitle="Module defaults, fleet state, rollout visibility, and gateway operations for the edge runtime"
/>
</template>
<template #content>
<section class="edge-gateway-module-page">
<article class="edge-gateway-module-page__panel" data-testid="edge-gateway-module-config">
<div class="edge-gateway-module-page__header">
<div>
<p class="edge-gateway-module-page__eyebrow">Module defaults</p>
<h2>Control-plane defaults</h2>
<p>These defaults are applied to newly claimed gateways when the module routes are enabled.</p>
</div>
</div>
<div v-if="moduleState.error" class="notification is-danger">
<strong>{{ moduleState.error.title }}</strong>
<p>{{ moduleState.error.message }}</p>
</div>
<div v-if="moduleState.unavailable" class="edge-gateway-module-page__disabled" data-testid="gateway-module-disabled-state">
The edge gateway module routes are currently unavailable. Re-enable the module before managing fleet traffic here.
</div>
<div v-else class="edge-gateway-module-page__grid">
<label class="edge-gateway-module-page__field">
<span>Enabled</span>
<input
v-model="moduleConfigMap.enabled"
data-testid="gateway-module-enabled"
type="checkbox"
/>
</label>
<label class="edge-gateway-module-page__field">
<span>Default release channel</span>
<div class="select is-fullwidth">
<select v-model="moduleConfigMap.default_release_channel" data-testid="gateway-module-release-channel">
<option value="stable">stable</option>
<option value="canary">canary</option>
</select>
</div>
</label>
<label class="edge-gateway-module-page__field">
<span>Default update window</span>
<input
v-model="moduleConfigMap.default_update_window"
class="input"
data-testid="gateway-module-update-window"
placeholder="02:00-04:00"
/>
</label>
</div>
<div class="edge-gateway-module-page__actions">
<button
class="button is-dark"
type="button"
data-testid="gateway-module-save"
:disabled="moduleState.loading || moduleState.saving || moduleState.unavailable"
@click="saveModuleConfig"
>
Save module defaults
</button>
</div>
</article>
<EdgeGatewayFleetLanding v-if="!moduleState.unavailable && !selectedGatewayId" />
<EdgeGatewayManager
v-if="!moduleState.unavailable"
:selected-gateway-id="selectedGatewayId"
:active-view="activeView"
:route-driven="true"
:allow-destructive="true"
@navigate="handleNavigation"
/>
</section>
</template>
</ConfigurationSubPageWrapper>
</RestrictedPageWrapper>
</template>
<style scoped>
.edge-gateway-module-page {
display: grid;
gap: 1rem;
}
.edge-gateway-module-page__panel {
border: 1px solid #e5e7eb;
border-radius: 20px;
background: #ffffff;
padding: 1rem;
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.07);
}
.edge-gateway-module-page__header h2 {
margin: 0 0 0.35rem;
color: #0f172a;
}
.edge-gateway-module-page__header p {
margin: 0;
color: #475569;
}
.edge-gateway-module-page__eyebrow {
margin: 0 0 0.35rem;
font-size: 0.78rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #64748b;
}
.edge-gateway-module-page__grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
margin-top: 1rem;
}
.edge-gateway-module-page__field {
display: grid;
gap: 0.45rem;
color: #334155;
}
.edge-gateway-module-page__actions {
margin-top: 1rem;
display: flex;
justify-content: flex-end;
}
.edge-gateway-module-page__disabled {
margin-top: 1rem;
padding: 1rem;
border-radius: 14px;
background: #fff7ed;
border: 1px solid #fdba74;
color: #9a3412;
}
</style>