Refactor database credentials and user permissions; update UI components for better usability and accessibility
- Changed default MariaDB password in docker-compose.yml from 'SE3112' to '3112'. - Added user creation and permissions for 'wago' in the MariaDB initialization script. - Updated unit options in App.jsx to include a digital/bool option. - Enhanced LoginScreen component to support inline display and improved error handling. - Improved point filtering logic in PointLibrary and adjusted default unit handling. - Refined authentication checks for dashboard and admin permissions. - Simplified API calls by removing unnecessary token checks. - Enhanced TrendChart component with better data handling and formatting. - Updated CSS styles for improved layout and responsiveness in trend views and sidebar.
This commit is contained in:
@@ -32,6 +32,25 @@ export function authenticateToken(req, res, next) {
|
||||
}
|
||||
}
|
||||
|
||||
export function authenticateTokenOptional(req, _res, next) {
|
||||
const header = req.headers.authorization || "";
|
||||
const token = header.startsWith("Bearer ") ? header.slice(7) : "";
|
||||
|
||||
if (!token) {
|
||||
req.user = null;
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
req.user = jwt.verify(token, config.jwtSecret);
|
||||
} catch {
|
||||
req.user = null;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
export function requireRoles(...roles) {
|
||||
return (req, res, next) => {
|
||||
if (!req.user || !roles.includes(req.user.role)) {
|
||||
|
||||
+29
-12
@@ -1,7 +1,7 @@
|
||||
import bcrypt from "bcryptjs";
|
||||
import cors from "cors";
|
||||
import express from "express";
|
||||
import { authenticateToken, createToken, requireRoles } from "./auth.js";
|
||||
import { authenticateToken, authenticateTokenOptional, createToken, requireRoles } from "./auth.js";
|
||||
import { config } from "./config.js";
|
||||
import { pool } from "./db.js";
|
||||
import {
|
||||
@@ -88,7 +88,7 @@ function assertPoints(points) {
|
||||
}
|
||||
|
||||
function canUseSelection(selection, user) {
|
||||
return Boolean(selection) && (selection.shared || selection.ownerId === user.sub || user.role === "admin");
|
||||
return Boolean(selection) && (selection.shared || (Boolean(user) && (selection.ownerId === user.sub || user.role === "admin")));
|
||||
}
|
||||
|
||||
function slugifyUsername(value) {
|
||||
@@ -147,6 +147,16 @@ async function serializeUserWithPermissions(row) {
|
||||
};
|
||||
}
|
||||
|
||||
const GUEST_VIEWER = {
|
||||
sub: "public",
|
||||
role: "viewer",
|
||||
name: "Gast",
|
||||
};
|
||||
|
||||
function resolveViewerUser(req) {
|
||||
return req.user || GUEST_VIEWER;
|
||||
}
|
||||
|
||||
function requirePermission(...permissions) {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
@@ -218,6 +228,12 @@ async function ensureUtf8Database() {
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureWagoApplicationUser() {
|
||||
await pool.query("CREATE USER IF NOT EXISTS 'wago'@'%' IDENTIFIED BY '3112'");
|
||||
await pool.query(`GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX ON \`${config.dbName}\`.* TO 'wago'@'%'`);
|
||||
await pool.query("FLUSH PRIVILEGES");
|
||||
}
|
||||
|
||||
async function ensureUserSchema() {
|
||||
await pool.query(
|
||||
`
|
||||
@@ -384,7 +400,7 @@ async function resolveIspPointCount(isp) {
|
||||
return await resolveTrendTablePointCount(pool, config.dbName, isp);
|
||||
}
|
||||
|
||||
app.get("/api/isps", authenticateToken, wrap(async (_req, res) => {
|
||||
app.get("/api/isps", authenticateTokenOptional, wrap(async (_req, res) => {
|
||||
const isps = await getAvailableIsps(pool, config.dbName);
|
||||
const response = await Promise.all(
|
||||
isps.map(async (isp) => {
|
||||
@@ -400,7 +416,7 @@ app.get("/api/isps", authenticateToken, wrap(async (_req, res) => {
|
||||
res.json({ isps: response });
|
||||
}));
|
||||
|
||||
app.get("/api/isps/:isp/aliases", authenticateToken, wrap(async (req, res) => {
|
||||
app.get("/api/isps/:isp/aliases", authenticateTokenOptional, wrap(async (req, res) => {
|
||||
const pointCount = await resolveIspPointCount(req.params.isp);
|
||||
const metadata = await ensureIspMetadata(pool, redis, req.params.isp, pointCount);
|
||||
res.json(metadata);
|
||||
@@ -694,20 +710,20 @@ app.delete("/api/selections/:selectionId", authenticateToken, requirePermission(
|
||||
res.status(204).send();
|
||||
}));
|
||||
|
||||
app.post("/api/trends/query", authenticateToken, requirePermission("view_trends"), wrap(async (req, res) => {
|
||||
const points = await resolveSelectionPoints(req.body, req.user);
|
||||
app.post("/api/trends/query", authenticateTokenOptional, wrap(async (req, res) => {
|
||||
const points = await resolveSelectionPoints(req.body, resolveViewerUser(req));
|
||||
const trendData = await fetchTrendSeries(pool, redis, points, normalizeRange(req.body.range));
|
||||
res.json(trendData);
|
||||
}));
|
||||
|
||||
app.post("/api/points/latest", authenticateToken, requirePermission("view_trends"), wrap(async (req, res) => {
|
||||
const points = await resolveSelectionPoints(req.body, req.user);
|
||||
app.post("/api/points/latest", authenticateTokenOptional, wrap(async (req, res) => {
|
||||
const points = await resolveSelectionPoints(req.body, resolveViewerUser(req));
|
||||
const values = await fetchLatestValues(pool, redis, points);
|
||||
res.json({ values });
|
||||
}));
|
||||
|
||||
app.get("/api/dashboard", authenticateToken, requirePermission("view_dashboard"), wrap(async (req, res) => {
|
||||
const dashboard = await getDashboard(redis, req.user.sub);
|
||||
app.get("/api/dashboard", authenticateTokenOptional, wrap(async (req, res) => {
|
||||
const dashboard = await getDashboard(redis, req.user?.sub || "public");
|
||||
res.json(dashboard);
|
||||
}));
|
||||
|
||||
@@ -716,12 +732,13 @@ app.put("/api/dashboard", authenticateToken, requirePermission("view_dashboard")
|
||||
res.json(dashboard);
|
||||
}));
|
||||
|
||||
app.post("/api/dashboard/data", authenticateToken, requirePermission("view_dashboard"), wrap(async (req, res) => {
|
||||
app.post("/api/dashboard/data", authenticateTokenOptional, wrap(async (req, res) => {
|
||||
const widgets = Array.isArray(req.body.widgets) ? req.body.widgets : [];
|
||||
const results = [];
|
||||
const viewer = resolveViewerUser(req);
|
||||
|
||||
for (const widget of widgets) {
|
||||
const points = widget.selectionId ? await resolveSelectionPoints({ selectionId: widget.selectionId }, req.user) : assertPoints(widget.points || []);
|
||||
const points = widget.selectionId ? await resolveSelectionPoints({ selectionId: widget.selectionId }, viewer) : assertPoints(widget.points || []);
|
||||
|
||||
if (widget.type === "chart") {
|
||||
const trendData = await fetchTrendSeries(pool, redis, points, normalizeRange(widget.range));
|
||||
|
||||
+14
-9
@@ -113,14 +113,19 @@ function createRandomPointColor(seed) {
|
||||
return hslToHex(hue, saturation, lightness);
|
||||
}
|
||||
|
||||
export function normalizeEngineeringUnit(value) {
|
||||
export function normalizeEngineeringUnit(value, kind = "analog") {
|
||||
const normalizedKind = kind === "digital" ? "digital" : "analog";
|
||||
const raw = String(value ?? "").trim();
|
||||
if (!raw) {
|
||||
return "\u00B0C";
|
||||
return normalizedKind === "digital" ? "bool" : "\u00B0C";
|
||||
}
|
||||
|
||||
if (normalizedKind === "digital" && /^(?:bool|boolean)$/i.test(raw)) {
|
||||
return "bool";
|
||||
}
|
||||
|
||||
if (/^\?C$/i.test(raw) || /^\u00B0\s*C$/i.test(raw) || (/[^\x00-\x7F]/.test(raw) && /C/i.test(raw))) {
|
||||
return "\u00B0C";
|
||||
return normalizedKind === "digital" ? "bool" : "\u00B0C";
|
||||
}
|
||||
|
||||
return raw;
|
||||
@@ -388,7 +393,7 @@ export async function ensureIspMetadata(pool, redis, isp, pointCount = LEGACY_PO
|
||||
parsed.points[pointKey] = {
|
||||
...defaultPoint,
|
||||
alias: initialAlias,
|
||||
unit: normalizeEngineeringUnit(sourceSeed?.unit || defaultPoint.unit),
|
||||
unit: normalizeEngineeringUnit(sourceSeed?.unit || defaultPoint.unit, sourceSeed?.kind || defaultPoint.kind),
|
||||
kind: sourceSeed?.kind || defaultPoint.kind,
|
||||
};
|
||||
changed = true;
|
||||
@@ -407,13 +412,13 @@ export async function ensureIspMetadata(pool, redis, isp, pointCount = LEGACY_PO
|
||||
point.kind = sourceSeed.kind;
|
||||
changed = true;
|
||||
}
|
||||
const normalizedUnit = normalizeEngineeringUnit(point.unit);
|
||||
const normalizedUnit = normalizeEngineeringUnit(point.unit, point.kind || sourceSeed?.kind || defaultPoint.kind);
|
||||
if (normalizedUnit !== point.unit) {
|
||||
point.unit = normalizedUnit;
|
||||
changed = true;
|
||||
}
|
||||
if ((!point.unit || point.unit === normalizeEngineeringUnit(defaultPoint.unit)) && sourceSeed?.unit) {
|
||||
const seededUnit = normalizeEngineeringUnit(sourceSeed.unit);
|
||||
if ((!point.unit || point.unit === normalizeEngineeringUnit(defaultPoint.unit, defaultPoint.kind)) && sourceSeed?.unit) {
|
||||
const seededUnit = normalizeEngineeringUnit(sourceSeed.unit, sourceSeed.kind || point.kind || defaultPoint.kind);
|
||||
if (point.unit !== seededUnit) {
|
||||
point.unit = seededUnit;
|
||||
changed = true;
|
||||
@@ -463,7 +468,7 @@ export async function ensureIspMetadata(pool, redis, isp, pointCount = LEGACY_PO
|
||||
metadata.points[pointKey] = {
|
||||
...metadata.points[pointKey],
|
||||
alias: sourceSeed.alias || sourceSeed.name || metadata.points[pointKey].alias,
|
||||
unit: normalizeEngineeringUnit(sourceSeed.unit || metadata.points[pointKey].unit),
|
||||
unit: normalizeEngineeringUnit(sourceSeed.unit || metadata.points[pointKey].unit, sourceSeed.kind || metadata.points[pointKey].kind),
|
||||
kind: sourceSeed.kind || metadata.points[pointKey].kind,
|
||||
};
|
||||
});
|
||||
@@ -506,7 +511,7 @@ export async function getPointDescriptors(pool, redis, points) {
|
||||
key: buildPointKey(isp, pointIndex),
|
||||
displayName: metadata.displayName,
|
||||
alias: pointMeta.alias,
|
||||
unit: normalizeEngineeringUnit(pointMeta.unit),
|
||||
unit: normalizeEngineeringUnit(pointMeta.unit, pointMeta.kind),
|
||||
factor: pointMeta.factor,
|
||||
kind: pointMeta.kind,
|
||||
min: pointMeta.min,
|
||||
|
||||
Reference in New Issue
Block a user