Compare commits

16 Commits

Author SHA1 Message Date
manuel.sowada
556ccc3a73 style fixes 2026-05-13 16:05:12 +02:00
manuel.sowada
4db4ac755c cleanup 2026-05-13 12:46:21 +02:00
manuel.sowada
63931bc4d5 style fixes 2026-05-12 15:58:46 +02:00
manuel.sowada
0a28a3e493 client online state 2026-05-12 08:06:46 +02:00
root
b34f912857 url change 2026-05-10 16:40:35 +02:00
2bd7cdd302 bugfixes 2026-05-08 14:19:11 +02:00
c848633a1f styles bugfix 2026-05-07 15:28:26 +02:00
e5ee067db4 add permission view 2026-05-05 14:49:34 +02:00
root
f09f148aea rbac and licenses 2026-05-01 22:37:21 +02:00
e208ef1759 add rbac roles 2026-05-01 10:50:12 +00:00
76a30fc94f rbac roles implementation 2026-04-30 14:22:12 +02:00
6679ed20fe rbac updated 2026-04-30 13:36:45 +02:00
bbd9441b31 rbac build 2026-04-29 15:44:20 +02:00
root
90497deebf rbac crud api 2026-04-29 06:14:06 +02:00
15f3a5f80d rbac crud 2026-04-28 16:24:21 +02:00
c98089e359 bugfix permissions 2026-04-28 15:35:20 +02:00
51 changed files with 3281 additions and 1547 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ stylesheet.json
radix_os_*.png radix_os_*.png
radix_os_icon.ico radix_os_icon.ico
login.hbs login.hbs
todo.txt

View File

@@ -1,5 +1,5 @@
DEMO IS RUNNING: DEMO IS RUNNING:
https://demo.sowadanas.dynv6.net/ https://demo.sowada.dev/
globalize: globalize:

View File

@@ -24,6 +24,7 @@ GO
-- DROP VIEW IF EXISTS dbo.vAuthentications; -- DROP VIEW IF EXISTS dbo.vAuthentications;
-- DROP VIEW IF EXISTS dbo.vEventLog; -- DROP VIEW IF EXISTS dbo.vEventLog;
-- DROP VIEW IF EXISTS dbo.vNotifyTray; -- DROP VIEW IF EXISTS dbo.vNotifyTray;
-- DROP VIEW IF EXISTS dbo.vPermissionOverview;s
-- DROP TABLE IF EXISTS dbo.AuthenticationRoles; -- DROP TABLE IF EXISTS dbo.AuthenticationRoles;
-- DROP TABLE IF EXISTS dbo.AuthenticationGroups; -- DROP TABLE IF EXISTS dbo.AuthenticationGroups;
@@ -44,26 +45,27 @@ GO
-- DROP TABLE IF EXISTS dbo.ObjectSource; -- DROP TABLE IF EXISTS dbo.ObjectSource;
-- DROP TABLE IF EXISTS dbo.AuthenticationUAC; -- DROP TABLE IF EXISTS dbo.AuthenticationUAC;
-- DROP TABLE IF EXISTS dbo.Vault; -- DROP TABLE IF EXISTS dbo.Vault;
GO -- GO
/* ========================================================= /* =========================================================
CORE TABLES CORE TABLES
========================================================= */ ========================================================= */
CREATE TABLE dbo.Vault ( CREATE TABLE dbo.Vault (
ID int IDENTITY(1,1) NOT NULL, ID INT IDENTITY(1,1) NOT NULL,
CustomerGUID uniqueidentifier NOT NULL, License_ID INT NOT NULL,
Feature nvarchar(128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, Customer_ID INT NOT NULL,
Payload nvarchar(MAX) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, Signature NVARCHAR(512) NOT NULL,
Signature nvarchar(MAX) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, EncryptedPayload VARBINARY(MAX) NOT NULL,
Active bit DEFAULT 1 NOT NULL, ExpiresAt DATETIME2 NULL,
ExpiresAt datetime NULL, Status_ID TINYINT NOT NULL,
CreatedAt datetime DEFAULT getdate() NOT NULL, LastVerifiedAt DATETIME2 NULL,
UpdatedAt datetime DEFAULT getdate() NULL, CreateDate DATETIME2 NOT NULL
CONSTRAINT PK__Vault__3214EC275180843D PRIMARY KEY (ID) CONSTRAINT DF_Vault_CreateDate DEFAULT SYSDATETIME(),
CONSTRAINT PK_Vault PRIMARY KEY (ID),
CONSTRAINT CK_Vault_Status CHECK (Status_ID IN (0,1,2,3,4))
); );
CREATE TABLE dbo.ObjectSource ( CREATE TABLE dbo.ObjectSource (
ID INT IDENTITY(1,1) PRIMARY KEY, ID INT IDENTITY(1,1) PRIMARY KEY,
Name VARCHAR(100) NOT NULL UNIQUE Name VARCHAR(100) NOT NULL UNIQUE
@@ -260,6 +262,9 @@ JOIN dbo.RolePermissions rp ON rp.Role_ID = r.Role_ID
JOIN dbo.Permission p ON p.ID = rp.Permission_ID; JOIN dbo.Permission p ON p.ID = rp.Permission_ID;
GO GO
/* ========================================================= /* =========================================================
FIXED vEventLog (SEQUELIZE MATCH + SYSTEM FIX) FIXED vEventLog (SEQUELIZE MATCH + SYSTEM FIX)
========================================================= */ ========================================================= */
@@ -364,14 +369,14 @@ INSERT INTO dbo.EventLevels VALUES
(4,'error','Error',1), (4,'error','Error',1),
(8,'throw_exception','Exception',0); (8,'throw_exception','Exception',0);
INSERT INTO dbo.Plugins VALUES ('SYSTEM',1,'1.0.0');
INSERT INTO dbo.[Role] (Name,Description,RoleType) INSERT INTO dbo.[Role] (Name,Description,RoleType)
VALUES ('ADMIN','System Administrator','SYSTEM'); VALUES ('ADMIN','System Administrators','SYSTEM');
INSERT INTO dbo.Permission (Scope,Resource,Action)
VALUES ('SYSTEM','ALL','ALL');
INSERT INTO dbo.Permission (Scope,Resource,Action) VALUES
('SYSTEM','ALL','ALL')
('SYSTEM','ALL','Default_Access')
INSERT INTO dbo.RolePermissions INSERT INTO dbo.RolePermissions
SELECT r.ID, p.ID SELECT r.ID, p.ID
@@ -380,6 +385,10 @@ JOIN dbo.Permission p ON p.Scope='SYSTEM'
WHERE r.Name='ADMIN'; WHERE r.Name='ADMIN';
INSERT INTO dbo.Group (ObjectGUID,Name,ObjectSource_ID) VALUES
('00000000-0000-0000-0000-000000000001','ADMINISTRATORS',1)
('00000000-0000-0000-0000-000000000002','USERS',1);
/* ========================================================= /* =========================================================
ADMIN USER ADMIN USER
========================================================= */ ========================================================= */
@@ -413,3 +422,232 @@ SELECT
ID ID
FROM dbo.[Role] FROM dbo.[Role]
WHERE Name='ADMIN'; WHERE Name='ADMIN';
GO
/* =========================================================
EXTENDED RBAC VIEWS
========================================================= */
-- ========================================================
-- 1. USER GROUPS (DIRECT + INHERITED)
-- ========================================================
CREATE OR ALTER VIEW dbo.vAuthenticationGroupsExpanded AS
SELECT
ag.Authentication_ObjectGUID,
g.ObjectGUID AS GroupGUID,
g.Name AS GroupName,
'DIRECT' AS Source
FROM dbo.AuthenticationGroups ag
JOIN dbo.[Group] g
ON g.ObjectGUID = ag.Group_ObjectGUID
UNION ALL
SELECT
ag.Authentication_ObjectGUID,
gc.ParentGroup_ObjectGUID,
g.Name,
'INHERITED'
FROM dbo.AuthenticationGroups ag
JOIN dbo.GroupClosure gc
ON gc.ChildGroup_ObjectGUID = ag.Group_ObjectGUID
JOIN dbo.[Group] g
ON g.ObjectGUID = gc.ParentGroup_ObjectGUID;
GO
-- ========================================================
-- 2. ROLES (DIRECT + GROUP + HIERARCHY)
-- ========================================================
CREATE OR ALTER VIEW dbo.vAuthenticationRolesExpanded AS
SELECT
ar.Authentication_ObjectGUID,
ar.Role_ID,
r.Name AS RoleName,
'DIRECT' AS Source
FROM dbo.AuthenticationRoles ar
JOIN dbo.[Role] r
ON r.ID = ar.Role_ID
UNION ALL
SELECT
ag.Authentication_ObjectGUID,
gr.Role_ID,
r.Name,
'GROUP'
FROM dbo.AuthenticationGroups ag
JOIN dbo.GroupRoles gr
ON gr.Group_ObjectGUID = ag.Group_ObjectGUID
JOIN dbo.[Role] r
ON r.ID = gr.Role_ID
UNION ALL
SELECT
ag.Authentication_ObjectGUID,
gr.Role_ID,
r.Name,
'GROUP_INHERITED'
FROM dbo.AuthenticationGroups ag
JOIN dbo.GroupClosure gc
ON gc.ChildGroup_ObjectGUID = ag.Group_ObjectGUID
JOIN dbo.GroupRoles gr
ON gr.Group_ObjectGUID = gc.ParentGroup_ObjectGUID
JOIN dbo.[Role] r
ON r.ID = gr.Role_ID;
GO
CREATE OR ALTER VIEW dbo.vRoleOverview AS
SELECT
r.ID,
r.Name,
r.Description,
r.RoleType,
ISNULL(g.GroupCount, 0) AS GroupCount,
ISNULL(u.UserCount, 0) AS UserCount
FROM dbo.Role r
-- 👥 Gruppen zählen
LEFT JOIN (
SELECT
Role_ID,
COUNT(DISTINCT Group_ObjectGUID) AS GroupCount
FROM dbo.GroupRoles
GROUP BY Role_ID
) g ON g.Role_ID = r.ID
-- 👤 NUR direkte User zählen (WICHTIG)
LEFT JOIN (
SELECT
Role_ID,
COUNT(DISTINCT Authentication_ObjectGUID) AS UserCount
FROM dbo.AuthenticationRoles
GROUP BY Role_ID
) u ON u.Role_ID = r.ID;
GO
-- ========================================================
-- 4. PERMISSIONS (DETAILED WITH ROLE SOURCE)
-- ========================================================
CREATE OR ALTER VIEW dbo.vAuthenticationPermissionsDetailed AS
SELECT
r.Authentication_ObjectGUID,
r.Role_ID,
r.RoleName,
p.Scope,
p.Resource,
p.Action,
CONCAT(p.Scope,'.',p.Resource,'.',p.Action) AS PermissionKey
FROM dbo.vAuthenticationRolesExpanded r
JOIN dbo.RolePermissions rp
ON rp.Role_ID = r.Role_ID
JOIN dbo.Permission p
ON p.ID = rp.Permission_ID;
GO
CREATE OR ALTER VIEW dbo.vPermissionOverview AS
SELECT p.ID AS Permission_ID, p.Scope, p.Resource, p.Action, p.Scope + '.' + p.Resource + '.' + p.Action AS PermissionKey, r.ID AS Role_ID, /* 🔥 NEU: Anzahl Rollen pro Permission*/ COUNT(r.ID) OVER (PARTITION BY p.ID)
AS RoleCount, COUNT(gr.Group_ObjectGUID) AS GroupCount, COUNT(ar.Authentication_ObjectGUID) AS DirectUserCount, COUNT(ag.Authentication_ObjectGUID) AS GroupUserCount,
COUNT(COALESCE (ar.Authentication_ObjectGUID, ag.Authentication_ObjectGUID)) AS TotalUserCount
FROM dbo.Permission AS p FULL JOIN
dbo.RolePermissions AS rp ON rp.Permission_ID = p.ID LEFT JOIN
dbo.Role AS r ON r.ID = rp.Role_ID LEFT JOIN
dbo.GroupRoles AS gr ON gr.Role_ID = r.ID LEFT JOIN
dbo.AuthenticationRoles AS ar ON ar.Role_ID = r.ID LEFT JOIN
dbo.AuthenticationGroups AS ag ON ag.Group_ObjectGUID = gr.Group_ObjectGUID
GROUP BY p.ID, p.Scope, p.Resource, p.Action, r.ID, r.Name;
GO
-- ========================================================
-- 5. PERMISSION MATRIX (FAST LOOKUP)
-- ========================================================
CREATE OR ALTER VIEW dbo.vPermissionMatrix AS
SELECT DISTINCT
Authentication_ObjectGUID,
CONCAT(Scope,'.',Resource,'.',Action) AS PermissionKey
FROM dbo.vAuthenticationPermissionsDetailed;
GO
-- ========================================================
-- 6. GROUP ROLES OVERVIEW
-- ========================================================
CREATE OR ALTER VIEW dbo.vGroupRolesDetailed AS
SELECT
g.ObjectGUID,
g.Name AS GroupName,
r.ID AS Role_ID,
r.Name AS RoleName
FROM dbo.GroupRoles gr
JOIN dbo.[Group] g
ON g.ObjectGUID = gr.Group_ObjectGUID
JOIN dbo.[Role] r
ON r.ID = gr.Role_ID;
GO
-- ========================================================
-- 7. GROUP HIERARCHY (READABLE)
-- ========================================================
CREATE OR ALTER VIEW dbo.vGroupHierarchyReadable AS
SELECT
parent.ObjectGUID AS ParentGroupGUID,
parent.Name AS ParentGroupName,
child.ObjectGUID AS ChildGroupGUID,
child.Name AS ChildGroupName,
gc.Depth
FROM dbo.GroupClosure gc
JOIN dbo.[Group] parent
ON parent.ObjectGUID = gc.ParentGroup_ObjectGUID
JOIN dbo.[Group] child
ON child.ObjectGUID = gc.ChildGroup_ObjectGUID;
GO
-- ========================================================
-- 8. USER OVERVIEW (ADMIN DASHBOARD)
-- ========================================================
CREATE OR ALTER VIEW dbo.vAuthenticationOverview AS
SELECT a.ObjectGUID, a.sAMAccountName, a.mail, a.givenName, a.sn, a.active, a.online, COUNT(DISTINCT r.Role_ID) AS RoleCount, COUNT(DISTINCT g.GroupGUID) AS GroupCount, a.title, a.department, a.streetAddress,
a.telephoneNumber, a.physicalDeliveryOfficeName, a.distinguishedName, dbo.ObjectSource.Name AS ObjectSourceName
FROM dbo.Authentication AS a LEFT OUTER JOIN
dbo.ObjectSource ON a.ObjectSource_ID = dbo.ObjectSource.ID LEFT OUTER JOIN
dbo.vAuthenticationRolesExpanded AS r ON r.Authentication_ObjectGUID = a.ObjectGUID LEFT OUTER JOIN
dbo.vAuthenticationGroupsExpanded AS g ON g.Authentication_ObjectGUID = a.ObjectGUID
GROUP BY a.ObjectGUID, a.sAMAccountName, a.mail, a.givenName, a.sn, a.active, a.online, a.title, a.department, a.streetAddress, a.telephoneNumber, a.physicalDeliveryOfficeName, a.distinguishedName, dbo.ObjectSource.Name
GO
-- ========================================================
-- 8.1. GROUP OVERVIEW (ADMIN DASHBOARD)
-- ========================================================
CREATE OR ALTER VIEW dbo.vGroupOverview AS
SELECT dbo.[Group].ObjectGUID, dbo.[Group].Name, COUNT(DISTINCT ag.Authentication_ObjectGUID) AS UserCount, COUNT(DISTINCT gr.Role_ID) AS RoleCount, dbo.ObjectSource.Name AS ObjectSourceName,
dbo.[Group].distinguishedName
FROM dbo.ObjectSource RIGHT OUTER JOIN
dbo.[Group] ON dbo.ObjectSource.ID = dbo.[Group].ObjectSource_ID LEFT OUTER JOIN
dbo.AuthenticationGroups AS ag ON dbo.[Group].ObjectGUID = ag.Group_ObjectGUID LEFT OUTER JOIN
dbo.GroupRoles AS gr ON dbo.[Group].ObjectGUID = gr.Group_ObjectGUID
GROUP BY dbo.[Group].ObjectGUID, dbo.[Group].Name, dbo.ObjectSource.Name, dbo.[Group].distinguishedName
GO
-- ========================================================
-- 9. BONUS: PERMISSION TRACE (WHY DOES USER HAVE THIS?)
-- ========================================================
CREATE OR ALTER VIEW dbo.vPermissionTrace AS
SELECT
apd.Authentication_ObjectGUID,
apd.RoleName,
apd.Scope,
apd.Resource,
apd.Action,
apd.PermissionKey
FROM dbo.vAuthenticationPermissionsDetailed apd;
GO

View File

@@ -1,621 +0,0 @@
/* =========================================================
DATABASE
========================================================= */
IF DB_ID('Radix_OS') IS NULL
BEGIN
CREATE DATABASE Radix_OS;
END
GO
USE Radix_OS;
GO
/* =========================================================
CLEAN RESET
========================================================= */
-- DROP VIEW IF EXISTS dbo.vAuthenticationEffectivePermissions;
-- DROP VIEW IF EXISTS dbo.vAuthenticationRoles;
-- DROP VIEW IF EXISTS dbo.vAuthenticationGroups;
-- DROP VIEW IF EXISTS dbo.vGroupHierarchy;
-- DROP VIEW IF EXISTS dbo.vAuthentications;
-- DROP VIEW IF EXISTS dbo.vEventLog;
-- DROP VIEW IF EXISTS dbo.vNotifyTray;
-- DROP TABLE IF EXISTS dbo.AuthenticationRoles;
-- DROP TABLE IF EXISTS dbo.AuthenticationGroups;
-- DROP TABLE IF EXISTS dbo.GroupRoles;
-- DROP TABLE IF EXISTS dbo.RolePermissions;
-- DROP TABLE IF EXISTS dbo.GroupClosure;
-- DROP TABLE IF EXISTS dbo.NotifyTray;
-- DROP TABLE IF EXISTS dbo.NotifyTrayObjects;
-- DROP TABLE IF EXISTS dbo.EventLog;
-- DROP TABLE IF EXISTS dbo.EventLevels;
-- DROP TABLE IF EXISTS dbo.Authentication;
-- DROP TABLE IF EXISTS dbo.[Group];
-- DROP TABLE IF EXISTS dbo.[Role];
-- DROP TABLE IF EXISTS dbo.Permission;
-- DROP TABLE IF EXISTS dbo.Plugins;
-- DROP TABLE IF EXISTS dbo.ObjectSource;
-- DROP TABLE IF EXISTS dbo.AuthenticationUAC;
-- DROP TABLE IF EXISTS dbo.Vault;
GO
/* =========================================================
CORE TABLES
========================================================= */
CREATE TABLE dbo.Vault (
ID int IDENTITY(1,1) NOT NULL,
CustomerGUID uniqueidentifier NOT NULL,
Feature nvarchar(128) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
Payload nvarchar(MAX) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
Signature nvarchar(MAX) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
Active bit DEFAULT 1 NOT NULL,
ExpiresAt datetime NULL,
CreatedAt datetime DEFAULT getdate() NOT NULL,
UpdatedAt datetime DEFAULT getdate() NULL,
CONSTRAINT PK__Vault__3214EC275180843D PRIMARY KEY (ID)
);
CREATE TABLE dbo.ObjectSource (
ID INT IDENTITY(1,1) PRIMARY KEY,
Name VARCHAR(100) NOT NULL UNIQUE
);
CREATE TABLE dbo.AuthenticationUAC (
ID INT PRIMARY KEY,
AttributeName NVARCHAR(100),
AttributeOriginal VARCHAR(255)
);
CREATE TABLE dbo.[Role] (
ID INT IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(255) UNIQUE,
Description NVARCHAR(MAX),
RoleType VARCHAR(50)
);
CREATE TABLE dbo.Permission (
ID INT IDENTITY(1,1) PRIMARY KEY,
Scope VARCHAR(100),
Resource VARCHAR(100),
Action VARCHAR(100),
CONSTRAINT UQ_Permission UNIQUE (Scope, Resource, Action)
);
CREATE TABLE dbo.Plugins (
Name VARCHAR(50) PRIMARY KEY,
Active BIT,
Version VARCHAR(25)
);
/* =========================================================
AUTHENTICATION
========================================================= */
CREATE TABLE dbo.Authentication (
ObjectGUID UNIQUEIDENTIFIER PRIMARY KEY,
sAMAccountName VARCHAR(255),
mail VARCHAR(255),
givenName VARCHAR(255),
sn VARCHAR(255),
employeeID VARCHAR(255),
title VARCHAR(255),
department VARCHAR(255),
streetAddress VARCHAR(255),
userAccountControl_ID INT,
telephoneNumber VARCHAR(255),
physicalDeliveryOfficeName VARCHAR(255),
distinguishedName VARCHAR(MAX),
password VARCHAR(MAX),
refreshtoken VARCHAR(MAX),
active BIT,
online BIT,
ObjectSource_ID INT,
FOREIGN KEY (ObjectSource_ID) REFERENCES dbo.ObjectSource(ID)
);
CREATE TABLE dbo.[Group] (
ObjectGUID UNIQUEIDENTIFIER PRIMARY KEY,
Name VARCHAR(255),
ObjectSource_ID INT,
distinguishedName VARCHAR(MAX),
FOREIGN KEY (ObjectSource_ID) REFERENCES dbo.ObjectSource(ID)
);
/* =========================================================
GROUP CLOSURE
========================================================= */
CREATE TABLE dbo.GroupClosure (
ParentGroup_ObjectGUID UNIQUEIDENTIFIER,
ChildGroup_ObjectGUID UNIQUEIDENTIFIER,
Depth INT,
PRIMARY KEY (ParentGroup_ObjectGUID, ChildGroup_ObjectGUID)
);
GO
/* =========================================================
RBAC
========================================================= */
CREATE TABLE dbo.AuthenticationRoles (
Authentication_ObjectGUID UNIQUEIDENTIFIER,
Role_ID INT,
PRIMARY KEY (Authentication_ObjectGUID, Role_ID)
);
GO
CREATE TABLE dbo.AuthenticationGroups (
Authentication_ObjectGUID UNIQUEIDENTIFIER,
Group_ObjectGUID UNIQUEIDENTIFIER,
PRIMARY KEY (Authentication_ObjectGUID, Group_ObjectGUID)
);
GO
CREATE TABLE dbo.GroupRoles (
Group_ObjectGUID UNIQUEIDENTIFIER,
Role_ID INT,
PRIMARY KEY (Group_ObjectGUID, Role_ID)
);
GO
CREATE TABLE dbo.RolePermissions (
Role_ID INT,
Permission_ID INT,
PRIMARY KEY (Role_ID, Permission_ID)
);
GO
/* =========================================================
EVENT SYSTEM
========================================================= */
CREATE TABLE dbo.EventLevels (
ID INT PRIMARY KEY,
LevelName VARCHAR(50),
DisplayName VARCHAR(150),
Priority INT
);
CREATE TABLE dbo.EventLog (
ID INT IDENTITY(1,1) PRIMARY KEY,
Message VARCHAR(MAX),
Trace VARCHAR(MAX),
Level_ID INT,
PluginName VARCHAR(50),
Date DATETIME2,
ObjectGUID UNIQUEIDENTIFIER
);
/* =========================================================
NOTIFY SYSTEM
========================================================= */
CREATE TABLE dbo.NotifyTrayObjects (
ID INT IDENTITY(1,1) PRIMARY KEY,
PluginName VARCHAR(50),
Message VARCHAR(MAX),
JSON VARCHAR(MAX),
ActionRequired BIT DEFAULT 0,
CreatedAt DATETIME2,
ExpiresAt DATETIME2
);
GO
CREATE TABLE dbo.NotifyTray (
ID INT IDENTITY(1,1) PRIMARY KEY,
ObjectGUID UNIQUEIDENTIFIER,
NotifyTrayObject_ID INT,
SeenAt DATETIME2
);
GO
/* =========================================================
SECURITY VIEWS
========================================================= */
CREATE VIEW dbo.vAuthenticationRoles AS
SELECT a.ObjectGUID, r.ID Role_ID, r.Name, 'DIRECT' Source
FROM dbo.Authentication a
JOIN dbo.AuthenticationRoles ar ON ar.Authentication_ObjectGUID = a.ObjectGUID
JOIN dbo.[Role] r ON r.ID = ar.Role_ID
UNION ALL
SELECT a.ObjectGUID, r.ID, r.Name, 'GROUP'
FROM dbo.Authentication a
JOIN dbo.AuthenticationGroups ag ON ag.Authentication_ObjectGUID = a.ObjectGUID
JOIN dbo.GroupRoles gr ON gr.Group_ObjectGUID = ag.Group_ObjectGUID
JOIN dbo.[Role] r ON r.ID = gr.Role_ID;
GO
CREATE VIEW dbo.vAuthenticationEffectivePermissions AS
SELECT DISTINCT
a.ObjectGUID,
p.Scope,
p.Resource,
p.Action,
CONCAT(p.Scope,'.',p.Resource,'.',p.Action) PermissionKey
FROM dbo.Authentication a
JOIN dbo.vAuthenticationRoles r ON r.ObjectGUID = a.ObjectGUID
JOIN dbo.RolePermissions rp ON rp.Role_ID = r.Role_ID
JOIN dbo.Permission p ON p.ID = rp.Permission_ID;
GO
/* =========================================================
FIXED vEventLog (SEQUELIZE MATCH + SYSTEM FIX)
========================================================= */
CREATE OR ALTER VIEW dbo.vEventLog
AS
SELECT
e.ID,
e.Message,
e.Trace,
e.Date,
e.Level_ID,
el.LevelName,
el.DisplayName AS LevelDisplayName,
el.Priority AS LevelPriority,
e.PluginName,
COALESCE(a.sn + ' ' + a.givenName, 'SYSTEM') AS ClearTextUser,
a.sn AS Surname,
a.givenName,
e.ObjectGUID,
a.sAMAccountName,
a.mail,
a.department,
a.telephoneNumber AS Phone,
a.physicalDeliveryOfficeName AS Office,
a.streetAddress AS Adress,
COALESCE(a.ObjectSource_ID, 1) AS ObjectSource_ID,
os.Name AS ObjectSourceName
FROM dbo.EventLog e
LEFT JOIN dbo.Authentication a ON a.ObjectGUID = e.ObjectGUID
LEFT JOIN dbo.EventLevels el ON el.ID = e.Level_ID
LEFT JOIN dbo.ObjectSource os ON os.ID = COALESCE(a.ObjectSource_ID, 1);
GO
/* =========================================================
AUTH VIEW
========================================================= */
CREATE VIEW dbo.vAuthentications AS
SELECT a.*, os.Name AS ObjectSource
FROM dbo.Authentication a
LEFT JOIN dbo.ObjectSource os ON os.ID = a.ObjectSource_ID;
GO
/* =========================================================
GROUP VIEW
========================================================= */
CREATE VIEW dbo.vGroupHierarchy AS
SELECT * FROM dbo.GroupClosure;
GO
/* =========================================================
NOTIFY VIEWS
========================================================= */
CREATE VIEW vNotifyTray AS
SELECT
n.ID,
n.ObjectGUID,
n.SeenAt,
a.sAMAccountName,
a.givenName,
a.sn,
a.mail,
a.active,
a.online,
nto.PluginName,
nto.JSON,
nto.ActionRequired,
nto.CreatedAt,
nto.Message
FROM NotifyTray n
LEFT JOIN Authentication a ON a.ObjectGUID = n.ObjectGUID
LEFT JOIN NotifyTrayObjects nto ON n.ID = n.NotifyTrayObject_ID
GO
/* =========================================================
SEED DATA
========================================================= */
INSERT INTO dbo.ObjectSource VALUES ('LOCAL'),('AD');
INSERT INTO dbo.EventLevels VALUES
(-1,'test','Test',5),
(0,'success','Success',4),
(1,'log','Log',3),
(2,'warn','Warn',2),
(4,'error','Error',1),
(8,'throw_exception','Exception',0);
INSERT INTO dbo.Plugins VALUES ('SYSTEM',1,'1.0.0');
INSERT INTO dbo.[Role] (Name,Description,RoleType)
VALUES ('ADMIN','System Administrator','SYSTEM');
INSERT INTO dbo.Permission (Scope,Resource,Action)
VALUES ('SYSTEM','ALL','ALL');
INSERT INTO dbo.RolePermissions
SELECT r.ID, p.ID
FROM dbo.[Role] r
JOIN dbo.Permission p ON p.Scope='SYSTEM'
WHERE r.Name='ADMIN';
/* =========================================================
ADMIN USER
========================================================= */
INSERT INTO dbo.Authentication (
ObjectGUID,
sAMAccountName,
mail,
givenName,
sn,
active,
online,
ObjectSource_ID
)
SELECT
'00000000-0000-0000-0000-000000000001',
'admin',
'admin@local',
'System',
'Admin',
1,
0,
ID
FROM dbo.ObjectSource
WHERE Name='LOCAL';
INSERT INTO dbo.AuthenticationRoles
SELECT
'00000000-0000-0000-0000-000000000001',
ID
FROM dbo.[Role]
WHERE Name='ADMIN';
GO
/* =========================================================
EXTENDED RBAC VIEWS
========================================================= */
-- ========================================================
-- 1. USER GROUPS (DIRECT + INHERITED)
-- ========================================================
CREATE OR ALTER VIEW dbo.vAuthenticationGroupsExpanded AS
SELECT
ag.Authentication_ObjectGUID,
g.ObjectGUID AS GroupGUID,
g.Name AS GroupName,
'DIRECT' AS Source
FROM dbo.AuthenticationGroups ag
JOIN dbo.[Group] g
ON g.ObjectGUID = ag.Group_ObjectGUID
UNION ALL
SELECT
ag.Authentication_ObjectGUID,
gc.ParentGroup_ObjectGUID,
g.Name,
'INHERITED'
FROM dbo.AuthenticationGroups ag
JOIN dbo.GroupClosure gc
ON gc.ChildGroup_ObjectGUID = ag.Group_ObjectGUID
JOIN dbo.[Group] g
ON g.ObjectGUID = gc.ParentGroup_ObjectGUID;
GO
-- ========================================================
-- 2. ROLES (DIRECT + GROUP + HIERARCHY)
-- ========================================================
CREATE OR ALTER VIEW dbo.vAuthenticationRolesExpanded AS
SELECT
ar.Authentication_ObjectGUID,
ar.Role_ID,
r.Name AS RoleName,
'DIRECT' AS Source
FROM dbo.AuthenticationRoles ar
JOIN dbo.[Role] r
ON r.ID = ar.Role_ID
UNION ALL
SELECT
ag.Authentication_ObjectGUID,
gr.Role_ID,
r.Name,
'GROUP'
FROM dbo.AuthenticationGroups ag
JOIN dbo.GroupRoles gr
ON gr.Group_ObjectGUID = ag.Group_ObjectGUID
JOIN dbo.[Role] r
ON r.ID = gr.Role_ID
UNION ALL
SELECT
ag.Authentication_ObjectGUID,
gr.Role_ID,
r.Name,
'GROUP_INHERITED'
FROM dbo.AuthenticationGroups ag
JOIN dbo.GroupClosure gc
ON gc.ChildGroup_ObjectGUID = ag.Group_ObjectGUID
JOIN dbo.GroupRoles gr
ON gr.Group_ObjectGUID = gc.ParentGroup_ObjectGUID
JOIN dbo.[Role] r
ON r.ID = gr.Role_ID;
GO
-- ========================================================
-- 3. EFFECTIVE ROLES (DEDUPLICATED)
-- ========================================================
CREATE OR ALTER VIEW dbo.vAuthenticationEffectiveRoles AS
SELECT DISTINCT
Authentication_ObjectGUID,
Role_ID,
RoleName
FROM dbo.vAuthenticationRolesExpanded;
GO
-- ========================================================
-- 4. PERMISSIONS (DETAILED WITH ROLE SOURCE)
-- ========================================================
CREATE OR ALTER VIEW dbo.vAuthenticationPermissionsDetailed AS
SELECT
r.Authentication_ObjectGUID,
r.Role_ID,
r.RoleName,
p.Scope,
p.Resource,
p.Action,
CONCAT(p.Scope,'.',p.Resource,'.',p.Action) AS PermissionKey
FROM dbo.vAuthenticationRolesExpanded r
JOIN dbo.RolePermissions rp
ON rp.Role_ID = r.Role_ID
JOIN dbo.Permission p
ON p.ID = rp.Permission_ID;
GO
-- ========================================================
-- 5. PERMISSION MATRIX (FAST LOOKUP)
-- ========================================================
CREATE OR ALTER VIEW dbo.vPermissionMatrix AS
SELECT DISTINCT
Authentication_ObjectGUID,
CONCAT(Scope,'.',Resource,'.',Action) AS PermissionKey
FROM dbo.vAuthenticationPermissionsDetailed;
GO
-- ========================================================
-- 6. GROUP ROLES OVERVIEW
-- ========================================================
CREATE OR ALTER VIEW dbo.vGroupRolesDetailed AS
SELECT
g.ObjectGUID,
g.Name AS GroupName,
r.ID AS Role_ID,
r.Name AS RoleName
FROM dbo.GroupRoles gr
JOIN dbo.[Group] g
ON g.ObjectGUID = gr.Group_ObjectGUID
JOIN dbo.[Role] r
ON r.ID = gr.Role_ID;
GO
-- ========================================================
-- 7. GROUP HIERARCHY (READABLE)
-- ========================================================
CREATE OR ALTER VIEW dbo.vGroupHierarchyReadable AS
SELECT
parent.ObjectGUID AS ParentGroupGUID,
parent.Name AS ParentGroupName,
child.ObjectGUID AS ChildGroupGUID,
child.Name AS ChildGroupName,
gc.Depth
FROM dbo.GroupClosure gc
JOIN dbo.[Group] parent
ON parent.ObjectGUID = gc.ParentGroup_ObjectGUID
JOIN dbo.[Group] child
ON child.ObjectGUID = gc.ChildGroup_ObjectGUID;
GO
-- ========================================================
-- 8. USER OVERVIEW (ADMIN DASHBOARD)
-- ========================================================
CREATE OR ALTER VIEW dbo.vAuthenticationOverview AS
SELECT
a.ObjectGUID,
a.sAMAccountName,
a.mail,
a.givenName,
a.sn,
a.active,
a.online,
COUNT(DISTINCT r.Role_ID) AS RoleCount,
COUNT(DISTINCT g.GroupGUID) AS GroupCount
FROM dbo.Authentication a
LEFT JOIN dbo.vAuthenticationRolesExpanded r
ON r.Authentication_ObjectGUID = a.ObjectGUID
LEFT JOIN dbo.vAuthenticationGroupsExpanded g
ON g.Authentication_ObjectGUID = a.ObjectGUID
GROUP BY
a.ObjectGUID,
a.sAMAccountName,
a.mail,
a.givenName,
a.sn,
a.active,
a.online;
GO
-- ========================================================
-- 9. BONUS: PERMISSION TRACE (WHY DOES USER HAVE THIS?)
-- ========================================================
CREATE OR ALTER VIEW dbo.vPermissionTrace AS
SELECT
apd.Authentication_ObjectGUID,
apd.RoleName,
apd.Scope,
apd.Resource,
apd.Action,
apd.PermissionKey
FROM dbo.vAuthenticationPermissionsDetailed apd;
GO

View File

@@ -14,6 +14,7 @@ module.exports = {
localPath, localPath,
cache: { cache: {
startMenuItems: [], startMenuItems: [],
clientsOnline: []
}, },
runtimeFile: { runtimeFile: {
package: new HotReload(path.join(localPath.root, 'package.json')), package: new HotReload(path.join(localPath.root, 'package.json')),

View File

@@ -1,4 +0,0 @@
© 2025 Grünflächenamt | Manuel Sowada
Diese Software ist ausschließlich für den internen dienstlichen Gebrauch durch Mitarbeiter des Grünflächenamtes vorgesehen.
Weitergabe, Veröffentlichung oder private Nutzung ist ohne ausdrückliche Genehmigung untersagt.

387
package-lock.json generated
View File

@@ -9,7 +9,6 @@
"version": "0.9", "version": "0.9",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"activedirectory2": "^2.2.0",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"body-parser": "^2.2.0", "body-parser": "^2.2.0",
"child_process": "^1.0.2", "child_process": "^1.0.2",
@@ -21,7 +20,7 @@
"fs-extra": "^11.3.2", "fs-extra": "^11.3.2",
"https": "^1.0.0", "https": "^1.0.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"ldapjs": "^3.0.7", "ldapts": "^8.1.7",
"module-alias": "^2.2.3", "module-alias": "^2.2.3",
"multer": "^2.0.2", "multer": "^2.0.2",
"net": "^1.0.2", "net": "^1.0.2",
@@ -298,92 +297,6 @@
"resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.6.5.tgz", "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.6.5.tgz",
"integrity": "sha512-3zwefSMwHpu8iVUW8YYz227sIv6UFqO31p1Bf1ZH/Vom7CmNyUsXjDBlnNzcuhmOL1XfxZ3nvND42kR23XlbcQ==" "integrity": "sha512-3zwefSMwHpu8iVUW8YYz227sIv6UFqO31p1Bf1ZH/Vom7CmNyUsXjDBlnNzcuhmOL1XfxZ3nvND42kR23XlbcQ=="
}, },
"node_modules/@ldapjs/asn1": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-2.0.0.tgz",
"integrity": "sha512-G9+DkEOirNgdPmD0I8nu57ygQJKOOgFEMKknEuQvIHbGLwP3ny1mY+OTUYLCbCaGJP4sox5eYgBJRuSUpnAddA==",
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md"
},
"node_modules/@ldapjs/attribute": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@ldapjs/attribute/-/attribute-1.0.0.tgz",
"integrity": "sha512-ptMl2d/5xJ0q+RgmnqOi3Zgwk/TMJYG7dYMC0Keko+yZU6n+oFM59MjQOUht5pxJeS4FWrImhu/LebX24vJNRQ==",
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
"dependencies": {
"@ldapjs/asn1": "2.0.0",
"@ldapjs/protocol": "^1.2.1",
"process-warning": "^2.1.0"
}
},
"node_modules/@ldapjs/change": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@ldapjs/change/-/change-1.0.0.tgz",
"integrity": "sha512-EOQNFH1RIku3M1s0OAJOzGfAohuFYXFY4s73wOhRm4KFGhmQQ7MChOh2YtYu9Kwgvuq1B0xKciXVzHCGkB5V+Q==",
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
"dependencies": {
"@ldapjs/asn1": "2.0.0",
"@ldapjs/attribute": "1.0.0"
}
},
"node_modules/@ldapjs/controls": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@ldapjs/controls/-/controls-2.1.0.tgz",
"integrity": "sha512-2pFdD1yRC9V9hXfAWvCCO2RRWK9OdIEcJIos/9cCVP9O4k72BY1bLDQQ4KpUoJnl4y/JoD4iFgM+YWT3IfITWw==",
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
"dependencies": {
"@ldapjs/asn1": "^1.2.0",
"@ldapjs/protocol": "^1.2.1"
}
},
"node_modules/@ldapjs/controls/node_modules/@ldapjs/asn1": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-1.2.0.tgz",
"integrity": "sha512-KX/qQJ2xxzvO2/WOvr1UdQ+8P5dVvuOLk/C9b1bIkXxZss8BaR28njXdPgFCpj5aHaf1t8PmuVnea+N9YG9YMw==",
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md"
},
"node_modules/@ldapjs/dn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@ldapjs/dn/-/dn-1.1.0.tgz",
"integrity": "sha512-R72zH5ZeBj/Fujf/yBu78YzpJjJXG46YHFo5E4W1EqfNpo1UsVPqdLrRMXeKIsJT3x9dJVIfR6OpzgINlKpi0A==",
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
"dependencies": {
"@ldapjs/asn1": "2.0.0",
"process-warning": "^2.1.0"
}
},
"node_modules/@ldapjs/filter": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@ldapjs/filter/-/filter-2.1.1.tgz",
"integrity": "sha512-TwPK5eEgNdUO1ABPBUQabcZ+h9heDORE4V9WNZqCtYLKc06+6+UAJ3IAbr0L0bYTnkkWC/JEQD2F+zAFsuikNw==",
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
"dependencies": {
"@ldapjs/asn1": "2.0.0",
"@ldapjs/protocol": "^1.2.1",
"process-warning": "^2.1.0"
}
},
"node_modules/@ldapjs/messages": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ldapjs/messages/-/messages-1.3.0.tgz",
"integrity": "sha512-K7xZpXJ21bj92jS35wtRbdcNrwmxAtPwy4myeh9duy/eR3xQKvikVycbdWVzkYEAVE5Ce520VXNOwCHjomjCZw==",
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
"dependencies": {
"@ldapjs/asn1": "^2.0.0",
"@ldapjs/attribute": "^1.0.0",
"@ldapjs/change": "^1.0.0",
"@ldapjs/controls": "^2.1.0",
"@ldapjs/dn": "^1.1.0",
"@ldapjs/filter": "^2.1.1",
"@ldapjs/protocol": "^1.2.1",
"process-warning": "^2.2.0"
}
},
"node_modules/@ldapjs/protocol": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@ldapjs/protocol/-/protocol-1.2.1.tgz",
"integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ==",
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md"
},
"node_modules/@socket.io/component-emitter": { "node_modules/@socket.io/component-emitter": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
@@ -455,11 +368,6 @@
"node": ">=6.5" "node": ">=6.5"
} }
}, },
"node_modules/abstract-logging": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
"integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="
},
"node_modules/accepts": { "node_modules/accepts": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@@ -472,40 +380,6 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/activedirectory2": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/activedirectory2/-/activedirectory2-2.2.0.tgz",
"integrity": "sha512-uGbw74xttFG6hgocU8T1a0oDofLsyTp44BPTn42JN5C2QlyO5kRl2E7ZoUdfpFzV+yxhaQTKI+8QqRB5HONYvA==",
"deprecated": "Decomissioned.",
"dependencies": {
"abstract-logging": "^2.0.0",
"async": "^3.1.0",
"ldapjs": "^2.3.3",
"merge-options": "^2.0.0"
},
"engines": {
"node": ">=4.0"
}
},
"node_modules/activedirectory2/node_modules/ldapjs": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.3.3.tgz",
"integrity": "sha512-75QiiLJV/PQqtpH+HGls44dXweviFwQ6SiIK27EqzKQ5jU/7UFrl2E5nLdQ3IYRBzJ/AVFJI66u0MZ0uofKYwg==",
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
"dependencies": {
"abstract-logging": "^2.0.0",
"asn1": "^0.2.4",
"assert-plus": "^1.0.0",
"backoff": "^2.5.0",
"ldap-filter": "^0.3.3",
"once": "^1.4.0",
"vasync": "^2.2.0",
"verror": "^1.8.1"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/agent-base": { "node_modules/agent-base": {
"version": "7.1.4", "version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@@ -541,38 +415,6 @@
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
}, },
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
},
"node_modules/backoff": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz",
"integrity": "sha512-wC5ihrnUXmR2douXmXLCe5O3zg3GKIyvRi/hi58a/XyRxVI+3/yM0PYueQOZXPXQ9pxBislYkw+sF9b7C/RuMA==",
"dependencies": {
"precond": "0.2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -885,11 +727,6 @@
"node": ">=6.6.0" "node": ">=6.6.0"
} }
}, },
"node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
},
"node_modules/cors": { "node_modules/cors": {
"version": "2.8.5", "version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@@ -1219,14 +1056,6 @@
"node": ">=22.15.0" "node": ">=22.15.0"
} }
}, },
"node_modules/extsprintf": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz",
"integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
"engines": [
"node >=0.6.0"
]
},
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
@@ -1558,14 +1387,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-plain-obj": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-promise": { "node_modules/is-promise": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
@@ -1660,37 +1481,15 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"node_modules/ldap-filter": { "node_modules/ldapts": {
"version": "0.3.3", "version": "8.1.7",
"resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.3.3.tgz", "resolved": "https://registry.npmjs.org/ldapts/-/ldapts-8.1.7.tgz",
"integrity": "sha512-/tFkx5WIn4HuO+6w9lsfxq4FN3O+fDZeO9Mek8dCD8rTUpqzRa766BOBO7BcGkn3X86m5+cBm1/2S/Shzz7gMg==", "integrity": "sha512-TJl6T92eIwMf/OJ0hDfKVa6ISwzo+lqCWCI5Mf//ARlKa3LKQZaSrme/H2rCRBhW0DZCQlrsV+fgoW5YHRNLUw==",
"dependencies": { "dependencies": {
"assert-plus": "^1.0.0" "strict-event-emitter-types": "2.0.0"
}, },
"engines": { "engines": {
"node": ">=0.8" "node": ">=20"
}
},
"node_modules/ldapjs": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-3.0.7.tgz",
"integrity": "sha512-1ky+WrN+4CFMuoekUOv7Y1037XWdjKpu0xAPwSP+9KdvmV9PG+qOKlssDV6a+U32apwxdD3is/BZcWOYzN30cg==",
"deprecated": "This package has been decomissioned. See https://github.com/ldapjs/node-ldapjs/blob/8ffd0bc9c149088a10ec4c1ec6a18450f76ad05d/README.md",
"dependencies": {
"@ldapjs/asn1": "^2.0.0",
"@ldapjs/attribute": "^1.0.0",
"@ldapjs/change": "^1.0.0",
"@ldapjs/controls": "^2.1.0",
"@ldapjs/dn": "^1.1.0",
"@ldapjs/filter": "^2.1.1",
"@ldapjs/messages": "^1.3.0",
"@ldapjs/protocol": "^1.2.1",
"abstract-logging": "^2.0.1",
"assert-plus": "^1.0.0",
"backoff": "^2.5.0",
"once": "^1.4.0",
"vasync": "^2.2.1",
"verror": "^1.10.1"
} }
}, },
"node_modules/lodash": { "node_modules/lodash": {
@@ -1769,17 +1568,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/merge-options": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-options/-/merge-options-2.0.0.tgz",
"integrity": "sha512-S7xYIeWHl2ZUKF7SDeBhGg6rfv5bKxVBdk95s/I7wVF8d+hjLSztJ/B271cnUiF6CAFduEQ5Zn3HYwAjT16DlQ==",
"dependencies": {
"is-plain-obj": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/mime-db": { "node_modules/mime-db": {
"version": "1.54.0", "version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
@@ -2102,23 +1890,6 @@
"node": "^20.17.0 || >=22.9.0" "node": "^20.17.0 || >=22.9.0"
} }
}, },
"node_modules/npm/node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"license": "MIT",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/npm/node_modules/@isaacs/brace-expansion": {
"version": "5.0.0",
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/npm/node_modules/@isaacs/fs-minipass": { "node_modules/npm/node_modules/@isaacs/fs-minipass": {
"version": "4.0.1", "version": "4.0.1",
"inBundle": true, "inBundle": true,
@@ -2637,14 +2408,6 @@
"node": ">=0.3.1" "node": ">=0.3.1"
} }
}, },
"node_modules/npm/node_modules/encoding": {
"version": "0.1.13",
"license": "MIT",
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
},
"node_modules/npm/node_modules/env-paths": { "node_modules/npm/node_modules/env-paths": {
"version": "2.2.1", "version": "2.2.1",
"inBundle": true, "inBundle": true,
@@ -2653,10 +2416,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/npm/node_modules/err-code": {
"version": "2.0.3",
"license": "MIT"
},
"node_modules/npm/node_modules/exponential-backoff": { "node_modules/npm/node_modules/exponential-backoff": {
"version": "3.1.3", "version": "3.1.3",
"inBundle": true, "inBundle": true,
@@ -2769,13 +2528,6 @@
"node": "^20.17.0 || >=22.9.0" "node": "^20.17.0 || >=22.9.0"
} }
}, },
"node_modules/npm/node_modules/imurmurhash": {
"version": "0.1.4",
"license": "MIT",
"engines": {
"node": ">=0.8.19"
}
},
"node_modules/npm/node_modules/ini": { "node_modules/npm/node_modules/ini": {
"version": "6.0.0", "version": "6.0.0",
"inBundle": true, "inBundle": true,
@@ -3448,17 +3200,6 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/npm/node_modules/promise-retry": {
"version": "2.0.1",
"license": "MIT",
"dependencies": {
"err-code": "^2.0.2",
"retry": "^0.12.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/npm/node_modules/promzard": { "node_modules/npm/node_modules/promzard": {
"version": "3.0.1", "version": "3.0.1",
"inBundle": true, "inBundle": true,
@@ -3496,13 +3237,6 @@
"node": "^20.17.0 || >=22.9.0" "node": "^20.17.0 || >=22.9.0"
} }
}, },
"node_modules/npm/node_modules/retry": {
"version": "0.12.0",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/npm/node_modules/safer-buffer": { "node_modules/npm/node_modules/safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
"inBundle": true, "inBundle": true,
@@ -3582,22 +3316,6 @@
"node": ">= 14" "node": ">= 14"
} }
}, },
"node_modules/npm/node_modules/spdx-correct": {
"version": "3.2.0",
"license": "Apache-2.0",
"dependencies": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
}
},
"node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": {
"version": "3.0.1",
"license": "MIT",
"dependencies": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
},
"node_modules/npm/node_modules/spdx-exceptions": { "node_modules/npm/node_modules/spdx-exceptions": {
"version": "2.5.0", "version": "2.5.0",
"inBundle": true, "inBundle": true,
@@ -3727,47 +3445,11 @@
"node": "^20.17.0 || >=22.9.0" "node": "^20.17.0 || >=22.9.0"
} }
}, },
"node_modules/npm/node_modules/unique-filename": {
"version": "5.0.0",
"license": "ISC",
"dependencies": {
"unique-slug": "^6.0.0"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/npm/node_modules/unique-slug": {
"version": "6.0.0",
"license": "ISC",
"dependencies": {
"imurmurhash": "^0.1.4"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/npm/node_modules/util-deprecate": { "node_modules/npm/node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"inBundle": true, "inBundle": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/npm/node_modules/validate-npm-package-license": {
"version": "3.0.4",
"license": "Apache-2.0",
"dependencies": {
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0"
}
},
"node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": {
"version": "3.0.1",
"license": "MIT",
"dependencies": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
},
"node_modules/npm/node_modules/validate-npm-package-name": { "node_modules/npm/node_modules/validate-npm-package-name": {
"version": "7.0.2", "version": "7.0.2",
"inBundle": true, "inBundle": true,
@@ -3960,14 +3642,6 @@
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==" "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="
}, },
"node_modules/precond": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
"integrity": "sha512-QCYG84SgGyGzqJ/vlMsxeXd/pgL/I94ixdNFyh1PusWmTCyVfPJjZ1K1jvHtsbfnXQs2TSkEP2fR7QiMZAnKFQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/process": { "node_modules/process": {
"version": "0.11.10", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -3976,11 +3650,6 @@
"node": ">= 0.6.0" "node": ">= 0.6.0"
} }
}, },
"node_modules/process-warning": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz",
"integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA=="
},
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -4508,6 +4177,11 @@
"node": ">=10.0.0" "node": ">=10.0.0"
} }
}, },
"node_modules/strict-event-emitter-types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz",
"integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA=="
},
"node_modules/string_decoder": { "node_modules/string_decoder": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -4735,43 +4409,6 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/vasync": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.1.tgz",
"integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==",
"engines": [
"node >=0.6.0"
],
"dependencies": {
"verror": "1.10.0"
}
},
"node_modules/vasync/node_modules/verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"engines": [
"node >=0.6.0"
],
"dependencies": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
},
"node_modules/verror": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",
"integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==",
"dependencies": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
},
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -21,7 +21,6 @@
"license": "UNLICENSED", "license": "UNLICENSED",
"licensefile": "license_internal.txt", "licensefile": "license_internal.txt",
"dependencies": { "dependencies": {
"activedirectory2": "^2.2.0",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"body-parser": "^2.2.0", "body-parser": "^2.2.0",
"child_process": "^1.0.2", "child_process": "^1.0.2",
@@ -33,7 +32,7 @@
"fs-extra": "^11.3.2", "fs-extra": "^11.3.2",
"https": "^1.0.0", "https": "^1.0.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"ldapjs": "^3.0.7", "ldapts": "^8.1.7",
"module-alias": "^2.2.3", "module-alias": "^2.2.3",
"multer": "^2.0.2", "multer": "^2.0.2",
"net": "^1.0.2", "net": "^1.0.2",

BIN
public/images/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
public/images/shield.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -140,6 +140,7 @@ class ContextMenu {
} }
show(target, options = {}) { show(target, options = {}) {
// this.hide();
this.closeAllSubmenus(); this.closeAllSubmenus();
let x, y; let x, y;

View File

@@ -147,25 +147,28 @@ function slideOutMessage(message) {
//#region Feedbox //#region Feedbox
/** /**
* feedbox({ feedbox({
* title: `<span style="color:#f44336">⚠ Upload abbrechen?</span>`, title: `<span style="color:#f44336">⚠ Upload abbrechen?</span>`,
* message: ` lock: true,
* <p>Es laufen noch <b>aktive Uploads</b>.</p> primary: 'yes',
* <p>Möchtest du wirklich <u>alle abbrechen</u>?</p> replace: false,
* `, message: `
* buttons: { <p>Es laufen noch <b>aktive Uploads</b>.</p>
* yes: { <p>Möchtest du wirklich <u>alle abbrechen</u>?</p>
* text: '<b>Ja</b>, abbrechen', `,
* onClick: () => stopUploadQueue() buttons: {
* }, yes: {
* no: { text: '<b>Ja</b>, abbrechen',
* text: 'Weiter hochladen' onClick: () => stopUploadQueue()
* }, },
* cancel: { no: {
* text: 'Zurück' text: 'Weiter hochladen'
* } },
* } cancel: {
*}); text: 'Zurück'
}
}
});
*/ */
function feedbox({ function feedbox({
title = '', title = '',

View File

@@ -1033,6 +1033,7 @@ function virtualTable({
}); });
// Live-Counter // Live-Counter
if(!filterConfig || filterConfig.hideCounter) return
if(filterState.counterEl) filterState.counterEl.textContent = `${visibleCount} Treffer`; if(filterState.counterEl) filterState.counterEl.textContent = `${visibleCount} Treffer`;
} }
@@ -1224,21 +1225,22 @@ window.addEventListener('resize', () => {
tableEl.before(container); tableEl.before(container);
// Höhe messen → thead offset setzen if(!filterConfig.hideCounter) {
requestAnimationFrame(()=>{ filterState.counterEl = document.createElement('div');
const h = container.getBoundingClientRect().height; filterState.counterEl.className = 'live-counter';
tableEl.style.setProperty('--filter-height', h + 'px'); container.appendChild(filterState.counterEl);
}); }
filterState.counterEl = document.createElement('div');
filterState.counterEl.className = 'live-counter';
container.appendChild(filterState.counterEl);
syncFilterWidth(container); syncFilterWidth(container);
const wrapperResizeObserver = new ResizeObserver(() => { const wrapperResizeObserver = new ResizeObserver(() => {
// Filter-Header anpassen // Filter-Header anpassen
syncFilterWidth(container); syncFilterWidth(container);
// Höhe messen → thead offset setzen
const h = container.getBoundingClientRect().height;
tableEl.style.setProperty('--filter-height', h + 'px');
// Tabelle neu rendern // Tabelle neu rendern
rebuildPrefix(); rebuildPrefix();
render(); render();
@@ -1483,7 +1485,7 @@ window.addEventListener('resize', () => {
}, },
refresh(){ applyFilters(); render(); }, refresh(){ applyFilters(); render(); },
clearData() { data = [] }, clearData() { data = [] },
source(newData) { data = []; this.addData(newData); }, source(newData) { data = []; this.addData(newData); this.refresh(); },
prepareData() { prepareData(); } prepareData() { prepareData(); }
}; };
} }

View File

@@ -11,19 +11,44 @@ const restoreIcon = '🗗';
const startBtn = document.getElementById('start-btn'); const startBtn = document.getElementById('start-btn');
const startMenu = document.getElementById('start-menu'); const startMenu = document.getElementById('start-menu');
const taskbar = document.getElementById('taskbar');
const windowsContainer = document.getElementById('windows'); const windowsContainer = document.getElementById('windows');
const taskbarWindows = document.getElementById('taskbar-windows'); const taskbarWindows = document.getElementById('taskbar-windows');
const ctx = new ContextMenu(); const ctx = new ContextMenu();
const windowCleanup = new Map(); const windowCleanup = new Map();
const username = getCookie('sAMAccountName'); const username = getCookie('sAMAccountName');
const LS_KEY = (key) => `${username}:${key}`; const LS_KEY = (key) => `${username}:${key}`;
const MAX_PADDING = { left: 4, top: 4, right: 4, bottom: (56 - 4) }; let MAX_PADDING = { left: 4, top: 4, right: 4, bottom: 40 };
startBtn.addEventListener('click', (evt) => { startBtn.addEventListener('click', (evt) => {
evt.stopPropagation(); // verhindert sofortiges Schließen evt.stopPropagation(); // verhindert sofortiges Schließen
startBtn.classList.toggle('active');
startMenu.classList.toggle('hidden'); startMenu.classList.toggle('hidden');
}); });
function updateStartMenuPosition() {
const height = taskbar.offsetHeight;
startMenu.style.bottom = (height + 12) + 'px';
}
const observer = new ResizeObserver(entries => {
for (let entry of entries) {
const height = entry.contentRect.height;
MAX_PADDING = { left: 4, top: 4, right: 4, bottom: height + 8 };
windowsContainer.querySelectorAll('[data-winid]').forEach(win => {
if(win.dataset.state === 'maximized') {
applyMaximized(win) ;
}
});
}
});
observer.observe(taskbar);
window.addEventListener('resize', updateStartMenuPosition);
window.addEventListener('load', updateStartMenuPosition);
// Launch app when clicking start menu item // Launch app when clicking start menu item
document.addEventListener('click', async (e) => { document.addEventListener('click', async (e) => {
const target = e.target.closest('.start-item'); const target = e.target.closest('.start-item');
@@ -31,6 +56,7 @@ document.addEventListener('click', async (e) => {
const clickedButton = startBtn.contains(e.target); const clickedButton = startBtn.contains(e.target);
if(!clickedInsideMenu && !clickedButton) { if(!clickedInsideMenu && !clickedButton) {
startBtn.classList.remove('active');
startMenu.classList.add('hidden'); startMenu.classList.add('hidden');
return; return;
} }

View File

@@ -0,0 +1,563 @@
const ctxRBAC = new ContextMenu();
//////////////////////////////
// 🌐 API LAYER
//////////////////////////////
const api = async (url, method = 'GET', body) => {
const res = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined
});
if(res.status >= 400) {
const text = await res.text();
sendUserEvent('RBAC', `Hoppla, da ist etwas schief gelaufen:\r\n${text}`, null, 4);
}
return res.json();
};
//////////////////////////////
// 🧠 RBAC SERVICE LAYER
//////////////////////////////
const RBAC = {
// 👤 USERS
loadUsers: async () => (await api('/api/rbac/auth/get', 'POST'))
.map(({ active, online, ...rest }) => ({
...rest,
Aktiv: active
}))
.sort((a, b) => a.sAMAccountName.localeCompare(b.sAMAccountName)),
createUser: (data) => api('/api/rbac/auth/create', 'POST', data),
syncUsersFromAD: () => api('/api/rbac/auth/syncFromAD', 'POST', { }),
deleteUser: (guid) => api(`/api/rbac/auth/${guid}`, 'DELETE'),
// 👥 GROUPS
loadGroups: () => api('/api/rbac/group/get', 'POST'),
createGroup: (name) => api('/api/rbac/group/create', 'POST', { name }),
syncGroupsFromAD: () => api('/api/rbac/group/syncFromAD', 'POST', { }),
deleteGroup: (guid) => api(`/api/rbac/group/${guid}`, 'DELETE'),
// 🎭 ROLES
loadRoles: () => api('/api/rbac/role/get', 'POST'),
createRole: (name) => api('/api/rbac/role/create', 'POST', { name }),
deleteRole: (id) => api(`/api/rbac/role/${id}`, 'DELETE'),
// 🎭 PERMISSIONS
loadPermissions: () => api('/api/rbac/permission/get', 'POST'),
createPermission: (data) => api('/api/rbac/permission/create', 'POST', data),
deletePermission: (id) => api(`/api/rbac/permission/${id}`, 'DELETE'),
// 🔗 ASSIGNMENTS
addUserToGroup: (authGuid, groupGuid) => api('/api/rbac/group/add-user', 'POST', { authGuid, groupGuid }),
addUserToRole: (authGuid, roleId) => api('/api/rbac/role/add-user', 'POST', { authGuid, roleId }),
addGroupToRole: (groupGuid, roleId) => api('/api/rbac/role/add-group', 'POST', { groupGuid, roleId }),
addPermissionToRole: (roleId, permissionId) => api('/api/rbac/role/add-permission', 'POST', { roleId, permissionId })
};
//////////////////////////////
// 🖱️ DRAG & DROP
//////////////////////////////
function createDragZone(el, data, type) {
el.draggable = true;
el.addEventListener('dragstart', (evt) => {
evt.dataTransfer.setData('application/json', JSON.stringify( { ...data, type }));
});
}
function createDropZone(el, type, target) {
const targetValue = target.ObjectGUID || target.ID || target.Role_ID || target.Permission_ID;
let process = { action: null, response: null, failure: false };
el.addEventListener('dragover', e => e.preventDefault());
el.addEventListener('drop', async (e) => {
e.preventDefault();
const data = JSON.parse(
e.dataTransfer.getData('application/json')
);
if(!targetValue || !data.type) {
sendUserEvent('RBAC', 'Deinem Drop-Ziel wurde kein ID-Attribut zugewiesen. Frag einfach Manuel ', null, 2);
return;
}
// NOCH NICHT UNTER ADMINROUTES EDITIERT: WENN BENUTZER/GRUPPE BEREITS IN ROLLE ENTHALTEN IST
switch (type) {
case 'group':
if(data.type === 'user') {
process.action = `Du hast den Benutzer der Gruppe hinzugefügt`;
process.response = await RBAC.addUserToGroup(data.ObjectGUID, targetValue);
}
break;
case 'role':
if(data.type === 'user') {
process.action = `Du hast den Benutzer der Rolle hinzugefügt`;
process.response = await RBAC.addUserToRole(data.ObjectGUID, targetValue);
break;
}
if(data.type === 'group') {
process.action = `Du hast die Gruppe der Rolle hinzugefügt`;
process.response = await RBAC.addGroupToRole(data.ObjectGUID, targetValue);
break;
}
case 'permission':
process.action = `Du hast die Berechtigung der Rolle hinzugefügt`;
process.response = await RBAC.addPermissionToRole(targetValue, data.ID);
break;
}
if(process.failure || !process.action) return;
loadUsers();
loadGroups();
loadRoles();
sendUserEvent('RBAC', process.action, null, 0);
});
}
//////////////////////////////
// 📋 TABLE (USERS)
//////////////////////////////
// const rbacUsersVT = virtualTable({
// tableEl: document.querySelector('#rbacUsersTable'),
// data: [],
// rowHeight: 35,
// buffer: 10,
// groupKey: 'ObjectSourceName',
// rowKey: 'ObjectGUID',
// filterConfig: {
// hideCounter: true,
// exceptedColumns: ['', 'Rollen', 'Gruppen'],
// columnModes: {
// Aktiv: 'dropdown',
// Online: 'dropdown'
// }
// },
// customRender: (row, tr) => {
// createDragZone(tr, row, 'user');
// tr.addEventListener('contextmenu', (evt) => {
// evt.preventDefault();
// ctxRBAC.setItems([
// {
// label: "Details",
// onClick: () => showAuthDetails(row.ObjectGUID)
// }
// ]);
// ctxRBAC.show(evt.pageX + 5, { y: evt.pageY + 5 });
// });
// createTd(tr,
// `<button class="redbutton"
// ${row['ObjectGUID'] === '00000000-0000-0000-0000-000000000001' ?
// 'disabled data-tooltip="Der Administrator kann nicht gelöscht werden"' :
// ''
// }>X</button>`, {
// styles: {
// 'position': 'sticky',
// 'left': '0px',
// 'max-width': '20px',
// 'z-index': '2'
// }, classes: [
// 'text-align:left'
// ], onclick: () => {
// if(row['ObjectGUID'] === '00000000-0000-0000-0000-000000000001') return;
// deleteUser(row['ObjectGUID'], row['sAMAccountName']);
// }
// });
// createTd(tr, row['ObjectGUID'], { classes: [ 'text-align:left' ], styles: { 'max-width': '100px' }, attributes: { 'data-tooltip': row['ObjectGUID'] } });
// createTd(tr, row['sAMAccountName'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['sAMAccountName'] } });
// createTd(tr, row['RoleCount'], { classes: [ 'text-align:center' ] });
// createTd(tr, row['GroupCount'], { classes: [ 'text-align:center' ] });
// createTd(tr, row['sn'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['sn'] } });
// createTd(tr, row['givenName'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['givenName'] } });
// createTd(tr, row['mail'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['mail'] } });
// createTd(tr, row['Aktiv'], { classes: [ 'text-align:center' ] });
// }
// });
//////////////////////////////
// 📋 TABLE (PERMISSIONS)
//////////////////////////////
// const rbacPermissionsVT = virtualTable({
// tableEl: document.querySelector('#rbacPermissionsTable'),
// data: [],
// rowHeight: 20,
// buffer: 5,
// groupKey: null,
// rowKey: 'ID',
// filterConfig: {
// hideCounter: true,
// exceptedColumns: ['', 'ID']
// },
// customRender: (row, tr) => {
// createDragZone(tr, row, 'user');
// tr.addEventListener('contextmenu', (evt) => {
// evt.preventDefault();
// ctxRBAC.setItems([
// {
// label: "Details",
// onClick: () => showAuthDetails(row.ObjectGUID)
// }
// ]);
// ctxRBAC.show(evt.pageX + 5, { y: evt.pageY + 5 });
// });
// createTd(tr,
// `<button class="redbutton"
// ${row['ID'] === 1 ?
// 'disabled data-tooltip="Die SYSTEM-Berechtigung kann nicht gelöscht werden"' :
// ''
// }>X</button>`, {
// styles: {
// 'position': 'sticky',
// 'left': '0px',
// 'max-width': '20px',
// 'z-index': '2'
// }, classes: [
// 'text-align:left'
// ], onclick: () => {
// if(row['ID'] === 1) return;
// deletePermission(row['ID'], `${row['Scope']}.${row['Resource']}.${row['Action']}`);
// }
// });
// createTd(tr, row['Permission_ID'], { classes: [ 'text-align:left' ], styles: { 'width': '100px' } } );
// createTd(tr, row['GroupUserCount'], { classes: [ 'text-align:center' ] });
// createTd(tr, row['TotalUserCount'], { classes: [ 'text-align:center' ] });
// createTd(tr, row['RoleCount'], { classes: [ 'text-align:center' ], styles: { 'width': '100px' } });
// createTd(tr, row['Scope'], { classes: [ 'text-align:right' ], styles: { 'width': '100px' } });
// createTd(tr, row['Resource'], { classes: [ 'text-align:center' ], styles: { 'width': '100px' } });
// createTd(tr, row['Action'], { classes: [ 'text-align:left' ], styles: { 'width': '100px' } });
// }
// });
class Tile {
constructor(title, parent, options = {}) {
this.title = title;
this.parent = parent;
this.options = options;
}
render() {
const tile = document.createElement('div');
tile.classList.add('tile');
tile.innerHTML = this.title;
if(this.options.onclick) {
tile.addEventListener('click', this.options.onclick);
}
this.parent.appendChild(tile);
}
}
//////////////////////////////
// 📥 LOADERS
//////////////////////////////
async function loadUsers() {
try {
// rbacUsersVT.source(await RBAC.loadUsers());
const users = await RBAC.loadUsers();
users.forEach(user => {
const tile = new Tile(`[${user.UserCount}] ${user.sAMAccountName}`, document.querySelector('#rbacTabContents') , { onclick: () => showAuthDetails(user.ObjectGUID) });
tile.render();
})
} catch (err) {
console.error(err);
}
}
async function loadPermissions() {
try {
rbacPermissionsVT.source(await RBAC.loadPermissions());
} catch (err) {
console.error(err);
}
}
async function loadGroups() {
const container = document.getElementById('rbacGroupContainer');
container.innerHTML = '';
const groups = await RBAC.loadGroups();
const fragment = document.createDocumentFragment();
groups.forEach(group => {
const section = document.createElement('section');
const groupName = document.createElement('span');
groupName.innerHTML = `[${group.UserCount}] ${group.Name}`;
groupName.dataset.tooltip = group.Name;
groupName.dataset.tooltipMode = 'ellipsis';
section.append(groupName);
const removeButton = document.createElement('button');
if(group.ObjectGUID === '00000000-0000-0000-0000-000000000001') {
removeButton.disabled = true;
removeButton.dataset.tooltip = `Die Gruppe ${group.Name} kann nicht gelöscht werden`;
}
removeButton.className = 'removeButton';
removeButton.innerHTML = 'X';
removeButton.onclick = async () => {
await deleteGroup(group.ObjectGUID, group.Name);
};
section.append(removeButton);
createDragZone(section, group, 'group');
createDropZone(section, 'group', group);
fragment.appendChild(section);
});
container.appendChild(fragment);
}
async function createGroup() {
const name = document.getElementById('newGroupName').value;
const group = await api('/api/rbac/group/create', 'POST', {
name
});
if(group) {
sendUserEvent('RBAC', `Gruppe ${name} angelegt`, null, 0);
loadGroups();
}
}
async function loadRoles() {
const container = document.getElementById('rbacRoleContainer');
container.innerHTML = '';
const roles = await RBAC.loadRoles();
const fragment = document.createDocumentFragment();
roles.forEach(role => {
const section = document.createElement('section');
const roleName = document.createElement('span');
roleName.innerHTML = `[${role.UserCount}][${role.GroupCount}] ${role.Name}`;
roleName.dataset.tooltip = role.Name;
section.append(roleName);
const removeButton = document.createElement('button');
removeButton.className = 'removeButton';
removeButton.innerHTML = 'X';
removeButton.onclick = async () => {
await deleteRole(role.ID, role.Name);
};
section.append(removeButton);
createDropZone(section, 'role', role);
fragment.appendChild(section);
});
container.appendChild(fragment);
}
//////////////////////////////
// 👤 USER ACTIONS
//////////////////////////////
async function createUser() {
const name = document.getElementById('newUserName').value;
if (!name || !name.includes('.')) return;
const [givenName, sn] = name.split('.');
const user = await RBAC.createUser({
sAMAccountName: name,
mail: `${name}@test.com`,
givenName: givenName[0].toUpperCase() + givenName.slice(1),
sn: sn[0].toUpperCase() + sn.slice(1)
});
sendUserEvent('RBAC', `Benutzer ${user.sAMAccountName} [${user.ObjectGUID}] angelegt`, null, 0);
loadUsers();
}
async function syncUsersFromAD() {
const users = await RBAC.syncUsersFromAD();
sendUserEvent('RBAC', `${users.length} Benutzer aus dem AD synchronisiert`, null, 0);
loadUsers();
}
async function deleteUser(guid, name) {
feedbox({
title: `<span>Benutzer löschen</span>`,
message: `Soll der Benutzer <b>${name}</b> und<br><b>alle seine Zugehörigkeiten</b> entfernt werden`,
buttons: {
no: { text: 'Nein' },
yes: {
text: '<b style=color:red>Ja</b>',
onClick: async () => {
const user = await RBAC.deleteUser(guid);
sendUserEvent('RBAC', `Benutzer ${user.sAMAccountName || ''} [${user.ObjectGUID}] gelöscht`, null, 0);
loadUsers();
loadGroups();
loadRoles();
loadPermissions();
}
}
}
});
}
//////////////////////////////
// 👥 GROUP ACTIONS
//////////////////////////////
async function deleteGroup(guid, name) {
feedbox({
title: `<span>Gruppe löschen</span>`,
message: `Möchtest du die Gruppe <b style="color:red;">${name} [${guid}]</b> wirklich löschen`,
buttons: {
no: { text: 'Nein' },
yes: {
text: '<b style=color:red>Ja</b>',
onClick: async () => {
const group = await RBAC.deleteGroup(guid);
sendUserEvent('RBAC', `Du hast die Gruppe ${name || ''} [${group.ObjectGUID}] gelöscht`, null, 0);
loadUsers();
loadGroups();
loadRoles();
loadPermissions();
}
}
}
});
}
async function syncGroupsFromAD() {
const group = await RBAC.syncGroupsFromAD();
sendUserEvent('RBAC', `${group.length} Gruppen aus dem AD synchronisiert`, null, 0);
loadGroups();
loadUsers();
}
//////////////////////////////
// 🎭 ROLE ACTIONS
//////////////////////////////
async function createRole() {
const name = document.getElementById('newRoleName').value;
if (!name) return;
const role = await RBAC.createRole(name);
sendUserEvent('RBAC', `Rolle ${name} angelegt`, null, 0);
loadRoles();
}
async function deleteRole(id, name) {
feedbox({
title: `<span>Rolle löschen</span>`,
message: `Möchtest du die Rolle <b style="color:red;">${name}</b> wirklich löschen`,
buttons: {
no: { text: 'Nein' },
yes: {
text: '<b style=color:red>Ja</b>',
onClick: async () => {
await RBAC.deleteRole(id);
sendUserEvent('RBAC', `Rolle ${name} gelöscht`, null, 0);
loadUsers();
loadGroups();
loadRoles();
loadPermissions(); // optional, falls RoleCount betroffen
}
}
}
});
}
//////////////////////////////
// 🎭 PERMISSION ACTIONS
//////////////////////////////
async function createPermission() {
const scope = document.getElementById('permScope').value;
const resource = document.getElementById('permResource').value;
const action = document.getElementById('permAction').value;
if (!scope || !resource || !action) return;
const permission = await RBAC.createPermission( { scope, resource, action } );
sendUserEvent('RBAC', `Berechtigung ${scope}.${resource}.${action} angelegt`, null, 0);
loadPermissions();
}
async function deletePermission(id, name) {
feedbox({
title: `<span>Berechtigung löschen</span>`,
message: `Möchtest du die Berechtigung <b style="color:red;">${name}</b> wirklich löschen`,
buttons: {
no: { text: 'Nein' },
yes: {
text: '<b style=color:red>Ja</b>',
onClick: async () => {
await RBAC.deletePermission(id);
sendUserEvent('RBAC', `Berechtigung ${name} gelöscht`, null, 0);
loadUsers();
loadGroups();
loadRoles();
loadPermissions();
}
}
}
});
}
//////////////////////////////
// 🚀 INIT
//////////////////////////////
loadUsers();
// loadGroups();
// loadRoles();
// loadPermissions();

View File

@@ -41,11 +41,11 @@ function writeEventLog(levelId, pluginName, message) {
// sendToParams: where clause to find objectGUIDs to send // sendToParams: where clause to find objectGUIDs to send
function sendUserEvent(pluginName, message, sendToParams, levelId = -1) { function sendUserEvent(pluginName, message, sendToParams, levelId = -1) {
mainSocket.emit('event', { mainSocket.emit('event', {
objectGuid: getCookie('ObjectGUID'), objectGuid: getCookie('ObjectGUID'),
levelId: levelId, levelId: levelId,
pluginName: pluginName, pluginName: pluginName,
message: message.stack === undefined ? message : { message: message.message }, message: message.stack === undefined ? message : { message: message.message },
sendToParams: sendToParams sendToParams: sendToParams
}); });
} }

View File

@@ -5,11 +5,11 @@
#tutorial-tooltip img {filter: invert(1);} #tutorial-tooltip img {filter: invert(1);}
#start-btn { background:var(--theme-accent-default-backcolor); color:var(--theme-accent-default-color); } #start-btn { background:var(--theme-accent-default-backcolor); color:var(--theme-accent-default-color); }
#start-btn.active { background:var(--theme-accent-active-backcolor); color:var(--theme-accent-active-color); }
#start-btn:hover { background:var(--theme-accent-hover-backcolor); color:var(--theme-accent-hover-color); } #start-btn:hover { background:var(--theme-accent-hover-backcolor); color:var(--theme-accent-hover-color); }
#start-btn:active { background:var(--theme-accent-active-backcolor); color:var(--theme-accent-active-color); }
/* #start-menu, .submenu { background:var(--theme-taskbar-backcolor); color:var(--theme-taskbar-color); } */ /* #start-menu, .submenu { background:var(--theme-taskbar-backcolor); color:var(--theme-taskbar-color); } */
#start-menu { padding-bottom:10px; background:var(--theme-startmenu-backcolor); color:var(--theme-startmenu-color); border:3px solid rgb(128,128,128); } #start-menu { padding-bottom:10px; background:var(--theme-startmenu-backcolor); color:var(--theme-startmenu-color); border:1px solid rgb(10,10,10); }
.start-submenu-head { background:var(--theme-startmenu-submenu-header-backcolor); color:var(--theme-startmenu-submenu-header-backcolor) } .start-submenu-head { background:var(--theme-startmenu-submenu-header-backcolor); color:var(--theme-startmenu-submenu-header-backcolor) }
.start-icon { background:var(--theme-accent-default-backcolor); } .start-icon { background:var(--theme-accent-default-backcolor); }
@@ -21,10 +21,11 @@
.start-item .unload { background:var(--theme-startmenu-item-disabled-backcolor); color:var(--theme-startmenu-item-disabled-color); } .start-item .unload { background:var(--theme-startmenu-item-disabled-backcolor); color:var(--theme-startmenu-item-disabled-color); }
/* #taskbar .taskbar-item { background:var(--theme-taskbar-item-backcolor); } */ /* #taskbar .taskbar-item { background:var(--theme-taskbar-item-backcolor); } */
.taskbar-item { position: relative;} .taskbar-item { position: relative; }
.taskbar-item::before { background: var(--theme-accent-active-color); } .taskbar-item::before { background: var(--theme-accent-active-backcolor); }
.taskbar-item.focus::before { background: var(--theme-accent-active-backcolor); } .taskbar-item.focus::before { background: var(--theme-accent-active-backcolor); }
/* .taskbar-item.minimized { background:var(--theme-taskbar-item-minimized-backcolor); color:var(--theme-taskbar-item-minimized-color); border-color:var(--theme-taskbar-item-minimized-border-color);} */ .taskbar-item.minimized::before { background: var(--theme-taskbar-item-minimized-backcolor); }
/* .taskbar-item.minimized { background:var(--theme-taskbar-item-minimized-backcolor); color:var(--theme-taskbar-item-minimized-color); border-color:var(--theme-taskbar-item-minimized-border-color);} */
.taskbar-item.default { background:var(--theme-taskbar-item-default-backcolor); color:var(--theme-taskbar-item-default-color); border-color:var(--theme-taskbar-item-default-border-color);} .taskbar-item.default { background:var(--theme-taskbar-item-default-backcolor); color:var(--theme-taskbar-item-default-color); border-color:var(--theme-taskbar-item-default-border-color);}
.taskbar-item:hover { background-color:var(--theme-startmenu-item-hover-backcolor); color:var(--theme-startmenu-item-hover-color); } .taskbar-item:hover { background-color:var(--theme-startmenu-item-hover-backcolor); color:var(--theme-startmenu-item-hover-color); }

View File

@@ -6,9 +6,10 @@
} }
.selectable { user-select: text !important; cursor: var(--theme-cursor-pointer) 0 16, pointer; } .selectable { user-select: text !important; cursor: var(--theme-cursor-default) 1 1, auto; }
input, input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="date"], textarea, select { border-width:2px; border-style:solid; font-size: var(--fontSize); font-family: var(--fontFamily); border-radius:10px; padding:10px 12px; outline:none; transition:all var(--times-transition-colors) ease; width:auto; } /* input, input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="date"], textarea, select { border-width:2px; border-style:solid; font-size: var(--fontSize); font-family: var(--fontFamily); border-radius:10px; padding:10px 12px; outline:none; transition:all var(--times-transition-colors) ease; width:auto; } */
input, input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="date"], textarea, select { border-width:2px; border-style:solid; font-size: var(--fontSize); font-family: var(--fontFamily); border-radius:10px; padding:4px 6px; outline:none; transition:all var(--times-transition-colors) ease; width:auto; }
input.width\:100px { width:100px; } input.width\:100px { width:100px; }
input.width\:90px { width:90px; } input.width\:90px { width:90px; }
input.width\:75px { width:75px; } input.width\:75px { width:75px; }
@@ -18,7 +19,9 @@ input.width\:25px { width:25px; }
*::placeholder, input[id="sAMAccountName"] { font-style:italic; font-weight:100; letter-spacing:3px; } *::placeholder, input[id="sAMAccountName"] { font-style:italic; font-weight:100; letter-spacing:3px; }
html, button { font-size: var(--fontSize); font-family: var(--fontFamily); } html, button { font-size: var(--fontSize); font-family: var(--fontFamily); }
button.monolyth, button.bluebutton, button.greenbutton, button.yellowbutton, button.redbutton { display:inline-block; padding:8px 10px; margin:0.2rem 1.6rem; font-weight:600; text-align:center; text-decoration:none; color:rgb(255, 255, 255); border:none; border-radius:8px; box-shadow:0 4px 6px rgba(0,0,0,0.1); transition:all var(--times-transition-colors) ease; } /* button.monolyth, button.bluebutton, button.greenbutton, button.yellowbutton, button.redbutton { display:inline-block; padding:8px 10px; margin:0.2rem 1.6rem; font-weight:600; text-align:center; text-decoration:none; color:rgb(255, 255, 255); border:none; border-radius:8px; box-shadow:0 4px 6px rgba(0,0,0,0.1); transition:all var(--times-transition-colors) ease; } */
button.monolyth, button.bluebutton, button.greenbutton, button.yellowbutton, button.redbutton { display:inline-block; padding:4px 8px; margin:0px 5px; font-weight:600; text-align:center; text-decoration:none; color:rgb(255, 255, 255); border:none; border-radius:8px; box-shadow:0 4px 6px rgba(0,0,0,0.1); transition:all var(--times-transition-colors) ease; }
button.monolyth { background-color:transparent; } button.monolyth { background-color:transparent; }
button:not(:disabled).monolyth:hover { opacity:0.9; } button:not(:disabled).monolyth:hover { opacity:0.9; }
button.bluebutton { color:var(--theme-button-blue-default-color); background:var(--theme-button-blue-default-backcolor); } button.bluebutton { color:var(--theme-button-blue-default-color); background:var(--theme-button-blue-default-backcolor); }
@@ -35,14 +38,14 @@ button:not(:disabled).yellowbutton:hover { background:var(--theme-button-yellow-
/* #region Container */ /* #region Container */
.container.static { width:calc(100% - 20px); margin:10px auto; display:flex; gap:12px; min-height:0; overflow:auto; max-height:100%; flex-direction: column;} .container.static { width:calc(100% - 10px); margin:5px auto; display:flex; gap:6px; min-height:0; overflow:auto; max-height:100vh; flex-direction: column;}
/* .card.static { display:flex; flex-direction:column;flex: 0 0 auto; } */ /* .card.static { display:flex; flex-direction:column;flex: 0 0 auto; } */
.card.static.row { overflow:hidden; display:flex; flex-direction:row; flex-wrap: wrap;} .card.static.row { overflow:hidden; display:flex; flex-direction:row; flex-wrap: wrap;}
.card.static { overflow:hidden; display:flex; flex-direction:column; } .card.static { overflow:hidden; display:flex; flex-direction:column; }
.container { width:calc(100% - 20px); margin:10px auto; display:grid; grid-template-columns:100%; gap:12px; min-height:0; overflow:auto; max-height:100%; } .container:not(.static) { width:calc(100% - 10px); margin:5px auto; display:grid; grid-template-columns:100%; gap:12px; min-height:0; overflow:auto; max-height:100%; }
.container:not(.static) * { box-sizing:border-box; } .container:not(.static) * { box-sizing:border-box; }
.card { border-width:1px; border-style:solid; border-radius:8px; padding:20px; } .card { border-width:1px; border-style:solid; border-radius:8px; padding:10px; }
.grid { display:grid; gap:16px; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); } .grid { display:grid; gap:16px; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); }
/* #endregion */ /* #endregion */
@@ -93,114 +96,16 @@ button:not(:disabled).yellowbutton:hover { background:var(--theme-button-yellow-
<span class="cb-label">Ein / Aus</span> <span class="cb-label">Ein / Aus</span>
</label> </label>
*/ */
.cb-switch { .cb-switch input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
--w: var(--responsive-switch-width); .switch-track { width: var(--responsive-switch-width); height: var(--responsive-switch-height); border-radius: 999px; padding: 3px; border: 1px solid; box-sizing: border-box; display: flex; align-items: center; position: relative; flex-shrink: 0; transition: background var(--times-transition-colors) ease, border-color var(--times-transition-colors) ease; }
--h: var(--responsive-switch-height); .switch-thumb { position: absolute; top: 50%; left: 3px; width: calc(var(--responsive-switch-height) - 6px); height: calc(var(--responsive-switch-height) - 6px); background: var(--theme-switch-thumb); border-radius: 50%; box-shadow: 0 2px 6px rgba(0,0,0,.15); transform: translateY(-50%);transition: left var(--times-transition-transform) cubic-bezier(.2,.9,.2,1), background var(--times-transition-colors) ease; }
.cb-switch input:checked + .switch-track { background: var(--theme-switch-active); }
display: inline-flex; .cb-switch input:checked + .switch-track .switch-thumb { left: calc(var(--responsive-switch-width) - var(--responsive-switch-height) + 3px); }
align-items: center; .cb-switch input:not(:disabled):hover + .switch-track { border-color: var(--theme-switch-hover); }
flex-shrink: 0; .cb-switch input:focus-visible + .switch-track { box-shadow: 0 0 0 6px rgba(6, 193, 103, 0.12); }
gap: 8px; .cb-switch input:disabled + .switch-track { background-color: dimgray; opacity: 0.6; }
}
/* Input verstecken */
.cb-switch input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
pointer-events: none;
}
/* TRACK */
.switch-track {
width: var(--w);
height: var(--h);
border-radius: 999px;
padding: 3px;
border: 1px solid;
box-sizing: border-box;
display: flex;
align-items: center;
position: relative;
flex-shrink: 0;
transition: background var(--times-transition-colors) ease, border-color var(--times-transition-colors) ease;
}
/* THUMB */
.switch-thumb {
position: absolute;
top: 50%;
left: 3px;
width: calc(var(--h) - 6px);
height: calc(var(--h) - 6px);
background: var(--theme-switch-thumb);
border-radius: 50%;
box-shadow: 0 2px 6px rgba(0,0,0,.15);
transform: translateY(-50%);
transition: left var(--times-transition-transform) cubic-bezier(.2,.9,.2,1), background var(--times-transition-colors) ease;
}
/* CHECKED STATE */
.cb-switch input:checked + .switch-track {
background: var(--theme-switch-active);
}
.cb-switch input:checked + .switch-track .switch-thumb {
left: calc(var(--w) - var(--h) + 3px);
}
/* HOVER */
.cb-switch input:not(:disabled):hover + .switch-track {
border-color: var(--theme-switch-hover);
}
/* FOCUS */
.cb-switch input:focus-visible + .switch-track {
box-shadow: 0 0 0 6px rgba(6, 193, 103, 0.12);
}
/* DISABLED */
.cb-switch input:disabled + .switch-track {
background-color: dimgray;
opacity: 0.6;
}
.cb-switch input:disabled + .switch-track .switch-thumb {
background-color: rgb(54, 50, 50);
}
/* optional label spacing */
.cb-switch label {
cursor: pointer;
}
/* .cb-switch { --w:45px; --h:27px; display:inline-flex; align-items:center; }
.cb-switch input { position:absolute; opacity:0; width:0; height:0; pointer-events:none; }
.switch-track { width:var(--w); height:var(--h); border-radius:999px; padding:3px; border-width:1px; border-style:solid; box-sizing:border-box; display:inline-flex; align-items:center; transition:background .18s ease, transform .12s ease, border-color .25s ease; }
.switch-thumb { min-width:calc(var(--h) - 2 * 3px); width:calc(var(--h) - 2 * 3px); height:calc(var(--h) - 2 * 3px); background:var(--theme-switch-thumb); border-radius:50%; box-shadow:0 2px 6px rgba(0,0,0,.15); transform:translateX(-1px); transition:transform .25s cubic-bezier(.2,.9,.2,1), background .18s ease }
.cb-switch input:disabled + .switch-track { background-color: dimgray;}
.cb-switch input:disabled + .switch-track .switch-thumb { background-color: rgb(54, 50, 50); } .cb-switch input:disabled + .switch-track .switch-thumb { background-color: rgb(54, 50, 50); }
.cb-switch label { cursor: pointer; }
.cb-switch input:not(:disabled):checked + .switch-track { background:var(--theme-switch-active); }
.cb-switch input:not(:disabled):hover + .switch-track { border-color:var(--theme-switch-hover); }
.cb-switch input:focus-visible + .switch-track { box-shadow:0 0 0 6px rgba(6,193,103,0.12); }
.cb-switch input:checked + .switch-track .switch-thumb { transform:translateX(calc(var(--w) - var(--h))); }
.cb-switch label { width: calc(100% - var(--w)); } */
/* #endregion */
/* #region CheckBox */ /* #region CheckBox */
/* /*
@@ -209,13 +114,12 @@ button:not(:disabled).yellowbutton:hover { background:var(--theme-button-yellow-
<span class="cb-box" aria-hidden="true"></span> <span class="cb-box" aria-hidden="true"></span>
</label> </label>
*/ */
.cb { display:inline-flex; align-items:center; gap:10px; user-select:none; transform:translateY(2px); } .cb { display:inline-grid;grid-auto-flow:column;align-items:center;gap:10px;user-select:none;vertical-align:middle; }
.cb input { position:absolute; opacity:0; width:0; height:0; pointer-events:none; } .cb input { position:absolute;opacity:0;width:0;height:0;pointer-events:none; }
.cb-box { width:20px; height:20px; border-radius:6px; background:var(--theme-checkbox-backcolor); border-width:2px; border-style:solid; display:inline-grid; place-items:center; transition:transform var(--times-transition-transform) ease, border-color var(--times-transition-colors) ease, background var(--times-transition-colors) ease; } .cb-box { width:20px;height:20px;border-radius:6px;background:var(--theme-checkbox-backcolor);border:2px solid;display:grid;place-items:center;transition:transform var(--times-transition-transform) ease,border-color var(--times-transition-colors) ease,background var(--times-transition-colors) ease;flex-shrink:0; }
.cb-box::after { content:""; width:12px; height:8px; transform:scale(0) translateY(-2px); background-repeat:no-repeat; background-position:center; background-size:contain; background-image:url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 4L4 7l7-7' stroke='%23fff' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); transition:transform var(--times-transition-transform) cubic-bezier(.2,.9,.2,1); } .cb-box::after { content:"";width:10px;height:6px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg)scale(0);transform-origin:center;transition:transform var(--times-transition-transform)cubic-bezier(.2,.9,.2,1); }
.cb input:checked + .cb-box::after { transform: rotate(-45deg) scale(1) translate(1px, -1px); }
.cb input:checked + .cb-box { transform:translateY(-1px); } .cb input:checked + .cb-box { transform:translateY(-1px); }
.cb input:checked + .cb-box::after { transform:scale(1) translate(-1px, 1px); }
table .cb input:checked + .cb-box::after { transform:scale(1) translate(1px, 1px); }
.cb input:focus-visible + .cb-box { outline:none; } .cb input:focus-visible + .cb-box { outline:none; }
/* #endregion */ /* #endregion */
@@ -226,8 +130,8 @@ table .cb input:checked + .cb-box::after { transform:scale(1) translate(1px, 1px
/* #region Tabs */ /* #region Tabs */
.tabs { display: flex; margin-bottom: 10px; border-bottom-width: 2px; border-bottom-style: solid; } .tabs { display: flex; flex:1 0 auto; margin-bottom: 5px; border-bottom-width: 2px; border-bottom-style: solid; overflow-x: auto; scrollbar-width: thin;}
.tab { padding: 10px 20px; border: 1px solid transparent; border-top-left-radius: 5px; border-top-right-radius: 5px; margin-right: 5px; transition: background .25s, color .25s, border-color .25s; } .tab { padding: 5px 10px; text-wrap: nowrap; border: 1px solid transparent; border-top-left-radius: 5px; border-top-right-radius: 5px; margin-right: 5px; transition: background .25s, color .25s, border-color .25s; }
.tab-content { border-width: 1px; border-style: solid; border-radius: 5px; padding: 15px; } .tab-content { border-width: 1px; border-style: solid; border-radius: 5px; padding: 15px; }
.item { margin-bottom: 5px; } .item { margin-bottom: 5px; }
/* #endregion */ /* #endregion */
@@ -235,7 +139,7 @@ table .cb input:checked + .cb-box::after { transform:scale(1) translate(1px, 1px
/* #region Feebox */ /* #region Feebox */
.feedbox-overlay { position:fixed; top:0; left:0; width:100vw; height:100vh; display:flex; align-items:center; justify-content:center; z-index:999; } .feedbox-overlay { position:fixed; top:0; left:0; width:100vw; height:100vh; display:flex; align-items:center; justify-content:center; z-index:999; }
.feedbox { border-radius:8px; max-width:50vw; width:100%; padding:20px; animation:feedboxFadeIn 0.2s ease-out; max-height:80vh; display:flex; flex-direction:column; overflow:hidden; } .feedbox { border-radius:8px; max-width:50vw; max-width:90vw; padding:20px; animation:feedboxFadeIn 0.2s ease-out; max-height:80vh; display:flex; flex-direction:column; overflow:hidden; }
.feedbox h3 { margin:0 0 10px 0; } .feedbox h3 { margin:0 0 10px 0; }
.feedbox-message { margin-bottom:20px; line-height:1.4; flex:1; overflow-y: auto; /* font-size:1rem; */ } .feedbox-message { margin-bottom:20px; line-height:1.4; flex:1; overflow-y: auto; /* font-size:1rem; */ }
.feedbox-actions { display:flex; justify-content:flex-end; gap:10px; flex-wrap:wrap; flex-shrink:0; } .feedbox-actions { display:flex; justify-content:flex-end; gap:10px; flex-wrap:wrap; flex-shrink:0; }
@@ -406,14 +310,23 @@ label { color:var(--muted); display:block; margin-bottom:6px; }
border-bottom:1px solid #eee; border-bottom:1px solid #eee;
} }
button.removeButton { .removeButton {
border:none; border:none;
background:none; background:#d11a2a;
color:#d11a2a; color:white;
cursor:var(--theme-cursor-pointer) -16 16, pointer; cursor:var(--theme-cursor-pointer) -16 16, pointer;
/* font-size:14px; */ padding: 0 4px;
border-radius: 47%;
transition: background var(--times-transition-colors) ease, color var(--times-transition-colors) ease;
} }
.removeButton:hover {
background:#ec5d4d;
color:white;
}
input[type="file"] { input[type="file"] {
display:none; display:none;
} }

View File

@@ -32,11 +32,11 @@ body, html { margin:0; padding:0; height:100%; overflow: hidden; font-family: va
.window-resize-sw { bottom: -6px;left: -6px;cursor: var(--theme-cursor-resize-45); } .window-resize-sw { bottom: -6px;left: -6px;cursor: var(--theme-cursor-resize-45); }
#taskbar { z-index: 2; position: absolute; width:100%; bottom:0; left:0; height:auto; overflow:visible; display:flex; flex: 0 0 auto; min-width:0; align-items:center; padding:0 8px; box-sizing:border-box; } #taskbar { z-index: 2; position: absolute; width:100%; bottom:0; left:0; height:auto; overflow:visible; display:flex; flex: 0 0 auto; min-width:0; align-items:center; padding:0 8px; box-sizing:border-box; }
#start-btn { transition: background-color var(--times-transition-colors) ease; padding: 8px 12px; border-radius: 5px; border: none; margin-right:8px; } #start-btn { transition: background-color var(--times-transition-colors) ease; padding: 5px 12px; border-radius: 5px; border: none; margin-right:8px; }
#taskbar-windows { display:flex; gap:6px; align-items:center; flex:1; overflow-y:hidden;overflow-x: auto; min-width: 0;scrollbar-width: thin; } #taskbar-windows { display:flex; gap:6px; align-items:center; flex:1; overflow-y:hidden;overflow-x: auto; min-width: 0;scrollbar-width: thin; }
.taskbar-item { display: flex; position: relative; padding:4px 10px; border-radius:4px; } .taskbar-item { display: flex; position: relative; padding:4px 10px; border-radius:4px; }
.taskbar-item::before { content: ''; position: absolute; bottom:1px; left:50%; width:40%; height: 4px; border-radius:4px; transform:translateX(-50%) scaleX(0); transform-origin:center; transition:transform var(--times-transition-colors) ease; } .taskbar-item::before { content: ''; position: absolute; bottom:1px; left:50%; width:40%; height: 4px; border-radius:4px; transform:translateX(-50%) scaleX(0); transform-origin:center; transition:transform var(--times-transition-colors) ease; }
.taskbar-item.focus::before { transform: translateX(-50%) scaleX(1); } .taskbar-item.minimized::before, .taskbar-item.focus::before { transform: translateX(-50%) scaleX(1); }
.notify-button { margin-left:auto; flex: 0 0 auto; display: flex; align-items: center; justify-content: center; } .notify-button { margin-left:auto; flex: 0 0 auto; display: flex; align-items: center; justify-content: center; }
.notify-button.resume, .notify-button.pulse { animation: pulse 1.5s infinite; animation-play-state: running; } .notify-button.resume, .notify-button.pulse { animation: pulse 1.5s infinite; animation-play-state: running; }
@@ -59,7 +59,7 @@ body, html { margin:0; padding:0; height:100%; overflow: hidden; font-family: va
.hidden { opacity: 0; pointer-events: none; } .hidden { opacity: 0; pointer-events: none; }
/* Open submenu vertical */ /* Open submenu vertical */
#start-menu { z-index: 2; transition: opacity var(--times-transition-opacity); display:flex; flex-direction:column; position: absolute; bottom: 50px; left: 8px; width: auto; min-width: 300px; border-radius: 8px; overflow: hidden; } #start-menu { z-index: 2; transition: opacity var(--times-transition-opacity); display:flex; flex-direction:column; position: absolute; bottom: auto; left: 8px; width: auto; min-width: 300px; border-radius: 8px; overflow: hidden; }
.start-header { display: flex; align-items: center; padding: 8px; font-weight: 600; } .start-header { display: flex; align-items: center; padding: 8px; font-weight: 600; }
.start-header > img { height: 24px; width: 24px; margin-right: 5px; } .start-header > img { height: 24px; width: 24px; margin-right: 5px; }
.start-list { list-style: none; margin: 0; padding: 8px 0; height: 60vh; overflow-y: auto; } .start-list { list-style: none; margin: 0; padding: 8px 0; height: 60vh; overflow-y: auto; }
@@ -70,7 +70,7 @@ body, html { margin:0; padding:0; height:100%; overflow: hidden; font-family: va
.start-item.has-submenu { position: relative; display: flex; flex-direction: column; padding-bottom: 8px; } .start-item.has-submenu { position: relative; display: flex; flex-direction: column; padding-bottom: 8px; }
.start-item.has-submenu > .submenu { width: 100%; list-style: none; padding-left: 2px; max-height: 0; overflow: hidden; transition: max-height var(--times-transition-transform) ease; margin: 2px 0 0 8px; } .start-item.has-submenu > .submenu { width: 100%; list-style: none; padding-left: 2px; max-height: 0; overflow: hidden; transition: max-height var(--times-transition-transform) ease; margin: 2px 0 0 8px; }
.start-item-sys-container { position: relative; left:0; bottom: -8px; padding: 5px 0px; width:100%; display:flex; height:30px; flex-direction:row; justify-content:flex-end; } .start-item-sys-container { position: relative; left:0; bottom: -10px; padding: 5px 0px; width:100%; display:flex; height:30px; flex-direction:row; justify-content:flex-end; }
.start-sys-item { margin: 0 10px 0 8px !important; } .start-sys-item { margin: 0 10px 0 8px !important; }
.start-submenu-head { position: relative; margin-left: 16px; height:32px; display:flex; flex-direction: row; align-items: center; gap:8px; } .start-submenu-head { position: relative; margin-left: 16px; height:32px; display:flex; flex-direction: row; align-items: center; gap:8px; }
@@ -80,7 +80,6 @@ body, html { margin:0; padding:0; height:100%; overflow: hidden; font-family: va
.start-item.has-submenu > .submenu li { opacity: 0; transform: translateY(-5px); transition: opacity var(--times-transition-opacity), transform 0.2s; } .start-item.has-submenu > .submenu li { opacity: 0; transform: translateY(-5px); transition: opacity var(--times-transition-opacity), transform 0.2s; }
.start-item.has-submenu.open > .submenu li { opacity: 1; transform: translateY(0); } .start-item.has-submenu.open > .submenu li { opacity: 1; transform: translateY(0); }
img.icon { width: auto; height:20px; object-fit: contain; filter: var(--theme-notifybubble-filter); transform: translate(2px, 2px) } img.icon { width: auto; height:20px; object-fit: contain; filter: var(--theme-notifybubble-filter); transform: translate(2px, 2px) }
@@ -123,6 +122,7 @@ img.icon { width: auto; height:20px; object-fit: contain; filter: var(--theme-no
#taskbar { #taskbar {
padding: 0 6px; padding: 0 6px;
overflow: hidden; overflow: hidden;
z-index: 9999;
} }
#start-btn { #start-btn {

View File

@@ -22,7 +22,7 @@ table thead { position:sticky; top:0; z-index:20; }
/* #endregion */ /* #endregion */
/* echte Tabelle */ /* echte Tabelle */
table { width:calc(100%); border-spacing:0 5px; } table { width:calc(100%); border-spacing:0 2px; }
table th, table td { min-width:100px; max-width:250px; overflow:hidden; white-space:nowrap; } table th, table td { min-width:100px; max-width:250px; overflow:hidden; white-space:nowrap; }
table tr.grouprow:hover { background: rgba(0,0,0,0.05);} table tr.grouprow:hover { background: rgba(0,0,0,0.05);}
@@ -35,6 +35,7 @@ thead, tbody { display:table-row-group; }
table thead th { padding:5px; } table thead th { padding:5px; }
/* table tbody td { padding:0 5px; } */
/* table tbody td { padding:5px 0px 5px 20px; } */ /* table tbody td { padding:5px 0px 5px 20px; } */
table tbody td:not(:first-child):not(:last-child), table thead th:not(:first-child):not(:last-child) { border-width:0; border-style:solid; } table tbody td:not(:first-child):not(:last-child), table thead th:not(:first-child):not(:last-child) { border-width:0; border-style:solid; }
table tbody tr.grouprow { font-weight:700; } table tbody tr.grouprow { font-weight:700; }
@@ -46,23 +47,25 @@ table .text-align\:left { text-align:left; }
td { overflow:hidden; text-overflow:ellipsis; /* verhindert, dass Inhalt die Zelle sprengt */ } td { overflow:hidden; text-overflow:ellipsis; /* verhindert, dass Inhalt die Zelle sprengt */ }
.table-filter-container { .table-filter-container {
border-bottom-width:8px; border-bottom-width:1px;
border-bottom-style:solid; border-bottom-style:solid;
display:flex; display:flex;
justify-content:flex-start;
flex-direction:column; flex-direction:column;
flex-wrap:wrap; gap:0;
gap:10px;
position:sticky; position:sticky;
left:0px; left:0px;
top:0px; top:0px;
width: 100% !important;
/* z-index:20; */ /* z-index:20; */
padding:5px 10px; padding:0px;
border-radius:var(--border-raduis) var(--border-raduis) 0 0; border-radius:var(--border-raduis) var(--border-raduis) 0 0;
justify-content: flex-start;
flex: 1;
flex-wrap: wrap;
overflow-x:auto;
} }
.table-filter-container .live-counter { position:absolute; right:18px; margin-left:auto; font-weight:bold; } .table-filter-container .live-counter { position:static; text-align: left ; padding-left:10px; font-style: italic; }
.table-filter-container input, .table-filter-container select { padding:5px !important; } .table-filter-container input { padding:5px !important; ; margin:5px 0;}
th.sort-asc::after { th.sort-asc::after {

View File

@@ -6,7 +6,7 @@
min-width: 200px; min-width: 200px;
max-height: 50vh; max-height: 50vh;
width: max-content; /* 🔑 wächst mit Inhalt */ width: max-content; /* 🔑 wächst mit Inhalt */
max-width: 600px; /* aber capped */ max-width: 500px; /* aber capped */
background: var(--theme-taskbar-tray-backcolor); background: var(--theme-taskbar-tray-backcolor);
color: var(--theme-taskbar-tray-color); color: var(--theme-taskbar-tray-color);
border-color: var(--theme-taskbar-tray-border-color); border-color: var(--theme-taskbar-tray-border-color);
@@ -14,7 +14,7 @@
overflow-y: auto; /* vertikal scrollen */ overflow-y: auto; /* vertikal scrollen */
overflow-x: hidden; /* horizontal verhindern */ overflow-x: hidden; /* horizontal verhindern */
box-shadow: 0 8px 20px rgba(0,0,0,0.4); box-shadow: 0 8px 20px rgba(0,0,0,0.4);
padding: 10px 10px 6px 10px; padding: 6px 6px 0px 6px;
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
transform: translateY(10px); transform: translateY(10px);
@@ -28,8 +28,8 @@
.bubble-item { .bubble-item {
display: grid; display: grid;
grid-template-columns: minmax(0, auto) 250px; grid-template-columns: minmax(0, auto) 250px;
gap: 10px; gap: 5px;
padding: 6px 8px; padding: 3px 6px;
border-radius: 6px; border-radius: 6px;
/* cursor: pointer; */ /* cursor: pointer; */
align-items: center; align-items: center;
@@ -55,7 +55,9 @@
/* hover Effekt */ /* hover Effekt */
.bubble-item:hover { .bubble-item:hover {
background: rgba(64,64,64,0.4); background: var(--theme-accent-hover-backcolor);
color: var(--theme-accent-hover-color);
border-color: var(--theme-accent-hover-border-color);
transform: scale(1.01); transform: scale(1.01);
} }

View File

@@ -36,7 +36,7 @@
{{#each items}} {{#each items}}
{{#ifSingle this.menu.items}} {{#ifSingle this.menu.items}}
{{#if this.authorized}} {{#if this.authorized}}
<li class="start-item {{#unless ../this.active}}unload{{/unless}}" data-active="{{#equaler ../this.active "&&" this.authorized}}true{{else}}false{{/equaler}}" data-appname="{{../this.name}}" data-appview="{{this.view}}" data-viewlabel="{{this.label}}"> <li class="start-item {{#unless ../this.active}}unload{{/unless}}" {{#if ../this.description}}data-tooltip="{{../this.description}}"{{/if}} data-active="{{#equaler ../this.active "&&" this.authorized}}true{{else}}false{{/equaler}}" data-appname="{{../this.name}}" data-appview="{{this.view}}" data-viewlabel="{{this.label}}">
{{#if this.icon}} {{#if this.icon}}
<img src="{{#if ../this.pluginPath}}/{{../this.name}}{{/if}}/images/{{this.icon}}" class="start-icon" /> <img src="{{#if ../this.pluginPath}}/{{../this.name}}{{/if}}/images/{{this.icon}}" class="start-icon" />
{{else}} {{else}}
@@ -47,7 +47,7 @@
{{else}} {{else}}
<li class="start-item has-submenu"> <li class="start-item has-submenu">
<img src="{{#if ../this.pluginPath}}/{{../this.name}}{{/if}}/images/folder.png" class="start-icon" style="position:absolute;left:12px;"/> <img src="{{#if ../this.pluginPath}}/{{../this.name}}{{/if}}/images/folder.png" class="start-icon" style="position:absolute;left:12px;"/>
<span class="menu-label">{{this.menu.label}}</span> <span {{#if this.description}}data-tooltip="{{this.description}}"{{/if}} class="menu-label">{{this.menu.label}}</span>
{{!-- {{#if this.version}}<small>v{{this.version}}</small>{{/if}} --}} {{!-- {{#if this.version}}<small>v{{this.version}}</small>{{/if}} --}}
<ul class="submenu"> <ul class="submenu">
@@ -82,7 +82,7 @@
<!-- Taskbar --> <!-- Taskbar -->
<div id="taskbar"> <div id="taskbar">
<button id="start-btn">☰</button> <button class="" id="start-btn">☰</button>
<div id="taskbar-windows"></div> <div id="taskbar-windows"></div>
<button style="margin-right:0;" class="monolyth notify-button pulse"> <button style="margin-right:0;" class="monolyth notify-button pulse">
<img class="icon" src="/images/notifybubble.png"> <img class="icon" src="/images/notifybubble.png">
@@ -96,7 +96,7 @@
<script src="javascript/contextMenu.js"></script> <script src="javascript/contextMenu.js"></script>
<script src="javascript/tableFilter.js"></script> <script src="javascript/tableFilter.js"></script>
<script src="javascript/requiredFields.js"></script> <script src="javascript/requiredFields.js"></script>
<script src="javascript/loadOnce.js"></script> <script src="javascript/uiEvents.js"></script>
<script src="javascript/JSON.js"></script> <script src="javascript/JSON.js"></script>
<script src="javascript/os.js"></script> <script src="javascript/os.js"></script>
<script> <script>
@@ -116,7 +116,10 @@
const trayNotifyButton = document.querySelector('#taskbar > .notify-button') const trayNotifyButton = document.querySelector('#taskbar > .notify-button')
const notify = new NotifyBubble(trayNotifyButton, "#notify-bubble"); const notify = new NotifyBubble(trayNotifyButton, "#notify-bubble");
document.addEventListener("contextmenu", evt => evt.preventDefault()); document.addEventListener("contextmenu", evt => {
evt.preventDefault()
evt.stopPropagation();
});
document.querySelectorAll('#start-menu .start-item.has-submenu').forEach(item => { document.querySelectorAll('#start-menu .start-item.has-submenu').forEach(item => {
item.addEventListener('click', evt => { item.addEventListener('click', evt => {
@@ -149,4 +152,24 @@
} }
}); });
/* Checks tab visibility
document.addEventListener(
'visibilitychange',
() => {
if (document.hidden) {
alert('AWAY AT: ' + new Date().toISOString());
} else {
updateActivity();
}
}
);
*/
</script> </script>

View File

@@ -15,7 +15,7 @@
<script> <script>
fetch('/api/getConfig', { method: 'POST' }) fetch('/api/config/get', { method: 'POST' })
.then(res => res.json()) .then(res => res.json())
.then(json => { .then(json => {
const tree = createJsonTree({ const tree = createJsonTree({
@@ -24,7 +24,7 @@
expandInitially: true, expandInitially: true,
onSave: json => { onSave: json => {
console.log(JSON.stringify(tree.getChanges())); console.log(JSON.stringify(tree.getChanges()));
fetch('/config', { fetch('/api/config/save', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(tree.getChanges(), null, 2) body: JSON.stringify(tree.getChanges(), null, 2)

View File

@@ -46,7 +46,7 @@
}); });
}; };
fetch('/api/getServerInfo', { method: 'POST' }) fetch('/api/serverInfo/get', { method: 'POST' })
.then(res => res.json()) .then(res => res.json())
.then(json => { .then(json => {

View File

@@ -10,7 +10,7 @@
</div> </div>
<script> <script>
fetch('/api/getStyles', { method: 'POST' }) fetch('/api/styles/get', { method: 'POST' })
.then(res => res.json()) .then(res => res.json())
.then(json => { .then(json => {
const tree = createJsonTree({ const tree = createJsonTree({
@@ -18,7 +18,7 @@
data: json, data: json,
expandInitially: true, expandInitially: true,
onSave: json => { onSave: json => {
fetch('/style', { fetch('/api/styles/save', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(tree.getChanges(), null, 2) body: JSON.stringify(tree.getChanges(), null, 2)

View File

@@ -1,14 +1,185 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="de"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Role Based Access Control</title> <title>Document</title>
<style>
#rbacAdmin {
font-family: Arial;
padding: 20px;
}
#rbacGroupContainer {
display: flex;
width: 100%;
height: 100%;
flex: 1;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
align-content: flex-start;
justify-content: flex-start;
}
section {
display:inline-flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 5px;
width: 200px;
border: 1px solid #ccc;
border-radius: 8px;
margin: 0 2px 2px 0;
transition: background var(--times-transition-colors) ease, border-color var(--times-transition-colors) ease, color var(--times-transition-colors) ease;
}
section:hover {
background: var(--theme-accent-hover-backcolor);
color: var(--theme-accent-hover-color);
border-color:var(--theme-accent-hover-boder-color);
}
section:active {
background: var(--theme-accent-active-backcolor);
color: var(--theme-accent-active-color);
border-color:var(--theme-accent-active-boder-color);
}
section span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
input {
margin: 5px;
}
.tile {
display:inline-flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
align-items: stretch;
height:60px;
padding: 5px;
width: 200px;
border: 1px solid #ccc;
border-radius: 8px;
margin: 0 2px 2px 0;
transition: background var(--times-transition-colors) ease, border-color var(--times-transition-colors) ease, color var(--times-transition-colors) ease;
}
</style>
</head> </head>
<body> <body>
<div class="container static" style="height: 100vh;"> <div class="container static" style="height:100vh;">
<div class="card static" style="overflow-y:auto;flex: 1 1 auto;" > <div class="card static">
<div class="tabs" id="rbacTabs" style="flex: 0 0 auto;">
<div class="tab">Verwaltung</div>
<div class="tab">Benutzer & Gruppen</div>
<div class="tab">Rollen & Berechtigungen</div>
</div>
<div id="rbacTabContents" class="tab-contents" style="flex: 1 1 100%;overflow-y:auto;padding:0;">
</div> </div>
</div> </div>
{{!-- <div class="container static" style="max-width:100vw;flex-direction:row;flex-wrap:wrap;flex: 1 1 100vh;">
<!-- USERS -->
<div class="card static row" style="align-items:center;height:fit-content;width:100%">
AD Synchronisation:
<button class="yellowbutton" data-tooltip="Synchronisiert die Benutzer aus dem AD" onclick="syncUsersFromAD()">Users</button>
<button class="yellowbutton" data-tooltip="Synchronisiert die Gruppen aus dem AD" onclick="syncGroupsFromAD()">Groups</button>
</div>
<div class="card" style="aflex-wrap:wrap;flex: 1 1 100%;justify-content: flex-start;">
Users <input id="newUserName" placeholder="sAMAccountName" />
<button class="bluebutton" onclick="createUser()">Create User</button>
<div class="table-wrapper " style="max-height:300px;overflow:auto">
<table id="rbacUsersTable">
<thead>
<tr>
<th class="text-align:left"></th>
<th class="text-align:left">ObjectGUID</th>
<th class="text-align:left">sAMAccountName</th>
<th class="text-align:center">Rollen</th>
<th class="text-align:center">Gruppen</th>
<th class="text-align:left">Name</th>
<th class="text-align:left">Vorname</th>
<th class="text-align:left">Mail</th>
<th class="text-align:center">Aktiv</th>
</tr>
</thead>
<tbody>
<tr><td colspan="100%">BENUTZER WERDEN GELADEN . . .</td></tr>
</tbody>
</table>
</div>
</div>
<!-- GROUPS -->
<div class="card static" style="min-width:300px;flex:1 1 calc(300px); max-height: 400px;overflow:auto">
<input id="newGroupName" placeholder="Gruppenname" /> <button class="bluebutton" onclick="createGroup()">Create Group</button>
<div id="rbacGroupContainer">
<span>GRUPPEN WERDEN GELADEN . . .</span>
</div>
</div>
<!-- ROLES -->
<div class="card" style="min-width:300px;flex:1 1 calc(300px)">
<input id="newRoleName" placeholder="Rollenname" />
<button class="bluebutton" onclick="createRole()">Create Role</button>
<div id="rbacRoleContainer">
<span>ROLLEN WERDEN GELADEN . . .</span>
</div>
</div>
<!-- PERMISSIONS -->
<div class="card" style="min-width:300px;flex:1 1 auto;align-items:center;">
<input id="permScope" placeholder="Scope" />.<input id="permResource" placeholder="Resource" />.<input id="permAction" placeholder="Action" />
<button class="bluebutton" onclick="createPermission()">Create Permission</button>
<div class="table-wrapper fit-table">
<table id="rbacPermissionsTable" style="">
<thead>
<tr>
<th class="text-align:left"></th>
<th class="text-align:left">ID</th>
<th class="text-align:center">Gruppen</th>
<th class="text-align:center">Benutzer</th>
<th class="text-align:center">Rollen</th>
<th class="text-align:right">Scope</th>
<th class="text-align:center">Resource</th>
<th class="text-align:left">Action</th>
</tr>
</thead>
<tbody>
<tr><td colspan="100%">BERECHTIGUNGEN WERDEN GELADEN . . .</td></tr>
</tbody>
</table>
</div>
</div>
</div> --}}
</body> </body>
<script type="text/javascript">
reloadPluginScript('/javascript/rbacAPI.js');
</script>
</html> </html>

View File

@@ -57,7 +57,8 @@ const server = https.createServer(httpsOptions, app);
const io = new Server(server, { const io = new Server(server, {
pingTimeout: 60000, // pingInterval: 1000, // alle 25s Ping senden
// pingTimeout: 200, // 20s ohne Pong => disconnect
maxHttpBufferSize: 1e8, // 100 MB maxHttpBufferSize: 1e8, // 100 MB
}); });
@@ -107,17 +108,24 @@ const server = https.createServer(httpsOptions, app);
databaseModel.set('authenticationGroupsModel', require(`@models/authenticationGroupsModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('authenticationGroupsModel', require(`@models/authenticationGroupsModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('authenticationRolesModel', require(`@models/authenticationRolesModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('authenticationRolesModel', require(`@models/authenticationRolesModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('groupClosureModel', require(`@models/groupClosureModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('groupClosureModel', require(`@models/groupClosureModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('groupModel', require(`@models/groupModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('group', require(`@models/groupModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('groupRolesModel', require(`@models/groupRolesModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('groupRolesModel', require(`@models/groupRolesModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('objectSourceModel', require(`@models/objectSourceModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('objectSourceModel', require(`@models/objectSourceModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('permissionModel', require(`@models/permissionModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('permissionModel', require(`@models/permissionModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('roleModel', require(`@models/roleModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('roleModel', require(`@models/roleModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('rolePermissionsModel', require(`@models/rolePermissionsModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('rolePermissionsModel', require(`@models/rolePermissionsModel`)(service.get('sqlManager').getInstance('main')));
service.set('rbacManager', new RBACManager(databaseModel)); databaseModel.set('authenticationOverviewView', require(`@models/authenticationOverviewView`)(service.get('sqlManager').getInstance('main')));
service.set('authenticationManager', new AuthenticationManager(databaseModel.get('authentication'), runtimeFile.configuration.live.integration.token.secret)); databaseModel.set('groupOverviewView', require(`@models/groupOverviewView`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('roleOverviewView', require(`@models/roleOverviewView`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('permissionTraceView', require(`@models/permissionTraceView`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('permissionOverviewView', require(`@models/permissionOverviewView`)(service.get('sqlManager').getInstance('main')));
service.set('activeDirectoryManager', new ActiveDirectory(runtimeFile.configuration.live.integration.activedirectory)) service.set('activeDirectoryManager', new ActiveDirectory(runtimeFile.configuration.live.integration.activedirectory))
service.set('rbacManager', new RBACManager(databaseModel, runtimeFile.configuration.live.integration.token.secret, service));
service.set('authenticationManager', new AuthenticationManager(databaseModel.get('authentication'), runtimeFile.configuration.live.integration.token.secret));
// everytime last created service! // everytime last created service!
service.set('pluginManager', new PluginManager(app, databaseModel.get('plugin'), localPath.plugins, runtimeFile.configuration.live.plugin.chown, service)); service.set('pluginManager', new PluginManager(app, databaseModel.get('plugin'), localPath.plugins, runtimeFile.configuration.live.plugin.chown, service));
@@ -128,13 +136,11 @@ const server = https.createServer(httpsOptions, app);
//#endregion //#endregion
//#region Service-Registration/Middleware/Utils/Helpers
require(`${localPath.root}/utils.js`); require(`${localPath.root}/utils.js`);
let helpers = service.get('fileSystemManager').loadAllFiles(`${localPath.public}/helpers`, '.js'); let helpers = service.get('fileSystemManager').loadAllFiles(`${localPath.public}/helpers`, '.js');
exports.helpers = helpers; exports.helpers = helpers;
// app.use(service.get('vaultifyManager').createMiddleware()); // app.use(service.get('vaultifyManager').createMiddleware());
app.use(service.get('rbacManager').requirePermissionMiddleware());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
app.use(express.json()); app.use(express.json());
@@ -145,7 +151,9 @@ const server = https.createServer(httpsOptions, app);
app.use(express.static(localPath.public)); app.use(express.static(localPath.public));
app.use(express.static(localPath.source)); app.use(express.static(localPath.source));
//#region Service-Registration/Middleware/Utils/Helpers
app.use(service.get('rbacManager').authenticate());
app.use(service.get('rbacManager').requirePermissionMiddleware());
app.use(function(request, response, next) { app.use(function(request, response, next) {
if (!request.secure) { if (!request.secure) {
@@ -200,14 +208,13 @@ const server = https.createServer(httpsOptions, app);
}); });
//#endregion //#endregion
//#region Implement routes //#region Implement routes
require(`${localPath.source}/routes/loginRoutes.js`).route(app, service); // #1 - no token security! important: first!!! require(`${localPath.source}/routes/loginRoutes.js`).route(app, service); // #1 - no token security! important: first!!!
require(`${localPath.source}/routes/indexRoutes.js`).route(app, service); // #2 - token security enabled at this point require(`${localPath.source}/routes/indexRoutes.js`).route(app, service); // #2 - token security enabled at this point
require(`${localPath.source}/routes/adminRoutes.js`).route(app, service); // #3 - token security always enabled require(`${localPath.source}/routes/adminRoutes.js`).route(app, service); // #3 - token security always enabled
//#endregion //#endregion
app.use(service.get('rbacManager').authenticate());
//#region Implements sockets //#region Implements sockets
require(`${localPath.source}/sockets/mainSocket.js`)( require(`${localPath.source}/sockets/mainSocket.js`)(

View File

@@ -12,6 +12,15 @@ module.exports = (sequelize) => {
} }
}, { }, {
tableName: 'AuthenticationGroups', tableName: 'AuthenticationGroups',
indexes: [
{
unique: true,
fields: [
'Authentication_ObjectGUID',
'Group_ObjectGUID'
]
}
],
timestamps: false timestamps: false
}); });

View File

@@ -5,6 +5,7 @@ module.exports = (sequelize) => {
const Authentication = sequelize.define('Authentication', { const Authentication = sequelize.define('Authentication', {
ObjectGUID: { ObjectGUID: {
type: DataTypes.UUID, type: DataTypes.UUID,
defaultValue: DataTypes.UUID,
primaryKey: true, primaryKey: true,
allowNull: false, allowNull: false,
}, },
@@ -41,7 +42,7 @@ module.exports = (sequelize) => {
allowNull: true, allowNull: true,
}, },
userAccountControl_ID: { userAccountControl_ID: {
type: DataTypes.STRING, type: DataTypes.INTEGER,
allowNull: true, allowNull: true,
}, },
telephoneNumber: { telephoneNumber: {
@@ -72,6 +73,10 @@ module.exports = (sequelize) => {
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,
allowNull: true, allowNull: true,
}, },
ObjectSource_ID: {
type: DataTypes.INTEGER,
allowNull: true,
}
}, { }, {
tableName: 'Authentication', // Tabellenname in der Datenbank tableName: 'Authentication', // Tabellenname in der Datenbank
timestamps: false, // Falls du keine createdAt/updatedAt Spalten hast timestamps: false, // Falls du keine createdAt/updatedAt Spalten hast

View File

@@ -0,0 +1,46 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const AuthenticationOverview = sequelize.define('AuthenticationOverviewView', {
ObjectGUID: {
type: DataTypes.UUID,
primaryKey: true
},
sAMAccountName: {
type: DataTypes.STRING
},
mail: {
type: DataTypes.STRING
},
givenName: {
type: DataTypes.STRING
},
sn: {
type: DataTypes.STRING
},
active: {
type: DataTypes.BOOLEAN
},
online: {
type: DataTypes.BOOLEAN
},
RoleCount: {
type: DataTypes.INTEGER
},
GroupCount: {
type: DataTypes.INTEGER
},
ObjectSourceName: {
type: DataTypes.STRING
}
}, {
tableName: 'vAuthenticationOverview',
schema: 'dbo',
timestamps: false,
// 🔥 WICHTIG für Views
freezeTableName: true
});
return AuthenticationOverview;
};

View File

@@ -4,7 +4,9 @@ module.exports = (sequelize) => {
const Group = sequelize.define('Group', { const Group = sequelize.define('Group', {
ObjectGUID: { ObjectGUID: {
type: DataTypes.UUID, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
}, },
Name: DataTypes.STRING(255), Name: DataTypes.STRING(255),
ObjectSource_ID: DataTypes.INTEGER, ObjectSource_ID: DataTypes.INTEGER,

View File

@@ -0,0 +1,39 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const GroupOverview = sequelize.define('GroupOverviewView', {
ObjectGUID: {
type: DataTypes.UUID,
primaryKey: true
},
Name: {
type: DataTypes.STRING
},
UserCount: {
type: DataTypes.INTEGER
},
RoleCount: {
type: DataTypes.INTEGER
},
ObjectSourceName: {
type: DataTypes.STRING
},
distinguishedName: {
type: DataTypes.STRING
}
}, {
tableName: 'vGroupOverview',
schema: 'dbo',
timestamps: false,
freezeTableName: true
});
return GroupOverview;
};

View File

@@ -13,10 +13,10 @@
"permissions": [ "permissions": [
{ {
"scope": "SYSTEM", "scope": "SYSTEM",
"resource": "ALL",
"action": "Administration" "action": "Administration"
} }
], ]
"authorized": true
}, },
{ {
"label": "Configs", "label": "Configs",
@@ -29,34 +29,29 @@
"permissions": [ "permissions": [
{ {
"scope": "SYSTEM", "scope": "SYSTEM",
"resource": "ALL",
"action": "Administration" "action": "Administration"
} }
], ]
"authorized": true
}, },
{ {
"label": "RBAC", "label": "RBAC",
"description": "Role-Based Access Control ist eine rollenbasierte Zugriffskontrolle, die auf Basis von Rollen und Gruppen, systemweite Berechtigungen vergibt", "description": "Role-Based Access Control ist eine rollenbasierte Zugriffskontrolle, die auf Basis von Rollen und Gruppen, systemweite Berechtigungen vergibt",
"view": "rbac.hbs", "view": "rbac",
"defaultSize": { "defaultSize": {
"width": "800px", "width": "800px",
"height": "600px" "height": "600px"
}, },
"icon": "app.png", "icon": "shield.png",
"permissions": [ "permissions": [
{ {
"scope": "SYSTEM", "scope": "SYSTEM",
"resource": "ALL",
"action": "Administration" "action": "Administration"
} }
], ]
"authorized": true
} }
] ]
},
"onlyAdministration": false,
"defaultSize": {
"width": "800px",
"height": "600px"
} }
}, },
{ {
@@ -70,24 +65,19 @@
"label": "EventLog", "label": "EventLog",
"view": "eventlog", "view": "eventlog",
"defaultSize": { "defaultSize": {
"width": "1200px", "width": "800px",
"height": "1200px" "height": "600px"
}, },
"icon": "eventlog.ico", "icon": "eventlog.ico",
"permissions": [ "permissions": [
{ {
"scope": "SYSTEM", "scope": "SYSTEM",
"resource": "ALL",
"action": "Administration" "action": "Administration"
} }
], ]
"authorized": true
} }
] ]
},
"onlyAdministration": false,
"defaultSize": {
"width": "1200px",
"height": "1200px"
} }
}, },
{ {
@@ -108,17 +98,12 @@
"permissions": [ "permissions": [
{ {
"scope": "SYSTEM", "scope": "SYSTEM",
"resource": "ALL",
"action": "Administration" "action": "Administration"
} }
], ]
"authorized": true
} }
] ]
},
"onlyAdministration": false,
"defaultSize": {
"width": "900px",
"height": "800px"
} }
}, },
{ {
@@ -139,17 +124,12 @@
"permissions": [ "permissions": [
{ {
"scope": "SYSTEM", "scope": "SYSTEM",
"resource": "ALL",
"action": "Administration" "action": "Administration"
} }
], ]
"authorized": true
} }
] ]
},
"onlyAdministration": false,
"defaultSize": {
"width": "900px",
"height": "500px"
} }
}, },
{ {
@@ -166,21 +146,16 @@
"width": "460px", "width": "460px",
"height": "515px" "height": "515px"
}, },
"icon": "app.png", "icon": "settings.png",
"permissions": [ "permissions": [
{ {
"scope": "SYSTEM", "scope": "SYSTEM",
"resource": "ALL",
"action": "Default_Access" "action": "Default_Access"
} }
], ]
"authorized": true
} }
] ]
},
"onlyAdministration": false,
"defaultSize": {
"width": "460px",
"height": "515px"
} }
}, },
{ {
@@ -197,17 +172,12 @@
"permissions": [ "permissions": [
{ {
"scope": "SYSTEM", "scope": "SYSTEM",
"resource": "ALL",
"action": "Default_Access" "action": "Default_Access"
} }
], ]
"authorized": true
} }
] ]
},
"onlyAdministration": false,
"defaultSize": {
"width": 800,
"height": 600
} }
} }
] ]

View File

@@ -0,0 +1,65 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const VPermissionOverviewView = sequelize.define(
'vPermissionOverviewView',
{
Permission_ID: {
type: DataTypes.INTEGER,
primaryKey: true
},
Scope: {
type: DataTypes.STRING
},
Resource: {
type: DataTypes.STRING
},
Action: {
type: DataTypes.STRING
},
PermissionKey: {
type: DataTypes.STRING
},
Role_ID: {
type: DataTypes.INTEGER,
primaryKey: true
},
RoleCount: {
type: DataTypes.INTEGER
},
GroupCount: {
type: DataTypes.INTEGER
},
DirectUserCount: {
type: DataTypes.INTEGER
},
GroupUserCount: {
type: DataTypes.INTEGER
},
TotalUserCount: {
type: DataTypes.INTEGER
}
},
{
tableName: 'vPermissionOverview', // ⚠️ exakt dein SQL View Name
timestamps: false,
freezeTableName: true,
// 🔒 Wichtig für Views
createdAt: false,
updatedAt: false
}
);
// 🚫 keine Mutationen erlauben (View!)
VPermissionOverviewView.removeAttribute('id');
return VPermissionOverviewView;
};

View File

@@ -0,0 +1,44 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const PermissionTrace = sequelize.define('vPermissionTrace', {
Authentication_ObjectGUID: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true
},
RoleName: {
type: DataTypes.STRING,
allowNull: true
},
Scope: {
type: DataTypes.STRING,
allowNull: true
},
Resource: {
type: DataTypes.STRING,
allowNull: true
},
Action: {
type: DataTypes.STRING,
allowNull: true
},
PermissionKey: {
type: DataTypes.STRING,
allowNull: true
}
}, {
tableName: 'vPermissionTrace',
timestamps: false,
freezeTableName: true
});
return PermissionTrace;
};

View File

@@ -0,0 +1,36 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize ) => {
return sequelize.define('vRoleOverview', {
ID: {
type: DataTypes.INTEGER,
primaryKey: true
},
Name: {
type: DataTypes.STRING
},
Description: {
type: DataTypes.STRING
},
RoleType: {
type: DataTypes.STRING
},
GroupCount: {
type: DataTypes.INTEGER
},
UserCount: {
type: DataTypes.INTEGER
}
}, {
tableName: 'vRoleOverview', // 🔥 wichtig: Name deiner VIEW
timestamps: false,
freezeTableName: true
});
};

View File

@@ -10,29 +10,24 @@ module.exports = (sequelize) => {
autoIncrement: true autoIncrement: true
}, },
License_ID: {
type: DataTypes.INTEGER,
allowNull: false
},
Customer_ID: { Customer_ID: {
type: DataTypes.UUIDV4, type: DataTypes.INTEGER,
allowNull: false
},
Feature: {
type: DataTypes.STRING(128),
allowNull: false
},
Payload: {
type: DataTypes.TEXT, // NVARCHAR(MAX)
allowNull: false allowNull: false
}, },
Signature: { Signature: {
type: DataTypes.TEXT, type: DataTypes.STRING(512),
allowNull: false allowNull: false
}, },
Active: { EncryptedPayload: {
type: DataTypes.BOOLEAN, type: DataTypes.BLOB('long'),
defaultValue: true allowNull: false
}, },
ExpiresAt: { ExpiresAt: {
@@ -40,12 +35,20 @@ module.exports = (sequelize) => {
allowNull: true allowNull: true
}, },
CreatedAt: { Status_ID: {
type: DataTypes.DATE, type: DataTypes.TINYINT,
defaultValue: DataTypes.NOW allowNull: false,
validate: {
isIn: [[0, 1, 2, 3, 4]]
}
}, },
UpdatedAt: { LastVerifiedAt: {
type: DataTypes.DATE,
allowNull: true
},
CreateDate: {
type: DataTypes.DATE, type: DataTypes.DATE,
defaultValue: DataTypes.NOW defaultValue: DataTypes.NOW
} }

View File

@@ -2,22 +2,25 @@ const { exec } = require('child_process');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { localPath, cache, runtimeFile } = require('@root/globalize.js'); const { localPath, cache, runtimeFile } = require('@root/globalize.js');
const { databaseModel } = require('@root/server.js');
const { raw } = require('body-parser');
const configurationFile = path.join(require('@root/server.js').path.source, 'models', 'configuration.json'); let rbacUsers, rbacGroups, rbacRoles, rbacPermissions = [];
const stylesheetFile = path.join(require('@root/server.js').path.source, 'models', 'stylesheet.json');
const serverInfoFile = path.join(require('@root/server.js').path.root, 'package.json');
module.exports = { module.exports = {
route(app, service) { route(app, service) {
// JSON configuration abrufen // JSON configuration abrufen
app.post('/api/getConfig', (req, res) => { app.post('/api/config/get', (req, res) => {
res.status(200).send(runtimeFile.configuration.live); res.status(200).send(runtimeFile.configuration.live);
}); });
// JSON stylesheet speichern
app.post('/api/styles/get', (req, res) => {
res.status(200).send(runtimeFile.stylesheet.live);
});
// JSON configuration speichern // JSON configuration speichern
app.post('/config', (req, res) => { app.post('/api/config/save', (req, res) => {
try { try {
runtimeFile.configuration.save(req.body); runtimeFile.configuration.save(req.body);
res.status(200).send({ status: 'ok' }); res.status(200).send({ status: 'ok' });
@@ -27,7 +30,7 @@ module.exports = {
}); });
// JSON stylesheet abrufen // JSON stylesheet abrufen
app.post('/api/getStyles', (req, res) => { app.post('/api/styles/save', (req, res) => {
try { try {
runtimeFile.stylesheet.save(req.body); runtimeFile.stylesheet.save(req.body);
res.status(200).send({ status: 'ok' }); res.status(200).send({ status: 'ok' });
@@ -36,29 +39,11 @@ module.exports = {
} }
}); });
// JSON stylesheet speichern
app.post('/style', (req, res) => {
fs.writeFile(runtimeFile.stylesheet.live, JSON.stringify(req.body, null, 2), (err) => {
if (err) return res.status(500).send(err);
res.status(200).send({ status: 'ok' });
});
});
// JSON package.json abrufen // JSON package.json abrufen
app.post('/api/getServerInfo', (req, res) => { app.post('/api/serverInfo/get', (req, res) => {
res.status(200).send({ package: runtimeFile.package.live, pid: process.pid, releaseNotes: runtimeFile.releaseNotes.live }); res.status(200).send({ package: runtimeFile.package.live, pid: process.pid, releaseNotes: runtimeFile.releaseNotes.live });
}); });
// JSON package.json speichern
app.post('/serverinfo', (req, res) => {
fs.writeFile(runtimeFile.package.live, JSON.stringify(req.body, null, 2), (err) => {
if (err) return res.status(500).send(err);
res.status(200).send({ status: 'ok' });
});
});
app.post('/api/eventlog/clearlog', (req, res) => { app.post('/api/eventlog/clearlog', (req, res) => {
service.get('eventManager').clear(); service.get('eventManager').clear();
res.status(200).send({ status: 'ok' }) res.status(200).send({ status: 'ok' })
@@ -103,11 +88,334 @@ module.exports = {
result = await service.get('pluginManager').unload(name); result = await service.get('pluginManager').unload(name);
} }
service.get('eventManager').write(null, result.levelId, name, result.message); service.get('eventManager').write(req.cookies.ObjectGUID, result.levelId, name, result.message);
service.get('socketManager').broadcast('/', 'plugin_status', result); service.get('socketManager').broadcast('/', 'plugin_status', result);
res.status(200).json(result); res.status(200).json(result);
}); });
// #region RBAC
// =========================================================
// 👤 AUTH
// =========================================================
app.post('/api/rbac/auth/get', async (req, res) => {
try {
rbacUsers = await service.get('rbacManager').getAuth();
res.json(rbacUsers);
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err);
res.status(500).json({ error: err.message });
}
})
app.get('/api/rbac/auth/details/:guid', async (req, res) => {
try {
const singleAuth = await databaseModel.get('authentication').findOne({ where: { ObjectGUID: req.params.guid }, raw: true });
const { refreshtoken, password, ...rest} = singleAuth
res.json(rest);
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err);
res.status(500).json({ error: err.message });
}
})
app.post('/api/rbac/auth/create', async (req, res) => {
try {
if(rbacUsers.map(user => user.sAMAccountName.toLowerCase() ).includes(req.body.sAMAccountName.toLowerCase())) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 2, 'RBAC', `${req.body.sAMAccountName} nicht angelegt.\r\nBenutzer existiert bereits`);
return res.status(400).json({ error: `${req.body.sAMAccountName} existiert bereits` });
}
const user = await service.get('rbacManager').createAuth(req.body);
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 0, 'RBAC', `Benutzer ${user.sAMAccountName} [${user.ObjectGUID}] angelegt`);
res.json(user);
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err.message);
res.status(500).json({ error: err.message });
}
});
app.post('/api/rbac/auth/syncFromAD', async (req, res) => {
try {
rbacUsers = await service.get('rbacManager').syncAuthByActiveDirectory();
console.log(await service.get('rbacManager').syncAuthenticationGroupsFromAD());
res.json(rbacUsers);
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err.message);
res.status(500).json({ error: err.message });
}
});
app.put('/api/rbac/auth/:id', async (req, res) => {
try {
await service.get('rbacManager').updateAuth(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.delete('/api/rbac/auth/:guid', async (req, res) => {
try {
const user = await databaseModel.get('authentication').findOne({ where: { ObjectGUID: req.params.guid }, raw: true });
if(!user) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 2, 'RBAC', `Authentifizierungs-GUID ${req.params.guid} nicht gefunden`);
res.status(400);
}
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 0, 'RBAC', `Benutzer ${user.sAMAccountName} [${user.ObjectGUID}] gelöscht`);
await service.get('rbacManager').deleteAuth(user.ObjectGUID);
res.status(200).json(user);
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err);
res.status(500).json({ error: err.message });
}
});
// =========================================================
// 👥 GROUPS
// =========================================================
app.post('/api/rbac/group/get', async (req, res) => {
try {
rbacGroups = await service.get('rbacManager').getGroup();
res.json(rbacGroups);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/api/rbac/group/create', async (req, res) => {
try {
if(rbacGroups.map(group => group.Name.toLowerCase()).includes(req.body.name.toLowerCase())) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 2, 'RBAC', `${req.body.name} nicht angelegt.\r\nGruppe existiert bereits`);
return res.status(400).json({ error: `${req.body.name} existiert bereits` });
}
const group = await service.get('rbacManager').createGroup(req.body);
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 0, 'RBAC', `Gruppe ${group.Name} [${group.ObjectGUID}] angelegt`);
res.json(group);
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err.message);
res.status(500).json({ error: err.message });
}
});
app.post('/api/rbac/group/syncFromAD', async (req, res) => {
try {
console.log(await service.get('rbacManager').syncGroupClosureFromAD());
rbacGroups = await service.get('rbacManager').syncGroupByActiveDirectory();
res.json(rbacGroups);
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err);
res.status(500).json({ error: err.message });
}
});
app.put('/api/rbac/group/:id', async (req, res) => {
try {
await service.get('rbacManager').updateGroup(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.delete('/api/rbac/group/:guid', async (req, res) => {
try {
const group = await databaseModel.get('group').findOne({ where: { ObjectGUID: req.params.guid }, raw: true });
if(!group) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 2, 'RBAC', `Gruppen-GUID ${req.params.guid} nicht gefunden`);
res.status(400);
}
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 0, 'RBAC', `Gruppe ${group.Name} [${group.ObjectGUID}] gelöscht`);
await service.get('rbacManager').deleteGroup(group.ObjectGUID);
res.status(200).json(group);
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err);
res.status(500).json({ error: err.message });
}
});
// =========================================================
// 🔗 USER ↔ GROUP
// =========================================================
app.post('/api/rbac/group/add-user', async (req, res) => {
const { authGuid, groupGuid } = req.body;
const auth = await databaseModel.get('authentication').findOne({ where: { ObjectGUID: authGuid }, raw: true });
const group = await databaseModel.get('group').findOne({ where: { ObjectGUID: groupGuid }, raw: true });
try {
const result = await service.get('rbacManager').addUserToGroup(authGuid, groupGuid);
if(!result) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 2, 'RBAC', `Benuzer ${auth.sAMAccountName} [${auth.ObjectGUID}] bereits in Gruppe ${group.Name} [${group.ObjectGUID}]`);
res.status(400).send('Benutzer bereits in Gruppe enthalten');
return;
}
res.json({ ok: true });
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', [ `Fehler beim Hinzufügen von ${auth.sAMAccountName} [${auth.ObjectGUID}] in Gruppe ${group.Name} [${group.ObjectGUID}]` ,err ]);
res.status(500).json({ error: err.message });
}
});
app.post('/api/group/remove-user', async (req, res) => {
try {
const { authGuid, groupId } = req.body;
await service.get('rbacManager').removeUserFromGroup(authId, groupId);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// =========================================================
// 🎭 ROLES
// =========================================================
app.post('/api/rbac/role/get', async (req, res) => {
try {
rbacRoles = await service.get('rbacManager').getRole();
res.json(rbacRoles);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/api/rbac/role/create', async (req, res) => {
try {
if(rbacRoles.map(role => role.Name.toLowerCase()).includes(req.body.name.toLowerCase())) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 2, 'RBAC', `${req.body.name} nicht angelegt.\r\nGruppe existiert bereits`);
return res.status(400).json({ error: `${req.body.name} existiert bereits` });
}
const role = await service.get('rbacManager').createRole(req.body);
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 0, 'RBAC', `Gruppe ${role.Name} [${role.ID}] angelegt`);
console.log(role);
res.json(role);
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err.message);
res.status(500).json({ error: err.message });
}
});
app.put('/api/rbac/role/:id', async (req, res) => {
try {
await service.get('rbacManager').updateRole(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.delete('/api/rbac/role/:id', async (req, res) => {
try {
const role = await databaseModel.get('roleModel').findOne({ where: { ID: req.params.id }, raw: true });
if(!role) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 2, 'RBAC', `Rolen-ID ${req.params.id} nicht gefunden`);
res.status(400);
}
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 0, 'RBAC', `Role ${role.Name} [${role.ID}] gelöscht`);
await service.get('rbacManager').deleteRole(role.ID);
res.status(200).json(role);
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err);
res.status(500).json({ error: err.message });
}
});
// =========================================================
// 🔗 ROLE ASSIGNMENTS
// =========================================================
app.post('/api/rbac/role/add-user', async (req, res) => {
try {
const { authGuid, roleId } = req.body;
await service.get('rbacManager').assignRoleToUser(authGuid, roleId);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/api/rbac/role/add-group', async (req, res) => {
try {
const { groupGuid, roleId } = req.body;
await service.get('rbacManager').assignRoleToGroup(groupGuid, roleId);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/api/rbac/role/add-permission', async (req, res) => {
try {
const { roleId, permissionId } = req.body;
await service.get('rbacManager').addPermissionToRole(roleId, permissionId);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/api/role/remove-permission', async (req, res) => {
try {
const { roleId, permissionId } = req.body;
await service.get('rbacManager').removePermissionFromRole(roleId, permissionId);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// =========================================================
// 🔐 PERMISSIONS
// =========================================================
app.post('/api/rbac/permission/get', async (req, res) => {
try {
rbacPermissions = await service.get('rbacManager').getPermission();
console.log(rbacPermissions)
res.json(rbacPermissions);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/api/rbac/permission/create', async (req, res) => {
try {
const perm = await service.get('rbacManager').createPermission(req.body);
res.json(perm);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.put('/permission/:id', async (req, res) => {
try {
await service.get('rbacManager').updatePermission(req.params.id, req.body);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.delete('/permission/:id', async (req, res) => {
try {
await service.get('rbacManager').deletePermission(req.params.id);
res.json({ ok: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
//#endregion RBAC
app.post('/api/plugins/getAll', async (req, res) => { app.post('/api/plugins/getAll', async (req, res) => {
try { try {
const plugins = await service.get('pluginManager').getStatus(); const plugins = await service.get('pluginManager').getStatus();
@@ -119,7 +427,7 @@ module.exports = {
app.post('/api/plugins/integrated', async (req, res) => { app.post('/api/plugins/integrated', async (req, res) => {
try { try {
res.status(200).json(cached.startMenuItems.live); res.status(200).json(runtimeFile.startMenuItems.live);
} catch (error) { } catch (error) {
res.status(500).json({ error: error.message }); res.status(500).json({ error: error.message });
} }

View File

@@ -21,12 +21,11 @@ module.exports = {
let context = fs.existsSync(pluginPath) let context = fs.existsSync(pluginPath)
? service.get('fileSystemManager').loadJSON(pluginPath) ? service.get('fileSystemManager').loadJSON(pluginPath)
: runtimeFile.startMenuItems.live.find(item => item.name == name); : cache.startMenuItems.find(item => item.name == name);
context.defaultSize = context.defaultSize =
context.menu.items.find(item => item.label == viewLabel)?.defaultSize || context.menu.items.find(item => item.label == viewLabel)?.defaultSize ||
{ width: 800, height: 600 }; { width: 800, height: 600 };
delete context.config; delete context.config;
res.json({ name, view, viewLabel, context, location, size, state, zIndex }); res.json({ name, view, viewLabel, context, location, size, state, zIndex });
}); });

View File

@@ -1,4 +1,4 @@
const { verify } = require("jsonwebtoken"); const { localPath, cache, runtimeFile } = require('@root/globalize.js');
module.exports = { module.exports = {
@@ -71,7 +71,7 @@ module.exports = {
res.clearCookie('sAMAccountName'); res.clearCookie('sAMAccountName');
res.clearCookie('ObjectGUID'); res.clearCookie('ObjectGUID');
res.render('login', { layout: false, title: app.locals.configuration.server.name }) res.render('login', { layout: false, title: runtimeFile.configuration.live.server.name })
// setTimeout(() => res.render('login', { layout: false, title: app.locals.configuration.server.name }), 3000); // setTimeout(() => res.render('login', { layout: false, title: app.locals.configuration.server.name }), 3000);
// res.json({ message: 'Logout erfolgreich' }); // res.json({ message: 'Logout erfolgreich' });
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -57,6 +57,10 @@ class AuthenticationManager {
return { token: null, levelId: 2, message: 'Falsches Passwort' }; return { token: null, levelId: 2, message: 'Falsches Passwort' };
} }
if(!user.active) {
return { token: null, levelId: 2, message: 'Benutzer nicht aktiv' };
}
const token = jwt.sign( const token = jwt.sign(
{ {
ObjectGUID: user.ObjectGUID, ObjectGUID: user.ObjectGUID,
@@ -67,12 +71,26 @@ class AuthenticationManager {
); );
user.refreshtoken = token; user.refreshtoken = token;
user.online = true; await this.setOnline(sAMAccountName);
await user.save(); await user.save();
return { token, levelId: 0, message: 'Erfolgreich angemeldet' }; return { token, levelId: 0, message: 'Erfolgreich angemeldet' };
} }
async setOnline(sAMAccountName) {
const user = await this.findUser(sAMAccountName);
user.online = true;
await user.save();
}
async setOffline(sAMAccountName) {
const user = await this.findUser(sAMAccountName);
user.online = false;
await user.save();
}
// ========================================================= // =========================================================
// LOGOUT // LOGOUT
// ========================================================= // =========================================================
@@ -85,8 +103,8 @@ class AuthenticationManager {
} }
user.refreshtoken = null; user.refreshtoken = null;
user.online = false;
await user.save(); await user.save();
await this.setOffline();
return { token: null, levelId: 0, message: 'Erfolgreich abgemeldet' }; return { token: null, levelId: 0, message: 'Erfolgreich abgemeldet' };
} }

View File

@@ -101,7 +101,7 @@ class EventManager {
const stackLine = err.stack.split('\n')[2]; // calls trace-line const stackLine = err.stack.split('\n')[2]; // calls trace-line
const trace = stackLine.match(/\/.*\d+/)[0].replace(localPath.root, ''); // path:line:column const trace = stackLine.match(/\/.*\d+/)[0].replace(localPath.root, ''); // path:line:column
const message = `${this.EventLog.tableName} cleared successfully`; const message = `${this.EventLog.tableName} geleert`;
await this.EventLog.destroy({ await this.EventLog.destroy({
where: {}, where: {},
truncate: true, truncate: true,

View File

@@ -313,8 +313,8 @@ class PluginManager {
permissions: [ permissions: [
{ {
scope: name, // Plugin Scope (default = plugin name) scope: name, // Plugin Scope (default = plugin name)
action: "Default_Access", resource: "Plugin",
resource: "MenuItem" action: "Default_Access"
} }
] ]
} }

View File

@@ -1,8 +1,11 @@
// rbac/RbacService.js const jwt = require('jsonwebtoken');
const sequelize = require('sequelize');
class RBACManager { class RBACManager {
constructor(databaseModel) { constructor(databaseModel, SECRET_KEY, service) {
this.db = databaseModel; this.db = databaseModel;
this.SECRET_KEY = SECRET_KEY;
this.service = service;
} }
async resolvePermissions(objectGuid) { async resolvePermissions(objectGuid) {
@@ -128,8 +131,6 @@ class RBACManager {
return next(); // oder 401 wenn du streng sein willst return next(); // oder 401 wenn du streng sein willst
} }
const rbac = this.rbac;
const permissions = req.user.permissions || []; const permissions = req.user.permissions || [];
const isSuperAdmin = req.user.isSuperAdmin || false; const isSuperAdmin = req.user.isSuperAdmin || false;
@@ -137,16 +138,12 @@ class RBACManager {
permissions, permissions,
isSuperAdmin, isSuperAdmin,
hasPermission: (required) => hasPermission: (required) =>
rbac.hasPermission(permissions, required, isSuperAdmin) this.hasPermission(permissions, required, isSuperAdmin)
}; };
return next();
next(); next();
} catch (err) { } catch (err) {
console.error('[RBAC MIDDLEWARE ERROR]', err); return res.status(500).json('[RBAC MIDDLEWARE ERROR]', err);
return res.status(500).json({ message: 'RBAC Fehler' });
} }
}; };
} }
@@ -168,7 +165,11 @@ class RBACManager {
const publicRoutes = [ const publicRoutes = [
'/login', '/login',
'/public' '/public',
'/css',
'/js',
'/images',
'/favicon.ico'
]; ];
const isPublicRoute = publicRoutes.some(route => const isPublicRoute = publicRoutes.some(route =>
@@ -189,8 +190,9 @@ class RBACManager {
return res.redirect('/login'); return res.redirect('/login');
} }
const user = await this.db.get('authentication').findOne( { where: { sAMAccountName } } ); req.user = {};
const user = await this.db.get('authentication').findOne( { where: { sAMAccountName } } );
if (!user || !user.active) { if (!user || !user.active) {
return res.redirect('/login'); return res.redirect('/login');
} }
@@ -234,6 +236,513 @@ class RBACManager {
resource: p.resource || null resource: p.resource || null
})); }));
} }
//#region CRUD
// =========================================================
// 👤 AUTH CRUD
// =========================================================
async getAuth() {
const Auth = this.db.get('authenticationOverviewView');
return await Auth.findAll({ raw: true });
}
async syncAuthByActiveDirectory() {
const auth = await this.db.get('authentication');
const all = await this.service.get('activeDirectoryManager').getAllUsers();
all.forEach(async user => {
try {
if(user.objectGUID !== null) {
await auth.upsert({...user, ObjectGUID: user.objectGUID});
} else {
}
} catch(err) {
throw err;
}
})
return all;
}
async createAuth(data) {
const Auth = this.db.get('authentication');
return await Auth.create({
sAMAccountName: data.sAMAccountName,
mail: data.mail,
sn: data.sn,
givenName: data.givenName,
ObjectSource_ID: 1,
active: true
});
}
async updateAuth(id, data) {
const Auth = this.db.get('authentication');
return await Auth.update(data, {
where: { ObjectGUID: id }
});
}
async deleteAuth(id) {
const Auth = this.db.get('authentication');
const AuthGroups = this.db.get('authenticationGroupsModel');
await AuthGroups.destroy({
where: { Authentication_ObjectGUID: id }
});
return await Auth.destroy({
where: { ObjectGUID: id }
});
}
// =========================================================
// 👥 GROUP CRUD
// =========================================================
async getGroup() {
const group = this.db.get('groupOverviewView');
return await group.findAll({ raw: true }) || [];
}
async createGroup(data) {
const group = this.db.get('group');
return await group.create({
Name: data.name,
ObjectSource_ID: 1
});
}
async syncGroupByActiveDirectory() {
const group = await this.db.get('group');
const all = await this.service.get('activeDirectoryManager').getAllGroups({
attributes: ['objectGUID', 'sAMAccountName', 'distinguishedName'],
includeMembers: false
});
all.forEach(async adGroup => {
try {
if(adGroup.objectGUID !== null) {
await group.upsert({...adGroup, ObjectGUID: adGroup.objectGUID, Name: adGroup.sAMAccountName});
} else {
}
} catch(err) {
throw err;
}
})
return all;
}
async updateGroup(id, data) {
const Group = this.db.get('group');
return await Group.update(data, {
where: { ObjectGUID: id }
});
}
async deleteGroup(id) {
const Group = this.db.get('group');
const AuthGroups = this.db.get('authenticationGroupsModel');
await AuthGroups.destroy({
where: { Group_ObjectGUID: id }
});
return await Group.destroy({
where: { ObjectGUID: id }
});
}
// =========================================================
// 🔗 AUTH ↔ GROUP RELATION
// =========================================================
async addUserToGroup(authGuid, groupGuid) {
const AuthGroups = this.db.get('authenticationGroupsModel');
if(await AuthGroups.findOne({ where: { Authentication_ObjectGUID: authGuid } })) { // AuthGroups.
return false
}
const group = await AuthGroups.create({
Authentication_ObjectGUID: authGuid,
Group_ObjectGUID: groupGuid
});
return group;
}
async removeUserFromGroup(authId, groupId) {
const AuthGroups = this.db.get('authenticationGroupsModel');
return await AuthGroups.destroy({
where: {
Authentication_ObjectGUID: authId,
Group_ObjectGUID: groupId
}
});
}
async syncAuthenticationGroupsFromAD() {
const AuthGroups = this.db.get('authenticationGroupsModel');
const users = await this.service
.get('activeDirectoryManager')
.getAllUsers([
'objectGUID',
'memberOf'
]);
const groups = await this.service
.get('activeDirectoryManager')
.getAllGroups({
attributes: [
'objectGUID',
'distinguishedName'
]
});
/**
* -----------------------------------------------------
* GROUP MAP (DN -> GUID)
* -----------------------------------------------------
*/
const groupMap = new Map();
for (const g of groups) {
if (g.distinguishedName && g.objectGUID) {
groupMap.set(
g.distinguishedName.toLowerCase(),
g.objectGUID
);
}
}
/**
* -----------------------------------------------------
* BUILD RELATIONS
* -----------------------------------------------------
*/
const relations = [];
for (const user of users) {
if (!user.objectGUID) continue;
const memberOf = Array.isArray(user.memberOf)
? user.memberOf
: user.memberOf
? [user.memberOf]
: [];
for (const dn of memberOf) {
const groupGuid = groupMap.get(dn.toLowerCase());
if (!groupGuid) continue;
relations.push({
Authentication_ObjectGUID: user.objectGUID,
Group_ObjectGUID: groupGuid
});
}
}
/**
* -----------------------------------------------------
* DB SYNC (clean rebuild)
* -----------------------------------------------------
*/
// await AuthGroups.destroy({
// where: {}
// });
if (relations.length) {
await AuthGroups.upsert(relations, {
// ignoreDuplicates: true
});
}
return relations;
}
async syncGroupClosureFromAD() {
const GroupClosure = this.db.get('groupClosureModel');
const groups = await this.service
.get('activeDirectoryManager')
.getAllGroups({
attributes: [
'objectGUID',
'distinguishedName',
'member'
],
includeMembers: false
});
/**
* -----------------------------------------------------
* MAP: DN -> GUID
* -----------------------------------------------------
*/
const groupMap = new Map();
for (const g of groups) {
if (g.distinguishedName && g.objectGUID) {
groupMap.set(
g.distinguishedName.toLowerCase(),
g.objectGUID
);
}
}
/**
* -----------------------------------------------------
* BUILD DIRECT RELATIONS
* -----------------------------------------------------
*/
const edges = [];
for (const group of groups) {
if (!group.objectGUID) continue;
const members = Array.isArray(group.member)
? group.member
: group.member
? [group.member]
: [];
for (const memberDN of members) {
const childGUID = groupMap.get(
memberDN.toLowerCase()
);
if (!childGUID) continue;
edges.push({
ParentGroup_ObjectGUID: group.objectGUID,
ChildGroup_ObjectGUID: childGUID,
Depth: 1
});
}
}
/**
* -----------------------------------------------------
* TRANSITIVE CLOSURE (DFS)
* -----------------------------------------------------
*/
const closure = [...edges];
const adjacency = new Map();
for (const e of edges) {
if (!adjacency.has(e.ParentGroup_ObjectGUID)) {
adjacency.set(e.ParentGroup_ObjectGUID, []);
}
adjacency.get(e.ParentGroup_ObjectGUID)
.push(e.ChildGroup_ObjectGUID);
}
const visitedCache = new Map();
const dfs = (root, current, depth) => {
const children = adjacency.get(current) || [];
for (const child of children) {
closure.push({
ParentGroup_ObjectGUID: root,
ChildGroup_ObjectGUID: child,
Depth: depth + 1
});
dfs(root, child, depth + 1);
}
};
for (const group of groups) {
const root = group.objectGUID;
if (!root) continue;
dfs(root, root, 0);
}
/**
* -----------------------------------------------------
* CLEAN & SAVE
* -----------------------------------------------------
*/
// await GroupClosure.destroy({
// where: {}
// });
await GroupClosure.bulkCreate(closure, {
ignoreDuplicates: true
});
return closure.length;
}
// =========================================================
// 🎭 ROLE CRUD
// =========================================================
async getRole() {
const role = this.db.get('roleOverviewView');
return await role.findAll({ raw: true }) || [];
}
async createRole(data) {
const Role = this.db.get('roleModel');
return await Role.create({
Name: data.name,
Description: data.description || null
});
}
async updateRole(id, data) {
const Role = this.db.get('roleModel');
return await Role.update(data, {
where: { ID: id }
});
}
async deleteRole(id) {
const Role = this.db.get('roleModel');
const RolePermissions = this.db.get('rolePermissionsModel');
const GroupRoles = this.db.get('groupRolesModel');
const AuthRoles = this.db.get('authenticationRolesModel');
await AuthRoles.destroy({
where: { Role_ID: id }
});
await GroupRoles.destroy({
where: { Role_ID: id }
});
await RolePermissions.destroy({
where: { Role_ID: id }
});
return await Role.destroy({
where: { ID: id }
});
}
// =========================================================
// 🔗 ROLE ASSIGNMENTS
// =========================================================
async assignRoleToUser(authId, roleId) {
const AuthRoles = this.db.get('authenticationRolesModel');
return await AuthRoles.create({
Authentication_ObjectGUID: authId,
Role_ID: roleId
});
}
async assignRoleToGroup(groupId, roleId) {
const GroupRoles = this.db.get('groupRolesModel');
return await GroupRoles.create({
Group_ObjectGUID: groupId,
Role_ID: roleId
});
}
async removeRoleFromUser(authId, roleId) {
const AuthRoles = this.db.get('authenticationRolesModel');
return await AuthRoles.destroy({
where: {
Authentication_ObjectGUID: authId,
Role_ID: roleId
}
});
}
// =========================================================
// 🔐 PERMISSION CRUD
// =========================================================
async getPermission() {
const permission = this.db.get('permissionOverviewView');
return await permission.findAll({ raw: true }) || [];
}
async createPermission(data) {
const Permission = this.db.get('permissionModel');
return await Permission.create({
Scope: data.scope,
Resource: data.resource,
Action: data.action
});
}
async updatePermission(id, data) {
const Permission = this.db.get('permissionModel');
return await Permission.update(data, {
where: { ID: id }
});
}
async deletePermission(id) {
const Permission = this.db.get('permissionModel');
return await Permission.destroy({
where: { ID: id }
});
}
// =========================================================
// 🔗 ROLE ↔ PERMISSION
// =========================================================
async addPermissionToRole(roleId, permissionId) {
const RolePerms = this.db.get('rolePermissionsModel');
return await RolePerms.create({
Role_ID: roleId,
Permission_ID: permissionId
});
}
async removePermissionFromRole(roleId, permissionId) {
const RolePerms = this.db.get('rolePermissionsModel');
return await RolePerms.destroy({
where: {
Role_ID: roleId,
Permission_ID: permissionId
}
});
}
//#endregion
} }
module.exports = RBACManager; module.exports = RBACManager;

View File

@@ -56,7 +56,7 @@ module.exports = {
...{ name: payload.name, view: payload.view, viewLabel: payload.viewLabel}, ...{ name: payload.name, view: payload.view, viewLabel: payload.viewLabel},
...data, ...data,
}; };
console.log(templateData) // console.log(templateData)
// Zuerst Plugin-View rendern // Zuerst Plugin-View rendern
app.render(view, templateData, (err, contentHtml) => { app.render(view, templateData, (err, contentHtml) => {

View File

@@ -25,11 +25,11 @@ class SocketManager {
userName: sAMAccountName userName: sAMAccountName
}); });
// console.log(`${sAMAccountName} [${objectGuid}] connected to ${namespace}`); console.log(`${sAMAccountName} [${objectGuid}] connected to ${namespace}`);
socket.on('disconnect', () => { socket.on('disconnect', () => {
clients.delete(objectGuid); clients.delete(objectGuid);
// console.log(`${sAMAccountName} [${objectGuid}] disconnected from ${namespace}`); console.log(`${sAMAccountName} [${objectGuid}] disconnected from ${namespace}`);
}); });
}); });

View File

@@ -75,26 +75,40 @@ class VaultifyManager {
// ========================================================= // =========================================================
verify(record) { verify(record) {
try { try {
const payload = this.parsePayload(record.Payload);
const data = { const data = {
Customer_ID: record.Customer_ID, Customer_ID: record.Customer_ID,
Feature: record.Feature, Feature: record.Feature,
Payload: this.parsePayload(record.Payload), Payload: payload,
ExpiresAt: record.ExpiresAt ExpiresAt: record.ExpiresAt ?? null
}; };
const verifier = crypto.createVerify('RSA-SHA256'); const verifier = crypto.createVerify('RSA-SHA256');
verifier.update(JSON.stringify(data)); verifier.update(JSON.stringify(data));
verifier.end(); verifier.end();
return verifier.verify( const isValid = verifier.verify(
this.publicKey, this.publicKey,
record.Signature, record.Signature,
'base64' 'base64'
); );
if (!isValid) return false;
// 🔥 WICHTIG: Ablaufdatum HIER erzwingen
if (payload.expiresAt && record.ExpiresAt) {
if (payload.expiresAt !== record.ExpiresAt) {
return false;
}
}
if (record.ExpiresAt && new Date(record.ExpiresAt) < new Date()) {
return false;
}
return true;
} catch { } catch {
return false; return false;
} }

View File

@@ -1,5 +1,7 @@
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { cache } = require('@root/globalize.js');
const { service } = require('@root/server.js');
module.exports = (app, socketManager, namespace, pluginManager, authenticationModel, fileSystemManager, eventManager, activeDirectory) => { module.exports = (app, socketManager, namespace, pluginManager, authenticationModel, fileSystemManager, eventManager, activeDirectory) => {
@@ -12,9 +14,26 @@ module.exports = (app, socketManager, namespace, pluginManager, authenticationMo
// eventManager.write(data.objectGuid, data.levelId, data.pluginName, data.message) // eventManager.write(data.objectGuid, data.levelId, data.pluginName, data.message)
}); });
// global.json.configuration.onChange(info => {
// console.log(info.delta)
// }); socketManager.io.on('connection', async (socket) => {
if(!socket.handshake.auth.objectGuid) {
return;
}
await service.get('authenticationManager').setOnline(socket.handshake.auth.sAMAccountName);
});
socketManager.on(namespace, 'disconnect', (socket, data) => {
if(!socket.handshake.auth.objectGuid) {
return;
}
setTimeout(async () => {
if(!socketManager.clients.get(namespace).has(socket.handshake.auth.objectGuid)) {
await service.get('authenticationManager').setOffline(socket.handshake.auth.sAMAccountName);
}
}, 8000);
});
mainSocket.on('connection', socket => { mainSocket.on('connection', socket => {

View File

@@ -21,8 +21,7 @@ module.exports = startMenuItems = async function (app, objectGuid) {
// ========================= // =========================
// Load menu sources // Load menu sources
// ========================= // =========================
const integratedStartmenuItems = runtimeFile.startMenuItems.live; const integratedStartmenuItems = safeClone(runtimeFile.startMenuItems.live);
const plugins = service const plugins = service
.get('pluginManager') .get('pluginManager')
.getStatus() .getStatus()
@@ -64,7 +63,6 @@ module.exports = startMenuItems = async function (app, objectGuid) {
plugin.menu.items = (plugin.menu.items || []).map(item => { plugin.menu.items = (plugin.menu.items || []).map(item => {
const resource = item.label;
const requiredPermissions = item.permissions || []; const requiredPermissions = item.permissions || [];
const debugTrace = []; const debugTrace = [];
@@ -78,25 +76,25 @@ module.exports = startMenuItems = async function (app, objectGuid) {
const scopeMatch = const scopeMatch =
userPerm.scope === required.scope; userPerm.scope === required.scope;
const resourceMatch =
!userPerm.resource ||
userPerm.resource === 'ALL' ||
userPerm.resource === required.resource;
const actionMatch = const actionMatch =
userPerm.action === 'ALL' || userPerm.action === 'ALL' ||
userPerm.action === required.action || userPerm.action === required.action ||
required.action === 'ALL'; required.action === 'ALL';
const resourceMatch = const result = scopeMatch && resourceMatch && actionMatch;
!userPerm.resource ||
userPerm.resource === 'ALL' ||
userPerm.resource === resource;
const result = scopeMatch && actionMatch && resourceMatch;
if (debug) { if (debug) {
debugTrace.push({ debugTrace.push({
userPerm, userPerm,
required, required,
scopeMatch, scopeMatch,
actionMatch,
resourceMatch, resourceMatch,
actionMatch,
result result
}); });
} }
@@ -106,7 +104,7 @@ module.exports = startMenuItems = async function (app, objectGuid) {
}); });
if (debug) { if (debug) {
log(`\nMENU ITEM: ${item.label} - AUTHORIZED:', ${authorized} - TRACE:', ${debugTrace}`); log(`\nMENU ITEM: ${item.label} - AUTHORIZED:', ${authorized} - TRACE:', ${JSON.stringify(debugTrace)}`);
} }
return { return {