Forgot Passaord
This commit is contained in:
1 parent
3140a024e6
commit
71ab82b592
31 files changed
+5193
-1259
No files matched your search
@@ -44,6 +44,10 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
avatar_color TEXT,
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
last_login DATETIME,
|
||||
security_phrase_content TEXT,
|
||||
security_phrase_iv TEXT,
|
||||
security_phrase_tag TEXT,
|
||||
security_phrase_downloaded_at DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
@@ -93,6 +97,83 @@ CREATE TABLE IF NOT EXISTS user_group_permissions (
|
||||
);
|
||||
`;
|
||||
|
||||
const createPermissionSectionsTable = `
|
||||
CREATE TABLE IF NOT EXISTS permission_sections (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
key TEXT NOT NULL UNIQUE,
|
||||
title TEXT NOT NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
has_navigation_flag INTEGER NOT NULL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`;
|
||||
|
||||
const createPermissionGroupsTable = `
|
||||
CREATE TABLE IF NOT EXISTS permission_groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
section_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(section_id, key),
|
||||
FOREIGN KEY (section_id) REFERENCES permission_sections(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
const createPermissionItemsTable = `
|
||||
CREATE TABLE IF NOT EXISTS permission_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
section_id INTEGER NOT NULL,
|
||||
group_id INTEGER,
|
||||
key TEXT NOT NULL UNIQUE,
|
||||
label TEXT NOT NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
default_level TEXT NOT NULL,
|
||||
available_levels TEXT NOT NULL,
|
||||
is_required INTEGER NOT NULL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (section_id) REFERENCES permission_sections(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (group_id) REFERENCES permission_groups(id) ON DELETE SET NULL
|
||||
);
|
||||
`;
|
||||
|
||||
const createPermissionDependenciesTable = `
|
||||
CREATE TABLE IF NOT EXISTS permission_dependencies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
permission_id INTEGER NOT NULL,
|
||||
depends_on_permission_id INTEGER NOT NULL,
|
||||
required_level TEXT DEFAULT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (permission_id) REFERENCES permission_items(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (depends_on_permission_id) REFERENCES permission_items(id) ON DELETE CASCADE,
|
||||
UNIQUE(permission_id, depends_on_permission_id)
|
||||
);
|
||||
`;
|
||||
|
||||
const createPermissionDependenciesIndex = `
|
||||
CREATE INDEX IF NOT EXISTS idx_permission_dependencies_required
|
||||
ON permission_dependencies (permission_id, depends_on_permission_id);
|
||||
`;
|
||||
|
||||
const createGroupPermissionValuesTable = `
|
||||
CREATE TABLE IF NOT EXISTS group_permission_values (
|
||||
group_id INTEGER NOT NULL,
|
||||
permission_id INTEGER NOT NULL,
|
||||
level TEXT NOT NULL,
|
||||
effective_level TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (group_id, permission_id),
|
||||
FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (permission_id) REFERENCES permission_items(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
|
||||
const createServersTable = `
|
||||
CREATE TABLE IF NOT EXISTS servers (
|
||||
@@ -364,6 +445,549 @@ console.log('✅ permissions table ready');
|
||||
db.exec(createUserGroupPermissionsTable);
|
||||
console.log('✅ user_group_permissions table ready');
|
||||
|
||||
db.exec(createPermissionSectionsTable);
|
||||
console.log('✅ permission_sections table ready');
|
||||
|
||||
db.exec(createPermissionGroupsTable);
|
||||
console.log('✅ permission_groups table ready');
|
||||
|
||||
db.exec(createPermissionItemsTable);
|
||||
console.log('✅ permission_items table ready');
|
||||
|
||||
db.exec(createPermissionDependenciesTable);
|
||||
db.exec(createPermissionDependenciesIndex);
|
||||
console.log('✅ permission_dependencies table ready');
|
||||
|
||||
db.exec(createGroupPermissionValuesTable);
|
||||
console.log('✅ group_permission_values table ready');
|
||||
|
||||
const permissionsSeeded = () => {
|
||||
try {
|
||||
const row = db.prepare('SELECT COUNT(*) as count FROM permission_sections').get();
|
||||
return row?.count > 0;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const seedPermissions = () => {
|
||||
if (permissionsSeeded()) {
|
||||
console.log('ℹ️ permission structure already seeded');
|
||||
return;
|
||||
}
|
||||
|
||||
const insertSection = db.prepare(`
|
||||
INSERT INTO permission_sections (key, title, sort_order, has_navigation_flag)
|
||||
VALUES (@key, @title, @sort_order, @has_navigation_flag)
|
||||
`);
|
||||
|
||||
const insertGroup = db.prepare(`
|
||||
INSERT INTO permission_groups (section_id, key, title, sort_order)
|
||||
VALUES (@section_id, @key, @title, @sort_order)
|
||||
`);
|
||||
|
||||
const insertItem = db.prepare(`
|
||||
INSERT INTO permission_items (
|
||||
section_id,
|
||||
group_id,
|
||||
key,
|
||||
label,
|
||||
sort_order,
|
||||
default_level,
|
||||
available_levels,
|
||||
is_required
|
||||
) VALUES (
|
||||
@section_id,
|
||||
@group_id,
|
||||
@key,
|
||||
@label,
|
||||
@sort_order,
|
||||
@default_level,
|
||||
@available_levels,
|
||||
@is_required
|
||||
)
|
||||
`);
|
||||
|
||||
const insertDependency = db.prepare(`
|
||||
INSERT INTO permission_dependencies (permission_id, depends_on_permission_id, required_level)
|
||||
VALUES (@permission_id, @depends_on_permission_id, @required_level)
|
||||
`);
|
||||
|
||||
const sections = [
|
||||
{
|
||||
key: 'stacks',
|
||||
title: 'Stacks',
|
||||
sortOrder: 0,
|
||||
hasNavigation: 0,
|
||||
groups: [],
|
||||
items: [
|
||||
{
|
||||
key: 'stacks-redeploy-single',
|
||||
label: 'Redeploy einzeln',
|
||||
sortOrder: 0,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: []
|
||||
},
|
||||
{
|
||||
key: 'stacks-redeploy-selection',
|
||||
label: 'Redeploy Auswahl',
|
||||
sortOrder: 1,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: []
|
||||
},
|
||||
{
|
||||
key: 'stacks-redeploy-all',
|
||||
label: 'Redeploy Alle',
|
||||
sortOrder: 2,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'logs',
|
||||
title: 'Logs',
|
||||
sortOrder: 1,
|
||||
hasNavigation: 1,
|
||||
groups: [],
|
||||
items: [
|
||||
{
|
||||
key: 'logs-access',
|
||||
label: 'Bereich & Navigation',
|
||||
sortOrder: 0,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 1,
|
||||
dependencies: []
|
||||
},
|
||||
{
|
||||
key: 'logs-export',
|
||||
label: 'Logs Exportieren',
|
||||
sortOrder: 1,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'logs-access',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'logs-delete',
|
||||
label: 'Logs löschen',
|
||||
sortOrder: 2,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'logs-access',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'users',
|
||||
title: 'Benutzer',
|
||||
sortOrder: 2,
|
||||
hasNavigation: 1,
|
||||
groups: [],
|
||||
items: [
|
||||
{
|
||||
key: 'users-access',
|
||||
label: 'Bereich & Navigation',
|
||||
sortOrder: 0,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 1,
|
||||
dependencies: []
|
||||
},
|
||||
{
|
||||
key: 'users-edit',
|
||||
label: 'Benutzer bearbeiten',
|
||||
sortOrder: 1,
|
||||
defaultLevel: 'read',
|
||||
levels: ['full', 'read'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'users-access',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'users-delete',
|
||||
label: 'Benutzer löschen',
|
||||
sortOrder: 2,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'users-access',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'users-security-phrase',
|
||||
label: 'Sicherheitsschlüssel',
|
||||
sortOrder: 3,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'read', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'users-access',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'user-groups',
|
||||
title: 'Benutzergruppen',
|
||||
sortOrder: 3,
|
||||
hasNavigation: 1,
|
||||
groups: [],
|
||||
items: [
|
||||
{
|
||||
key: 'user-groups-access',
|
||||
label: 'Bereich & Navigation',
|
||||
sortOrder: 0,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 1,
|
||||
dependencies: []
|
||||
},
|
||||
{
|
||||
key: 'user-groups-edit',
|
||||
label: 'Benutzergruppen bearbeiten',
|
||||
sortOrder: 1,
|
||||
defaultLevel: 'read',
|
||||
levels: ['full', 'read'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'user-groups-access',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'user-groups-delete',
|
||||
label: 'Benutzergruppen löschen',
|
||||
sortOrder: 2,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'user-groups-access',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'maintenance',
|
||||
title: 'Wartung',
|
||||
sortOrder: 4,
|
||||
hasNavigation: 1,
|
||||
groups: [
|
||||
{
|
||||
key: 'maintenance-server-group',
|
||||
title: 'Server & Endpoints',
|
||||
sortOrder: 0,
|
||||
items: [
|
||||
{
|
||||
key: 'maintenance-server-manage',
|
||||
label: 'Server/Endpoint-Sektion',
|
||||
sortOrder: 0,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'read', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'maintenance-access',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'maintenance-server-delete',
|
||||
label: 'Server/Endpoint löschen',
|
||||
sortOrder: 1,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'maintenance-access',
|
||||
requiredLevel: '!=none'
|
||||
},
|
||||
{
|
||||
dependsOnKey: 'maintenance-server-manage',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'maintenance-portainer-group',
|
||||
title: 'Portainer',
|
||||
sortOrder: 1,
|
||||
items: [
|
||||
{
|
||||
key: 'maintenance-portainer',
|
||||
label: 'Portainer-Sektion',
|
||||
sortOrder: 0,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'maintenance-access',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'maintenance-ssh-update',
|
||||
label: 'SSH/Update-Skript',
|
||||
sortOrder: 1,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'read', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'maintenance-access',
|
||||
requiredLevel: '!=none'
|
||||
},
|
||||
{
|
||||
dependsOnKey: 'maintenance-portainer',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'maintenance-update',
|
||||
label: 'Update durchführen',
|
||||
sortOrder: 2,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'maintenance-access',
|
||||
requiredLevel: '!=none'
|
||||
},
|
||||
{
|
||||
dependsOnKey: 'maintenance-portainer',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'maintenance-duplicates-group',
|
||||
title: 'Doppelte Stacks',
|
||||
sortOrder: 2,
|
||||
items: [
|
||||
{
|
||||
key: 'maintenance-duplicates',
|
||||
label: 'Doppelte Stacks',
|
||||
sortOrder: 0,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'read', 'none'],
|
||||
isRequired: 0,
|
||||
dependencies: [
|
||||
{
|
||||
dependsOnKey: 'maintenance-access',
|
||||
requiredLevel: '!=none'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
items: [
|
||||
{
|
||||
key: 'maintenance-access',
|
||||
label: 'Bereich & Navigation',
|
||||
sortOrder: 0,
|
||||
defaultLevel: 'none',
|
||||
levels: ['full', 'none'],
|
||||
isRequired: 1,
|
||||
dependencies: []
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const itemKeyToId = new Map();
|
||||
|
||||
const insertPermissions = db.transaction(() => {
|
||||
sections.forEach((section) => {
|
||||
const sectionResult = insertSection.run({
|
||||
key: section.key,
|
||||
title: section.title,
|
||||
sort_order: section.sortOrder,
|
||||
has_navigation_flag: section.hasNavigation ? 1 : 0
|
||||
});
|
||||
|
||||
const sectionId = sectionResult.lastInsertRowid;
|
||||
|
||||
const sectionItems = section.items ?? [];
|
||||
sectionItems.forEach((item, index) => {
|
||||
const itemResult = insertItem.run({
|
||||
section_id: sectionId,
|
||||
group_id: null,
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
sort_order: item.sortOrder ?? index,
|
||||
default_level: item.defaultLevel,
|
||||
available_levels: JSON.stringify(item.levels),
|
||||
is_required: item.isRequired ? 1 : 0
|
||||
});
|
||||
itemKeyToId.set(item.key, itemResult.lastInsertRowid);
|
||||
});
|
||||
|
||||
const groups = section.groups ?? [];
|
||||
groups.forEach((group, groupIdx) => {
|
||||
const groupResult = insertGroup.run({
|
||||
section_id: sectionId,
|
||||
key: group.key,
|
||||
title: group.title,
|
||||
sort_order: group.sortOrder ?? groupIdx
|
||||
});
|
||||
const groupId = groupResult.lastInsertRowid;
|
||||
const groupItems = group.items ?? [];
|
||||
groupItems.forEach((item, itemIdx) => {
|
||||
const itemResult = insertItem.run({
|
||||
section_id: sectionId,
|
||||
group_id: groupId,
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
sort_order: item.sortOrder ?? itemIdx,
|
||||
default_level: item.defaultLevel,
|
||||
available_levels: JSON.stringify(item.levels),
|
||||
is_required: item.isRequired ? 1 : 0
|
||||
});
|
||||
itemKeyToId.set(item.key, itemResult.lastInsertRowid);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sections.forEach((section) => {
|
||||
const collectDependencies = (items = []) => {
|
||||
items.forEach((item) => {
|
||||
if (!item.dependencies || !item.dependencies.length) {
|
||||
return;
|
||||
}
|
||||
const permissionId = itemKeyToId.get(item.key);
|
||||
item.dependencies.forEach((dependency) => {
|
||||
const dependsId = itemKeyToId.get(dependency.dependsOnKey);
|
||||
if (!dependsId) {
|
||||
console.warn(`⚠️ dependency target ${dependency.dependsOnKey} not found for ${item.key}`);
|
||||
return;
|
||||
}
|
||||
insertDependency.run({
|
||||
permission_id: permissionId,
|
||||
depends_on_permission_id: dependsId,
|
||||
required_level: dependency.requiredLevel ?? null
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
collectDependencies(section.items);
|
||||
(section.groups ?? []).forEach((group) => collectDependencies(group.items));
|
||||
});
|
||||
});
|
||||
|
||||
insertPermissions();
|
||||
console.log('✅ permission structure seeded');
|
||||
};
|
||||
|
||||
seedPermissions();
|
||||
|
||||
const ensureUserSecurityPhraseColumns = () => {
|
||||
const columns = db.prepare('PRAGMA table_info(users)').all();
|
||||
const columnNames = new Set(columns.map((column) => column.name));
|
||||
|
||||
const addColumnIfMissing = (name, sql) => {
|
||||
if (!columnNames.has(name)) {
|
||||
db.exec(sql);
|
||||
}
|
||||
};
|
||||
|
||||
addColumnIfMissing('security_phrase_content', 'ALTER TABLE users ADD COLUMN security_phrase_content TEXT');
|
||||
addColumnIfMissing('security_phrase_iv', 'ALTER TABLE users ADD COLUMN security_phrase_iv TEXT');
|
||||
addColumnIfMissing('security_phrase_tag', 'ALTER TABLE users ADD COLUMN security_phrase_tag TEXT');
|
||||
addColumnIfMissing('security_phrase_downloaded_at', 'ALTER TABLE users ADD COLUMN security_phrase_downloaded_at DATETIME');
|
||||
};
|
||||
|
||||
ensureUserSecurityPhraseColumns();
|
||||
|
||||
const ensurePermissionDefaultLevels = () => {
|
||||
const desiredDefaults = [
|
||||
{ key: 'stacks-redeploy-single', defaultLevel: 'none' },
|
||||
{ key: 'stacks-redeploy-selection', defaultLevel: 'none' },
|
||||
{ key: 'stacks-redeploy-all', defaultLevel: 'none' },
|
||||
{ key: 'logs-access', defaultLevel: 'none' },
|
||||
{ key: 'logs-export', defaultLevel: 'none' },
|
||||
{ key: 'logs-delete', defaultLevel: 'none' },
|
||||
{ key: 'users-access', defaultLevel: 'none' },
|
||||
{ key: 'users-edit', defaultLevel: 'read' },
|
||||
{ key: 'users-delete', defaultLevel: 'none' },
|
||||
{ key: 'users-security-phrase', defaultLevel: 'none' },
|
||||
{ key: 'user-groups-access', defaultLevel: 'none' },
|
||||
{ key: 'user-groups-edit', defaultLevel: 'read' },
|
||||
{ key: 'user-groups-delete', defaultLevel: 'none' },
|
||||
{ key: 'maintenance-access', defaultLevel: 'none' },
|
||||
{ key: 'maintenance-server-manage', defaultLevel: 'none' },
|
||||
{ key: 'maintenance-server-delete', defaultLevel: 'none' },
|
||||
{ key: 'maintenance-portainer', defaultLevel: 'none' },
|
||||
{ key: 'maintenance-ssh-update', defaultLevel: 'none' },
|
||||
{ key: 'maintenance-update', defaultLevel: 'none' },
|
||||
{ key: 'maintenance-duplicates', defaultLevel: 'none' }
|
||||
];
|
||||
|
||||
const updateDefaultLevel = db.prepare(`
|
||||
UPDATE permission_items
|
||||
SET default_level = @default_level
|
||||
WHERE key = @key AND default_level != @default_level
|
||||
`);
|
||||
|
||||
db.transaction(() => {
|
||||
desiredDefaults.forEach(({ key, defaultLevel }) => {
|
||||
updateDefaultLevel.run({ key, default_level: defaultLevel });
|
||||
});
|
||||
})();
|
||||
};
|
||||
|
||||
ensurePermissionDefaultLevels();
|
||||
|
||||
db.exec(
|
||||
"DELETE FROM group_permission_values WHERE group_id IN (SELECT id FROM user_groups WHERE lower(name) = 'superuser')"
|
||||
);
|
||||
|
||||
db.exec(createServersTable);
|
||||
console.log('✅ servers table ready');
|
||||
|
||||
|
||||
+510
-46
@@ -18,9 +18,9 @@ import {
|
||||
registerSuperuser,
|
||||
removeSuperuser,
|
||||
findUserByIdentifier,
|
||||
findUserById,
|
||||
markUserLogin,
|
||||
verifyPassword
|
||||
verifyPassword,
|
||||
SUPERUSER_GROUP_NAME
|
||||
} from './auth/superuser.js';
|
||||
import {
|
||||
logEvent,
|
||||
@@ -45,14 +45,46 @@ import {
|
||||
removeServer,
|
||||
setServerApiKey
|
||||
} from './setup/index.js';
|
||||
import { listUsers, getUserById, updateUserGroups, createUser, updateUserDetails, deleteUser, updateUserActiveStatus } from './users/index.js';
|
||||
import {
|
||||
listUsers,
|
||||
getUserById,
|
||||
updateUserGroups,
|
||||
createUser,
|
||||
updateUserDetails,
|
||||
deleteUser,
|
||||
updateUserActiveStatus,
|
||||
getUserSecurityPhrase,
|
||||
renewUserSecurityPhrase,
|
||||
markSecurityPhraseDownloaded,
|
||||
ensureSecurityPhrasesForExistingUsers,
|
||||
verifySecurityPhraseForUsername,
|
||||
setUserPassword
|
||||
} from './users/index.js';
|
||||
import { listGroups, createGroup, getGroupById, updateGroupDetails, deleteGroup } from './groups/index.js';
|
||||
import {
|
||||
getPermissionStructure,
|
||||
getPermissionValuesByGroup,
|
||||
saveGroupPermissionValues,
|
||||
clearGroupPermissionValues,
|
||||
getSuperuserPermissionMap,
|
||||
getEffectivePermissionsForGroups,
|
||||
hasRequiredPermission
|
||||
} from './permissions/index.js';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
ensureSuperuserFromEnv();
|
||||
ensureDefaultsFromEnv();
|
||||
|
||||
try {
|
||||
const initializedCount = ensureSecurityPhrasesForExistingUsers();
|
||||
if (initializedCount > 0) {
|
||||
console.log(`ℹ️ Sicherheitsschlüssel für ${initializedCount} bestehende Benutzer initialisiert`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('⚠️ Initialisierung der Sicherheitsschlüssel für bestehende Benutzer fehlgeschlagen:', error);
|
||||
}
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
@@ -131,6 +163,8 @@ const AUTH_COOKIE_OPTIONS = {
|
||||
};
|
||||
|
||||
const activeSessions = new Map();
|
||||
const passwordResetTickets = new Map();
|
||||
const PASSWORD_RESET_TTL_MS = 1000 * 60 * 15;
|
||||
|
||||
const PUBLIC_API_ROUTES = [
|
||||
{ method: 'GET', matcher: /^\/api\/auth\/superuser\/status$/ },
|
||||
@@ -138,6 +172,8 @@ const PUBLIC_API_ROUTES = [
|
||||
{ method: 'POST', matcher: /^\/api\/auth\/login$/ },
|
||||
{ method: 'POST', matcher: /^\/api\/auth\/logout$/ },
|
||||
{ method: 'GET', matcher: /^\/api\/auth\/session$/ },
|
||||
{ method: 'POST', matcher: /^\/api\/auth\/recover\/verify$/ },
|
||||
{ method: 'POST', matcher: /^\/api\/auth\/recover\/reset$/ },
|
||||
{ method: 'GET', matcher: /^\/api\/setup\/status$/ },
|
||||
{ method: 'POST', matcher: /^\/api\/setup\/complete$/ }
|
||||
];
|
||||
@@ -149,6 +185,40 @@ const sanitizeUser = (user) => ({
|
||||
avatarColor: user.avatar_color || null
|
||||
});
|
||||
|
||||
const buildUserSessionPayload = (userId) => {
|
||||
const record = getUserById(userId);
|
||||
if (!record || !record.isActive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const groups = Array.isArray(record.groups) ? record.groups : [];
|
||||
const groupIds = groups
|
||||
.map((group) => Number(group.id))
|
||||
.filter((groupId) => Number.isFinite(groupId) && groupId > 0);
|
||||
|
||||
const isSuperuser = groups.some(
|
||||
(group) => typeof group?.name === 'string' && group.name.toLowerCase() === SUPERUSER_GROUP_NAME
|
||||
);
|
||||
|
||||
const permissions = isSuperuser
|
||||
? getSuperuserPermissionMap()
|
||||
: getEffectivePermissionsForGroups(groupIds);
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: record.id,
|
||||
username: record.username,
|
||||
email: record.email || null,
|
||||
avatarColor: record.avatarColor || null,
|
||||
groups,
|
||||
isSuperuser,
|
||||
securityPhraseDownloadedAt: record.securityPhraseDownloadedAt || null,
|
||||
requiresSecurityPhraseDownload: !record.securityPhraseDownloadedAt
|
||||
},
|
||||
permissions
|
||||
};
|
||||
};
|
||||
|
||||
const cleanupExpiredSessions = () => {
|
||||
const now = Date.now();
|
||||
for (const [token, session] of activeSessions.entries()) {
|
||||
@@ -167,6 +237,49 @@ const removeSessionsForUser = (userId) => {
|
||||
}
|
||||
};
|
||||
|
||||
const cleanupExpiredPasswordResetTickets = () => {
|
||||
const now = Date.now();
|
||||
for (const [token, ticket] of passwordResetTickets.entries()) {
|
||||
if (!ticket || ticket.expiresAt <= now) {
|
||||
passwordResetTickets.delete(token);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const removePasswordResetTicketsForUser = (userId) => {
|
||||
if (!userId) return;
|
||||
for (const [token, ticket] of passwordResetTickets.entries()) {
|
||||
if (ticket?.userId === userId) {
|
||||
passwordResetTickets.delete(token);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const createPasswordResetTicketForUser = (userId) => {
|
||||
cleanupExpiredPasswordResetTickets();
|
||||
removePasswordResetTicketsForUser(userId);
|
||||
const token = crypto.randomBytes(32).toString('hex');
|
||||
const expiresAt = Date.now() + PASSWORD_RESET_TTL_MS;
|
||||
passwordResetTickets.set(token, { userId, expiresAt });
|
||||
return { token, expiresAt };
|
||||
};
|
||||
|
||||
const getPasswordResetTicket = (token) => {
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
cleanupExpiredPasswordResetTickets();
|
||||
const record = passwordResetTickets.get(token);
|
||||
if (!record) {
|
||||
return null;
|
||||
}
|
||||
if (record.expiresAt <= Date.now()) {
|
||||
passwordResetTickets.delete(token);
|
||||
return null;
|
||||
}
|
||||
return record;
|
||||
};
|
||||
|
||||
const createSessionForUser = (user) => {
|
||||
cleanupExpiredSessions();
|
||||
removeSessionsForUser(user.id);
|
||||
@@ -892,6 +1005,52 @@ const maintenanceGuard = (req, res, next) => {
|
||||
});
|
||||
};
|
||||
|
||||
const requirePermission = (permissionKey, requiredLevel = 'full') => (req, res, next) => {
|
||||
if (!permissionKey) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.user?.isSuperuser) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const permissions = req.userPermissions || {};
|
||||
if (hasRequiredPermission(permissions, permissionKey, requiredLevel)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
return res.status(403).json({
|
||||
error: 'INSUFFICIENT_PERMISSIONS',
|
||||
permission: permissionKey,
|
||||
requiredLevel
|
||||
});
|
||||
};
|
||||
|
||||
const requireAnyPermission = (candidates = []) => (req, res, next) => {
|
||||
if (!Array.isArray(candidates) || candidates.length === 0) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.user?.isSuperuser) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const permissions = req.userPermissions || {};
|
||||
const granted = candidates.some(({ key, level }) =>
|
||||
typeof key === 'string' && hasRequiredPermission(permissions, key, level || 'full')
|
||||
);
|
||||
|
||||
if (granted) {
|
||||
return next();
|
||||
}
|
||||
|
||||
return res.status(403).json({
|
||||
error: 'INSUFFICIENT_PERMISSIONS',
|
||||
permission: candidates.map((entry) => entry.key).filter(Boolean),
|
||||
requiredLevel: candidates.map((entry) => entry.level || 'full')
|
||||
});
|
||||
};
|
||||
|
||||
const logScriptOutput = (data, level) => {
|
||||
if (!data) return;
|
||||
const text = data.toString();
|
||||
@@ -1425,14 +1584,37 @@ app.use((req, res, next) => {
|
||||
return res.status(401).json({ error: 'UNAUTHORIZED' });
|
||||
}
|
||||
|
||||
const user = findUserById(session.userId);
|
||||
if (!user || !user.is_active) {
|
||||
const userRecord = getUserById(session.userId);
|
||||
if (!userRecord || !userRecord.isActive) {
|
||||
activeSessions.delete(token);
|
||||
clearAuthCookie(res);
|
||||
return res.status(401).json({ error: 'UNAUTHORIZED' });
|
||||
}
|
||||
|
||||
req.user = sanitizeUser(user);
|
||||
const userGroups = Array.isArray(userRecord.groups) ? userRecord.groups : [];
|
||||
const groupIds = userGroups
|
||||
.map((group) => Number(group.id))
|
||||
.filter((groupId) => Number.isFinite(groupId) && groupId > 0);
|
||||
|
||||
const isSuperuser = userGroups.some(
|
||||
(group) => typeof group?.name === 'string' && group.name.toLowerCase() === SUPERUSER_GROUP_NAME
|
||||
);
|
||||
|
||||
const permissionsMap = isSuperuser
|
||||
? getSuperuserPermissionMap()
|
||||
: getEffectivePermissionsForGroups(groupIds);
|
||||
|
||||
req.user = {
|
||||
id: userRecord.id,
|
||||
username: userRecord.username,
|
||||
email: userRecord.email,
|
||||
avatarColor: userRecord.avatarColor || null,
|
||||
groups: userGroups,
|
||||
isSuperuser,
|
||||
securityPhraseDownloadedAt: userRecord.securityPhraseDownloadedAt || null,
|
||||
requiresSecurityPhraseDownload: !userRecord.securityPhraseDownloadedAt
|
||||
};
|
||||
req.userPermissions = permissionsMap;
|
||||
req.authToken = token;
|
||||
touchSession(token);
|
||||
setAuthCookie(res, token);
|
||||
@@ -1618,7 +1800,7 @@ app.post('/api/setup/complete', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/setup/endpoints/:id', (req, res) => {
|
||||
app.delete('/api/setup/endpoints/:id', requirePermission('maintenance-server-delete', 'full'), (req, res) => {
|
||||
const { id } = req.params;
|
||||
const numericId = Number(id);
|
||||
if (!Number.isFinite(numericId)) {
|
||||
@@ -1643,7 +1825,7 @@ app.delete('/api/setup/endpoints/:id', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/setup/servers/:id', (req, res) => {
|
||||
app.delete('/api/setup/servers/:id', requirePermission('maintenance-server-delete', 'full'), (req, res) => {
|
||||
const { id } = req.params;
|
||||
const numericId = Number(id);
|
||||
if (!Number.isFinite(numericId)) {
|
||||
@@ -1668,7 +1850,7 @@ app.delete('/api/setup/servers/:id', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/setup/servers/:id/api-key', (req, res) => {
|
||||
app.put('/api/setup/servers/:id/api-key', requirePermission('maintenance-server-manage', 'full'), (req, res) => {
|
||||
const { id } = req.params;
|
||||
const numericId = Number(id);
|
||||
if (!Number.isFinite(numericId)) {
|
||||
@@ -1732,7 +1914,12 @@ app.post('/api/auth/login', (req, res) => {
|
||||
markUserLogin(user.id);
|
||||
setAuthCookie(res, session.token);
|
||||
|
||||
res.json({ user: sanitizeUser(user) });
|
||||
const payload = buildUserSessionPayload(user.id);
|
||||
if (payload) {
|
||||
res.json(payload);
|
||||
} else {
|
||||
res.json({ user: sanitizeUser(user), permissions: {} });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/auth/logout', (req, res) => {
|
||||
@@ -1744,6 +1931,74 @@ app.post('/api/auth/logout', (req, res) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.post('/api/auth/recover/verify', (req, res) => {
|
||||
const { username, phrase, words } = req.body ?? {};
|
||||
|
||||
try {
|
||||
const candidate = typeof phrase === 'string' && phrase.trim() ? phrase : words;
|
||||
const verification = verifySecurityPhraseForUsername(username, candidate);
|
||||
const ticket = createPasswordResetTicketForUser(verification.userId);
|
||||
res.json({ token: ticket.token, expiresIn: PASSWORD_RESET_TTL_MS });
|
||||
} catch (error) {
|
||||
if (error.code === 'USERNAME_REQUIRED' || error.code === 'PHRASE_REQUIRED') {
|
||||
return res.status(400).json({ error: error.code });
|
||||
}
|
||||
if (error.code === 'USER_NOT_FOUND') {
|
||||
return res.status(404).json({ error: 'USER_NOT_FOUND' });
|
||||
}
|
||||
if (error.code === 'PHRASE_NOT_INITIALIZED') {
|
||||
return res.status(409).json({ error: 'PHRASE_NOT_INITIALIZED' });
|
||||
}
|
||||
if (error.code === 'PHRASE_MISMATCH') {
|
||||
return res.status(401).json({ error: 'PHRASE_MISMATCH' });
|
||||
}
|
||||
if (error.code === 'INVALID_PASSWORD') {
|
||||
return res.status(400).json({ error: 'INVALID_PASSWORD' });
|
||||
}
|
||||
console.error('⚠️ [Auth] Sicherheitsphrase-Überprüfung fehlgeschlagen:', error);
|
||||
res.status(500).json({ error: 'RECOVERY_VERIFY_FAILED' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/auth/recover/reset', (req, res) => {
|
||||
const { token, password, confirmPassword } = req.body ?? {};
|
||||
|
||||
if (!token || typeof token !== 'string' || !token.trim()) {
|
||||
return res.status(400).json({ error: 'TOKEN_REQUIRED' });
|
||||
}
|
||||
|
||||
if (typeof password !== 'string' || !password.trim()) {
|
||||
return res.status(400).json({ error: 'PASSWORD_REQUIRED' });
|
||||
}
|
||||
|
||||
if (typeof confirmPassword === 'string' && password !== confirmPassword) {
|
||||
return res.status(400).json({ error: 'PASSWORD_MISMATCH' });
|
||||
}
|
||||
|
||||
const trimmedToken = token.trim();
|
||||
const ticket = getPasswordResetTicket(trimmedToken);
|
||||
if (!ticket) {
|
||||
return res.status(410).json({ error: 'TOKEN_INVALID_OR_EXPIRED' });
|
||||
}
|
||||
|
||||
try {
|
||||
setUserPassword(ticket.userId, password, { resetMethod: 'security-phrase' });
|
||||
passwordResetTickets.delete(trimmedToken);
|
||||
removeSessionsForUser(ticket.userId);
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
if (error.code === 'INVALID_PASSWORD' || error.code === 'PASSWORD_TOO_SHORT') {
|
||||
return res.status(400).json({ error: error.code });
|
||||
}
|
||||
if (error.code === 'USER_NOT_FOUND') {
|
||||
passwordResetTickets.delete(trimmedToken);
|
||||
return res.status(404).json({ error: 'USER_NOT_FOUND' });
|
||||
}
|
||||
console.error('⚠️ [Auth] Passwort-Zurücksetzung fehlgeschlagen:', error);
|
||||
res.status(500).json({ error: 'RECOVERY_RESET_FAILED' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/auth/session', (req, res) => {
|
||||
if (!hasSuperuser()) {
|
||||
return res.status(403).json({ error: 'SUPERUSER_REQUIRED' });
|
||||
@@ -1760,8 +2015,8 @@ app.get('/api/auth/session', (req, res) => {
|
||||
return res.status(401).json({ error: 'UNAUTHORIZED' });
|
||||
}
|
||||
|
||||
const user = findUserById(session.userId);
|
||||
if (!user || !user.is_active) {
|
||||
const payload = buildUserSessionPayload(session.userId);
|
||||
if (!payload) {
|
||||
activeSessions.delete(token);
|
||||
clearAuthCookie(res);
|
||||
return res.status(401).json({ error: 'UNAUTHORIZED' });
|
||||
@@ -1769,10 +2024,10 @@ app.get('/api/auth/session', (req, res) => {
|
||||
|
||||
touchSession(token);
|
||||
setAuthCookie(res, token);
|
||||
res.json({ user: sanitizeUser(user) });
|
||||
res.json(payload);
|
||||
});
|
||||
|
||||
app.get('/api/users', (req, res) => {
|
||||
app.get('/api/users', requirePermission('users-access', 'read'), (req, res) => {
|
||||
try {
|
||||
const users = listUsers();
|
||||
res.json({ items: users, total: users.length });
|
||||
@@ -1782,11 +2037,11 @@ app.get('/api/users', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/users', (req, res) => {
|
||||
app.post('/api/users', requirePermission('users-edit', 'full'), (req, res) => {
|
||||
const { username, email, password, groupId, avatarColor } = req.body ?? {};
|
||||
|
||||
try {
|
||||
const user = createUser({ username, email, password, groupId, avatarColor });
|
||||
const user = createUser({ username, email, password, groupId, avatarColor }, { actor: req.user });
|
||||
res.status(201).json({ item: user });
|
||||
} catch (error) {
|
||||
if (error.code === 'USERNAME_REQUIRED' || error.code === 'INVALID_GROUP_ID') {
|
||||
@@ -1809,7 +2064,62 @@ app.post('/api/users', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/users/:userId', (req, res) => {
|
||||
app.get('/api/users/me/security-phrase', (req, res) => {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'UNAUTHORIZED' });
|
||||
}
|
||||
|
||||
try {
|
||||
const phrase = getUserSecurityPhrase(userId, { actor: req.user });
|
||||
if (phrase.downloadedAt) {
|
||||
return res.status(409).json({
|
||||
error: 'SECURITY_PHRASE_ALREADY_DOWNLOADED',
|
||||
downloadedAt: phrase.downloadedAt
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
item: {
|
||||
userId: phrase.userId,
|
||||
words: phrase.words,
|
||||
downloadedAt: phrase.downloadedAt
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'USER_NOT_FOUND') {
|
||||
return res.status(404).json({ error: 'USER_NOT_FOUND' });
|
||||
}
|
||||
console.error(`⚠️ [Users] Sicherheitsschlüssel konnte für Benutzer ${userId} nicht geladen werden:`, error);
|
||||
res.status(500).json({ error: 'USER_SECURITY_PHRASE_FETCH_FAILED' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/users/me/security-phrase/downloaded', (req, res) => {
|
||||
const userId = req.user?.id;
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'UNAUTHORIZED' });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = markSecurityPhraseDownloaded(userId, { actor: req.user });
|
||||
const sessionPayload = buildUserSessionPayload(userId);
|
||||
res.json({
|
||||
item: {
|
||||
userId: result.userId,
|
||||
downloadedAt: result.downloadedAt
|
||||
},
|
||||
session: sessionPayload
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'USER_NOT_FOUND') {
|
||||
return res.status(404).json({ error: 'USER_NOT_FOUND' });
|
||||
}
|
||||
console.error(`⚠️ [Users] Sicherheits-Download konnte nicht vermerkt werden (${userId}):`, error);
|
||||
res.status(500).json({ error: 'USER_SECURITY_PHRASE_DOWNLOAD_FAILED' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/users/:userId', requirePermission('users-access', 'read'), (req, res) => {
|
||||
const { userId } = req.params;
|
||||
const numericId = Number(userId);
|
||||
|
||||
@@ -1829,7 +2139,53 @@ app.get('/api/users/:userId', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/users/:userId', (req, res) => {
|
||||
app.get('/api/users/:userId/security-phrase', requirePermission('users-security-phrase', 'read'), (req, res) => {
|
||||
const { userId } = req.params;
|
||||
const numericId = Number(userId);
|
||||
|
||||
if (!Number.isFinite(numericId) || numericId <= 0) {
|
||||
return res.status(400).json({ error: 'INVALID_USER_ID' });
|
||||
}
|
||||
|
||||
try {
|
||||
const phrase = getUserSecurityPhrase(numericId, { actor: req.user });
|
||||
res.json({ item: phrase });
|
||||
} catch (error) {
|
||||
if (error.code === 'INVALID_USER_ID') {
|
||||
return res.status(400).json({ error: 'INVALID_USER_ID' });
|
||||
}
|
||||
if (error.code === 'USER_NOT_FOUND') {
|
||||
return res.status(404).json({ error: 'USER_NOT_FOUND' });
|
||||
}
|
||||
console.error(`⚠️ [Users] Sicherheitsschlüssel konnte nicht geladen werden (${userId}):`, error);
|
||||
res.status(500).json({ error: 'USER_SECURITY_PHRASE_FETCH_FAILED' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/users/:userId/security-phrase/renew', requirePermission('users-security-phrase', 'full'), (req, res) => {
|
||||
const { userId } = req.params;
|
||||
const numericId = Number(userId);
|
||||
|
||||
if (!Number.isFinite(numericId) || numericId <= 0) {
|
||||
return res.status(400).json({ error: 'INVALID_USER_ID' });
|
||||
}
|
||||
|
||||
try {
|
||||
const renewed = renewUserSecurityPhrase(numericId, { actor: req.user });
|
||||
res.json({ item: renewed });
|
||||
} catch (error) {
|
||||
if (error.code === 'INVALID_USER_ID') {
|
||||
return res.status(400).json({ error: 'INVALID_USER_ID' });
|
||||
}
|
||||
if (error.code === 'USER_NOT_FOUND') {
|
||||
return res.status(404).json({ error: 'USER_NOT_FOUND' });
|
||||
}
|
||||
console.error(`⚠️ [Users] Sicherheitsschlüssel konnte nicht erneuert werden (${userId}):`, error);
|
||||
res.status(500).json({ error: 'USER_SECURITY_PHRASE_RENEW_FAILED' });
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/users/:userId', requirePermission('users-edit', 'full'), (req, res) => {
|
||||
const { userId } = req.params;
|
||||
const numericId = Number(userId);
|
||||
|
||||
@@ -1886,7 +2242,7 @@ app.put('/api/users/:userId', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/users/:userId/groups', (req, res) => {
|
||||
app.put('/api/users/:userId/groups', requirePermission('users-edit', 'full'), (req, res) => {
|
||||
const { userId } = req.params;
|
||||
const numericId = Number(userId);
|
||||
const { groupIds } = req.body ?? {};
|
||||
@@ -1913,7 +2269,7 @@ app.put('/api/users/:userId/groups', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/users/:userId', (req, res) => {
|
||||
app.delete('/api/users/:userId', requirePermission('users-delete', 'full'), (req, res) => {
|
||||
const { userId } = req.params;
|
||||
const numericId = Number(userId);
|
||||
|
||||
@@ -1940,7 +2296,7 @@ app.delete('/api/users/:userId', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/users/:userId/active', (req, res) => {
|
||||
app.put('/api/users/:userId/active', requirePermission('users-edit', 'full'), (req, res) => {
|
||||
const { userId } = req.params;
|
||||
const numericId = Number(userId);
|
||||
const { isActive } = req.body ?? {};
|
||||
@@ -1968,7 +2324,13 @@ app.put('/api/users/:userId/active', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/groups', (req, res) => {
|
||||
app.get(
|
||||
'/api/groups',
|
||||
requireAnyPermission([
|
||||
{ key: 'user-groups-access', level: 'read' },
|
||||
{ key: 'users-edit', level: 'read' }
|
||||
]),
|
||||
(req, res) => {
|
||||
try {
|
||||
const groups = listGroups();
|
||||
res.json({ items: groups, total: groups.length });
|
||||
@@ -1976,9 +2338,16 @@ app.get('/api/groups', (req, res) => {
|
||||
console.error('⚠️ [Groups] Abruf der Benutzergruppenliste fehlgeschlagen:', error);
|
||||
res.status(500).json({ error: 'GROUPS_FETCH_FAILED' });
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
app.get('/api/groups/:groupId', (req, res) => {
|
||||
app.get(
|
||||
'/api/groups/:groupId',
|
||||
requireAnyPermission([
|
||||
{ key: 'user-groups-access', level: 'read' },
|
||||
{ key: 'users-edit', level: 'read' }
|
||||
]),
|
||||
(req, res) => {
|
||||
const { groupId } = req.params;
|
||||
const numericId = Number(groupId);
|
||||
|
||||
@@ -1996,9 +2365,88 @@ app.get('/api/groups/:groupId', (req, res) => {
|
||||
console.error(`⚠️ [Groups] Abruf der Gruppendetails fehlgeschlagen (${groupId}):`, error);
|
||||
res.status(500).json({ error: 'GROUP_FETCH_FAILED' });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.get('/api/groups/:groupId/permissions', requirePermission('user-groups-edit', 'read'), (req, res) => {
|
||||
const { groupId } = req.params;
|
||||
const numericId = Number(groupId);
|
||||
|
||||
if (!Number.isFinite(numericId) || numericId <= 0) {
|
||||
return res.status(400).json({ error: 'INVALID_GROUP_ID' });
|
||||
}
|
||||
|
||||
try {
|
||||
const group = getGroupById(numericId);
|
||||
if (!group) {
|
||||
return res.status(404).json({ error: 'GROUP_NOT_FOUND' });
|
||||
}
|
||||
|
||||
const sections = getPermissionStructure();
|
||||
const isSuperuser = (group.name || '').toLowerCase() === SUPERUSER_GROUP_NAME;
|
||||
let values = {};
|
||||
|
||||
if (isSuperuser) {
|
||||
clearGroupPermissionValues(numericId);
|
||||
} else {
|
||||
values = getPermissionValuesByGroup(numericId);
|
||||
}
|
||||
|
||||
res.json({ sections, values });
|
||||
} catch (error) {
|
||||
console.error(`⚠️ [Groups] Abruf der Gruppenberechtigungen fehlgeschlagen (${groupId}):`, error);
|
||||
res.status(500).json({ error: 'GROUP_PERMISSIONS_FETCH_FAILED' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/groups', (req, res) => {
|
||||
app.put('/api/groups/:groupId/permissions', requirePermission('user-groups-edit', 'full'), (req, res) => {
|
||||
const { groupId } = req.params;
|
||||
const numericId = Number(groupId);
|
||||
|
||||
if (!Number.isFinite(numericId) || numericId <= 0) {
|
||||
return res.status(400).json({ error: 'INVALID_GROUP_ID' });
|
||||
}
|
||||
|
||||
try {
|
||||
const group = getGroupById(numericId);
|
||||
if (!group) {
|
||||
return res.status(404).json({ error: 'GROUP_NOT_FOUND' });
|
||||
}
|
||||
|
||||
const isSuperuser = (group.name || '').toLowerCase() === SUPERUSER_GROUP_NAME;
|
||||
if (isSuperuser) {
|
||||
clearGroupPermissionValues(numericId);
|
||||
return res.status(403).json({ error: 'GROUP_SUPERUSER_PROTECTED' });
|
||||
}
|
||||
|
||||
const valuesPayload = req.body?.values ?? {};
|
||||
const savedValues = saveGroupPermissionValues(numericId, valuesPayload);
|
||||
|
||||
res.json({ success: true, values: savedValues });
|
||||
} catch (error) {
|
||||
const serverError = error?.code;
|
||||
if (serverError === 'GROUP_NOT_FOUND') {
|
||||
return res.status(404).json({ error: 'GROUP_NOT_FOUND' });
|
||||
}
|
||||
if (serverError === 'PERMISSION_INVALID_PAYLOAD') {
|
||||
return res.status(400).json({ error: 'PERMISSION_INVALID_PAYLOAD' });
|
||||
}
|
||||
if (serverError === 'PERMISSION_INVALID_LEVEL') {
|
||||
return res.status(400).json({ error: 'PERMISSION_INVALID_LEVEL', permissionKey: error.permissionKey, level: error.level });
|
||||
}
|
||||
if (serverError === 'PERMISSION_UNKNOWN_KEY') {
|
||||
return res.status(400).json({ error: 'PERMISSION_UNKNOWN_KEY', permissionKey: error.permissionKey });
|
||||
}
|
||||
if (serverError === 'INVALID_GROUP_ID') {
|
||||
return res.status(400).json({ error: 'INVALID_GROUP_ID' });
|
||||
}
|
||||
|
||||
console.error(`⚠️ [Groups] Aktualisierung der Berechtigungen fehlgeschlagen (${groupId}):`, error);
|
||||
res.status(500).json({ error: 'GROUP_PERMISSIONS_UPDATE_FAILED' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/groups', requirePermission('user-groups-edit', 'full'), (req, res) => {
|
||||
const { name, description } = req.body ?? {};
|
||||
try {
|
||||
const group = createGroup({ name, description });
|
||||
@@ -2015,7 +2463,7 @@ app.post('/api/groups', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/groups/:groupId', (req, res) => {
|
||||
app.put('/api/groups/:groupId', requirePermission('user-groups-edit', 'full'), (req, res) => {
|
||||
const { groupId } = req.params;
|
||||
const numericId = Number(groupId);
|
||||
|
||||
@@ -2053,7 +2501,7 @@ app.put('/api/groups/:groupId', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/groups/:groupId', (req, res) => {
|
||||
app.delete('/api/groups/:groupId', requirePermission('user-groups-delete', 'full'), (req, res) => {
|
||||
const { groupId } = req.params;
|
||||
const numericId = Number(groupId);
|
||||
|
||||
@@ -2197,7 +2645,7 @@ app.get('/api/stacks', maintenanceGuard, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/maintenance/portainer-status', async (req, res) => {
|
||||
app.get('/api/maintenance/portainer-status', requirePermission('maintenance-portainer', 'read'), async (req, res) => {
|
||||
console.log("🧭 [Maintenance] GET /api/maintenance/portainer-status: Prüfung gestartet");
|
||||
try {
|
||||
const payload = await fetchPortainerStatusSummary();
|
||||
@@ -2212,7 +2660,7 @@ app.get('/api/maintenance/portainer-status', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/maintenance/mode', (req, res) => {
|
||||
app.post('/api/maintenance/mode', requirePermission('maintenance-access', 'full'), (req, res) => {
|
||||
try {
|
||||
const { active, message } = req.body ?? {};
|
||||
const normalizedMessage = typeof message === 'string' && message.trim() ? message.trim() : null;
|
||||
@@ -2231,7 +2679,7 @@ app.post('/api/maintenance/mode', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/maintenance/config', (req, res) => {
|
||||
app.get('/api/maintenance/config', requirePermission('maintenance-server-manage', 'read'), (req, res) => {
|
||||
const custom = getCustomPortainerScript();
|
||||
const effective = getEffectivePortainerScript();
|
||||
const ssh = getPortainerSshConfig();
|
||||
@@ -2257,7 +2705,7 @@ app.get('/api/maintenance/config', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.put('/api/maintenance/ssh-config', (req, res) => {
|
||||
app.put('/api/maintenance/ssh-config', requirePermission('maintenance-ssh-update', 'full'), (req, res) => {
|
||||
try {
|
||||
const config = savePortainerSshConfig(req.body || {});
|
||||
res.json({
|
||||
@@ -2276,7 +2724,7 @@ app.put('/api/maintenance/ssh-config', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/maintenance/ssh-config', (req, res) => {
|
||||
app.delete('/api/maintenance/ssh-config', requirePermission('maintenance-ssh-update', 'full'), (req, res) => {
|
||||
const config = deletePortainerSshConfig();
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -2290,7 +2738,7 @@ app.delete('/api/maintenance/ssh-config', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/maintenance/test-ssh', async (req, res) => {
|
||||
app.post('/api/maintenance/test-ssh', requirePermission('maintenance-ssh-update', 'full'), async (req, res) => {
|
||||
try {
|
||||
const override = req.body && Object.keys(req.body).length ? req.body : null;
|
||||
const result = await testSshConnection(override);
|
||||
@@ -2301,7 +2749,7 @@ app.post('/api/maintenance/test-ssh', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.put('/api/maintenance/update-script', (req, res) => {
|
||||
app.put('/api/maintenance/update-script', requirePermission('maintenance-ssh-update', 'full'), (req, res) => {
|
||||
if (portainerUpdateState.running) {
|
||||
return res.status(409).json({
|
||||
error: 'Aktualisierung läuft. Skript kann derzeit nicht geändert werden.',
|
||||
@@ -2332,7 +2780,7 @@ app.put('/api/maintenance/update-script', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.delete('/api/maintenance/update-script', (req, res) => {
|
||||
app.delete('/api/maintenance/update-script', requirePermission('maintenance-ssh-update', 'full'), (req, res) => {
|
||||
if (portainerUpdateState.running) {
|
||||
return res.status(409).json({
|
||||
error: 'Aktualisierung läuft. Skript kann derzeit nicht geändert werden.',
|
||||
@@ -2356,14 +2804,14 @@ app.delete('/api/maintenance/update-script', (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/maintenance/update-status', (req, res) => {
|
||||
app.get('/api/maintenance/update-status', requirePermission('maintenance-update', 'read'), (req, res) => {
|
||||
res.json({
|
||||
maintenance: getMaintenanceState(),
|
||||
update: getPortainerUpdateStatus()
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/maintenance/portainer-update', async (req, res) => {
|
||||
app.post('/api/maintenance/portainer-update', requirePermission('maintenance-update', 'full'), async (req, res) => {
|
||||
if (portainerUpdateState.running) {
|
||||
return res.status(409).json({
|
||||
error: 'Ein Portainer-Update läuft bereits.',
|
||||
@@ -2429,7 +2877,11 @@ app.post('/api/maintenance/portainer-update', async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/maintenance/duplicates', maintenanceGuard, async (req, res) => {
|
||||
app.get(
|
||||
'/api/maintenance/duplicates',
|
||||
maintenanceGuard,
|
||||
requirePermission('maintenance-duplicates', 'read'),
|
||||
async (req, res) => {
|
||||
console.log("🧹 [Maintenance] GET /api/maintenance/duplicates: Abruf gestartet");
|
||||
try {
|
||||
const { duplicates } = await loadStackCollections();
|
||||
@@ -2464,7 +2916,11 @@ app.get('/api/maintenance/duplicates', maintenanceGuard, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/maintenance/duplicates/cleanup', maintenanceGuard, async (req, res) => {
|
||||
app.post(
|
||||
'/api/maintenance/duplicates/cleanup',
|
||||
maintenanceGuard,
|
||||
requirePermission('maintenance-duplicates', 'full'),
|
||||
async (req, res) => {
|
||||
const canonicalId = req.body?.canonicalId;
|
||||
const duplicateIdsInput = Array.isArray(req.body?.duplicateIds) ? req.body.duplicateIds : [];
|
||||
const duplicateIds = duplicateIdsInput
|
||||
@@ -2592,7 +3048,7 @@ app.post('/api/maintenance/duplicates/cleanup', maintenanceGuard, async (req, re
|
||||
});
|
||||
|
||||
// Event-Logs abrufen
|
||||
app.get('/api/logs', (req, res) => {
|
||||
app.get('/api/logs', requirePermission('logs-access', 'read'), (req, res) => {
|
||||
const perPageParam = req.query.perPage ?? req.query.limit;
|
||||
const perPage = perPageParam === 'all' ? 'all' : Math.min(parseInt(perPageParam, 10) || 50, 500);
|
||||
const page = Math.max(parseInt(req.query.page, 10) || 1, 1);
|
||||
@@ -2677,7 +3133,7 @@ app.get('/api/logs', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/logs/:id', (req, res) => {
|
||||
app.delete('/api/logs/:id', requirePermission('logs-delete', 'full'), (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
if (Number.isNaN(id)) {
|
||||
return res.status(400).json({ error: 'Ungültige ID' });
|
||||
@@ -2695,7 +3151,7 @@ app.delete('/api/logs/:id', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/logs', (req, res) => {
|
||||
app.delete('/api/logs', requirePermission('logs-delete', 'full'), (req, res) => {
|
||||
try {
|
||||
const deleted = deleteEventLogsByFilters(req.query);
|
||||
res.json({ success: true, deleted });
|
||||
@@ -2705,7 +3161,7 @@ app.delete('/api/logs', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/logs/export', (req, res) => {
|
||||
app.get('/api/logs/export', requirePermission('logs-export', 'full'), (req, res) => {
|
||||
const format = (req.query.format || 'txt').toLowerCase();
|
||||
if (!['txt', 'sql'].includes(format)) {
|
||||
return res.status(400).json({ error: 'Ungültiges Export-Format' });
|
||||
@@ -2723,7 +3179,7 @@ app.get('/api/logs/export', (req, res) => {
|
||||
});
|
||||
|
||||
// Einzel-Redeploy
|
||||
app.put('/api/stacks/:id/redeploy', async (req, res) => {
|
||||
app.put('/api/stacks/:id/redeploy', requirePermission('stacks-redeploy-single', 'full'), async (req, res) => {
|
||||
const { id } = req.params;
|
||||
console.log(`🔄 PUT /api/stacks/${id}/redeploy: Redeploy gestartet`);
|
||||
|
||||
@@ -2739,7 +3195,11 @@ app.put('/api/stacks/:id/redeploy', async (req, res) => {
|
||||
});
|
||||
|
||||
// Redeploy ALL
|
||||
app.put('/api/stacks/redeploy-all', maintenanceGuard, async (req, res) => {
|
||||
app.put(
|
||||
'/api/stacks/redeploy-all',
|
||||
maintenanceGuard,
|
||||
requirePermission('stacks-redeploy-all', 'full'),
|
||||
async (req, res) => {
|
||||
console.log(`🚀 PUT /api/stacks/redeploy-all: Redeploy ALL gestartet`);
|
||||
|
||||
let endpointId;
|
||||
@@ -2840,7 +3300,11 @@ app.put('/api/stacks/redeploy-all', maintenanceGuard, async (req, res) => {
|
||||
});
|
||||
|
||||
// Redeploy selection
|
||||
app.put('/api/stacks/redeploy-selection', maintenanceGuard, async (req, res) => {
|
||||
app.put(
|
||||
'/api/stacks/redeploy-selection',
|
||||
maintenanceGuard,
|
||||
requirePermission('stacks-redeploy-selection', 'full'),
|
||||
async (req, res) => {
|
||||
const { stackIds } = req.body || {};
|
||||
const totalCount = Array.isArray(stackIds) ? stackIds.length : 0;
|
||||
console.log(`🚀 PUT /api/stacks/redeploy-selection: Redeploy Auswahl gestartet (${totalCount} Stacks)`);
|
||||
|
||||
Generated
+32
@@ -8,6 +8,7 @@
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@scure/bip39": "^2.0.0",
|
||||
"axios": "^1.7.0",
|
||||
"better-sqlite3": "^9.6.0",
|
||||
"dockerode": "^4.0.7",
|
||||
@@ -59,6 +60,17 @@
|
||||
"url": "https://opencollective.com/js-sdsl"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz",
|
||||
"integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==",
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
@@ -113,6 +125,26 @@
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
},
|
||||
"node_modules/@scure/base": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz",
|
||||
"integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/bip39": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-2.0.1.tgz",
|
||||
"integrity": "sha512-PsxdFj/d2AcJcZDX1FXN3dDgitDDTmwf78rKZq1a6c1P1Nan1X/Sxc7667zU3U+AN60g7SxxP0YCVw2H/hBycg==",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "2.0.1",
|
||||
"@scure/base": "2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dockerode": "^4.0.7",
|
||||
"dotenv": "^16.0.0",
|
||||
"express": "^4.18.0",
|
||||
"socket.io": "^4.8.1"
|
||||
"socket.io": "^4.8.1",
|
||||
"@scure/bip39": "^2.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
import { db } from '../db/index.js';
|
||||
|
||||
const LEVEL_PRIORITY = {
|
||||
none: 0,
|
||||
read: 1,
|
||||
full: 2
|
||||
};
|
||||
|
||||
const selectSections = db.prepare(`
|
||||
SELECT id, key, title, sort_order, has_navigation_flag
|
||||
FROM permission_sections
|
||||
ORDER BY sort_order ASC, id ASC
|
||||
`);
|
||||
|
||||
const selectGroups = db.prepare(`
|
||||
SELECT id, section_id, key, title, sort_order
|
||||
FROM permission_groups
|
||||
ORDER BY section_id ASC, sort_order ASC, id ASC
|
||||
`);
|
||||
|
||||
const selectItems = db.prepare(`
|
||||
SELECT
|
||||
id,
|
||||
section_id,
|
||||
group_id,
|
||||
key,
|
||||
label,
|
||||
sort_order,
|
||||
default_level,
|
||||
available_levels,
|
||||
is_required
|
||||
FROM permission_items
|
||||
ORDER BY section_id ASC, COALESCE(group_id, 0) ASC, sort_order ASC, id ASC
|
||||
`);
|
||||
|
||||
const selectDependencies = db.prepare(`
|
||||
SELECT permission_id, depends_on_permission_id, required_level
|
||||
FROM permission_dependencies
|
||||
`);
|
||||
|
||||
const selectPermissionItemsForMap = db.prepare(`
|
||||
SELECT id, key, available_levels, default_level
|
||||
FROM permission_items
|
||||
`);
|
||||
|
||||
const selectGroupPermissionValues = db.prepare(`
|
||||
SELECT gp.permission_id, gp.level, gp.effective_level, pi.key
|
||||
FROM group_permission_values gp
|
||||
JOIN permission_items pi ON pi.id = gp.permission_id
|
||||
WHERE gp.group_id = ?
|
||||
`);
|
||||
|
||||
const deleteGroupPermissionValuesStmt = db.prepare(`
|
||||
DELETE FROM group_permission_values
|
||||
WHERE group_id = ?
|
||||
`);
|
||||
|
||||
const insertGroupPermissionValueStmt = db.prepare(`
|
||||
INSERT INTO group_permission_values (group_id, permission_id, level, effective_level, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
`);
|
||||
|
||||
const parseAvailableLevels = (raw) => {
|
||||
if (typeof raw !== 'string' || !raw.trim()) {
|
||||
return ['full', 'read', 'none'];
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (Array.isArray(parsed) && parsed.every((entry) => typeof entry === 'string')) {
|
||||
return parsed;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Konnte available_levels nicht parsen:', error.message);
|
||||
}
|
||||
return ['full', 'read', 'none'];
|
||||
};
|
||||
|
||||
const getLevelPriority = (level) => {
|
||||
if (!level) return 0;
|
||||
return LEVEL_PRIORITY[level] ?? 0;
|
||||
};
|
||||
|
||||
let cachedPermissionItems = null;
|
||||
|
||||
const getPermissionItems = () => {
|
||||
if (!cachedPermissionItems) {
|
||||
cachedPermissionItems = selectPermissionItemsForMap.all();
|
||||
}
|
||||
return cachedPermissionItems;
|
||||
};
|
||||
|
||||
const resetPermissionItemsCache = () => {
|
||||
cachedPermissionItems = null;
|
||||
};
|
||||
|
||||
export function getPermissionStructure() {
|
||||
const sections = selectSections.all();
|
||||
const groups = selectGroups.all();
|
||||
const items = selectItems.all();
|
||||
const dependencies = selectDependencies.all();
|
||||
|
||||
const itemIdToKey = new Map(items.map((item) => [item.id, item.key]));
|
||||
|
||||
const dependenciesByItemId = new Map();
|
||||
dependencies.forEach((dependency) => {
|
||||
const list = dependenciesByItemId.get(dependency.permission_id) || [];
|
||||
list.push(dependency);
|
||||
dependenciesByItemId.set(dependency.permission_id, list);
|
||||
});
|
||||
|
||||
const groupsBySectionId = new Map();
|
||||
groups.forEach((group) => {
|
||||
const list = groupsBySectionId.get(group.section_id) || [];
|
||||
list.push(group);
|
||||
groupsBySectionId.set(group.section_id, list);
|
||||
});
|
||||
|
||||
const itemsBySectionId = new Map();
|
||||
const itemsByGroupId = new Map();
|
||||
|
||||
items.forEach((item) => {
|
||||
if (item.group_id) {
|
||||
const list = itemsByGroupId.get(item.group_id) || [];
|
||||
list.push(item);
|
||||
itemsByGroupId.set(item.group_id, list);
|
||||
} else {
|
||||
const list = itemsBySectionId.get(item.section_id) || [];
|
||||
list.push(item);
|
||||
itemsBySectionId.set(item.section_id, list);
|
||||
}
|
||||
});
|
||||
|
||||
const formatItem = (item) => {
|
||||
const availableLevels = parseAvailableLevels(item.available_levels);
|
||||
|
||||
const dependencyEntries = dependenciesByItemId.get(item.id) || [];
|
||||
const formattedDependencies = dependencyEntries
|
||||
.map((dependency) => {
|
||||
const dependsOnKey = itemIdToKey.get(dependency.depends_on_permission_id);
|
||||
if (!dependsOnKey) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
key: dependsOnKey,
|
||||
requiredLevel: dependency.required_level || null
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
return {
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
sortOrder: item.sort_order ?? 0,
|
||||
defaultLevel: item.default_level || 'none',
|
||||
availableLevels,
|
||||
isRequired: Boolean(item.is_required),
|
||||
dependencies: formattedDependencies
|
||||
};
|
||||
};
|
||||
|
||||
const formattedSections = sections.map((section) => {
|
||||
const sectionItems = (itemsBySectionId.get(section.id) || []).map(formatItem);
|
||||
const sectionGroups = (groupsBySectionId.get(section.id) || []).map((group) => ({
|
||||
key: group.key,
|
||||
title: group.title,
|
||||
sortOrder: group.sort_order ?? 0,
|
||||
items: (itemsByGroupId.get(group.id) || []).map(formatItem)
|
||||
}));
|
||||
|
||||
return {
|
||||
key: section.key,
|
||||
title: section.title,
|
||||
sortOrder: section.sort_order ?? 0,
|
||||
hasNavigation: Boolean(section.has_navigation_flag),
|
||||
items: sectionItems,
|
||||
groups: sectionGroups
|
||||
};
|
||||
});
|
||||
|
||||
return formattedSections;
|
||||
}
|
||||
|
||||
export function getPermissionValuesByGroup(groupId) {
|
||||
const numericId = Number(groupId);
|
||||
if (!Number.isFinite(numericId) || numericId <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const rows = selectGroupPermissionValues.all(numericId);
|
||||
const values = {};
|
||||
rows.forEach((row) => {
|
||||
if (row?.key && typeof row.level === 'string') {
|
||||
values[row.key] = row.level;
|
||||
}
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
export function clearGroupPermissionValues(groupId) {
|
||||
const numericId = Number(groupId);
|
||||
if (!Number.isFinite(numericId) || numericId <= 0) {
|
||||
return;
|
||||
}
|
||||
deleteGroupPermissionValuesStmt.run(numericId);
|
||||
}
|
||||
|
||||
export function saveGroupPermissionValues(groupId, values = {}) {
|
||||
const numericId = Number(groupId);
|
||||
if (!Number.isFinite(numericId) || numericId <= 0) {
|
||||
const error = new Error('INVALID_GROUP_ID');
|
||||
error.code = 'INVALID_GROUP_ID';
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (values === null || typeof values !== 'object' || Array.isArray(values)) {
|
||||
const error = new Error('PERMISSION_INVALID_PAYLOAD');
|
||||
error.code = 'PERMISSION_INVALID_PAYLOAD';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const items = getPermissionItems();
|
||||
const itemMap = new Map();
|
||||
items.forEach((item) => {
|
||||
itemMap.set(item.key, {
|
||||
id: item.id,
|
||||
key: item.key,
|
||||
defaultLevel: item.default_level || 'none',
|
||||
availableLevels: parseAvailableLevels(item.available_levels)
|
||||
});
|
||||
});
|
||||
|
||||
const entries = Object.entries(values);
|
||||
const normalizedEntries = [];
|
||||
|
||||
entries.forEach(([key, rawValue]) => {
|
||||
const item = itemMap.get(key);
|
||||
if (!item) {
|
||||
const error = new Error('PERMISSION_UNKNOWN_KEY');
|
||||
error.code = 'PERMISSION_UNKNOWN_KEY';
|
||||
error.permissionKey = key;
|
||||
throw error;
|
||||
}
|
||||
|
||||
let level = typeof rawValue === 'string' ? rawValue.trim() : String(rawValue ?? '').trim();
|
||||
if (!level) {
|
||||
level = 'none';
|
||||
}
|
||||
|
||||
if (!item.availableLevels.includes(level)) {
|
||||
const error = new Error('PERMISSION_INVALID_LEVEL');
|
||||
error.code = 'PERMISSION_INVALID_LEVEL';
|
||||
error.permissionKey = key;
|
||||
error.level = level;
|
||||
throw error;
|
||||
}
|
||||
|
||||
normalizedEntries.push({ item, level });
|
||||
});
|
||||
|
||||
const runSave = db.transaction(() => {
|
||||
deleteGroupPermissionValuesStmt.run(numericId);
|
||||
normalizedEntries.forEach(({ item, level }) => {
|
||||
if (level === (item.defaultLevel || 'none')) {
|
||||
return;
|
||||
}
|
||||
insertGroupPermissionValueStmt.run(numericId, item.id, level, level);
|
||||
});
|
||||
});
|
||||
|
||||
runSave();
|
||||
resetPermissionItemsCache();
|
||||
|
||||
return getPermissionValuesByGroup(numericId);
|
||||
}
|
||||
|
||||
export function getDefaultPermissionMap() {
|
||||
const items = getPermissionItems();
|
||||
const defaults = {};
|
||||
items.forEach((item) => {
|
||||
defaults[item.key] = item.default_level || 'none';
|
||||
});
|
||||
return defaults;
|
||||
}
|
||||
|
||||
export function getSuperuserPermissionMap() {
|
||||
const items = getPermissionItems();
|
||||
const result = {};
|
||||
|
||||
items.forEach((item) => {
|
||||
const available = parseAvailableLevels(item.available_levels);
|
||||
let chosen = 'full';
|
||||
if (!available.includes('full')) {
|
||||
chosen = available.reduce(
|
||||
(best, candidate) =>
|
||||
getLevelPriority(candidate) > getLevelPriority(best) ? candidate : best,
|
||||
'none'
|
||||
);
|
||||
}
|
||||
result[item.key] = chosen;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getEffectivePermissionsForGroup(groupId) {
|
||||
const defaults = getDefaultPermissionMap();
|
||||
const numericId = Number(groupId);
|
||||
if (!Number.isFinite(numericId) || numericId <= 0) {
|
||||
return defaults;
|
||||
}
|
||||
|
||||
const overrides = getPermissionValuesByGroup(numericId);
|
||||
const map = { ...defaults };
|
||||
|
||||
Object.keys(overrides).forEach((key) => {
|
||||
const value = overrides[key];
|
||||
if (typeof value === 'string') {
|
||||
map[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
export function getEffectivePermissionsForGroups(groupIds = []) {
|
||||
const defaults = getDefaultPermissionMap();
|
||||
const finalMap = { ...defaults };
|
||||
|
||||
const iterableIds = Array.isArray(groupIds) ? groupIds : [];
|
||||
iterableIds
|
||||
.map((value) => Number(value))
|
||||
.filter((value) => Number.isFinite(value) && value > 0)
|
||||
.forEach((groupId) => {
|
||||
const map = getEffectivePermissionsForGroup(groupId);
|
||||
Object.entries(map).forEach(([key, level]) => {
|
||||
if (getLevelPriority(level) > getLevelPriority(finalMap[key] ?? 'none')) {
|
||||
finalMap[key] = level;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return finalMap;
|
||||
}
|
||||
|
||||
export function hasRequiredPermission(permissions = {}, permissionKey, requiredLevel = 'full') {
|
||||
if (!permissionKey) {
|
||||
return true;
|
||||
}
|
||||
if (requiredLevel === 'none' || requiredLevel === null || requiredLevel === undefined) {
|
||||
return true;
|
||||
}
|
||||
const current = typeof permissions[permissionKey] === 'string' ? permissions[permissionKey] : 'none';
|
||||
const required = typeof requiredLevel === 'string' ? requiredLevel : 'full';
|
||||
return getLevelPriority(current) >= getLevelPriority(required);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#eee}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#bdbdbd}input::placeholder,textarea::placeholder{opacity:1;color:#bdbdbd}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 540px){.container{max-width:540px}}@media (min-width: 720px){.container{max-width:720px}}@media (min-width: 960px){.container{max-width:960px}}@media (min-width: 1140px){.container{max-width:1140px}}@media (min-width: 1320px){.container{max-width:1320px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.\!invisible{visibility:hidden!important}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.\!absolute{position:absolute!important}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bLine truncated
|
||||
@@ -1,93 +0,0 @@
|
||||
function Aj(e,t){for(var r=0;r<t.length;r++){const n=t[r];if(typeof n!="string"&&!Array.isArray(n)){for(const o in n)if(o!=="default"&&!(o in e)){const a=Object.getOwnPropertyDescriptor(n,o);a&&Object.defineProperty(e,o,a.get?a:{enumerable:!0,get:()=>n[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))n(o);new MutationObserver(o=>{for(const a of o)if(a.type==="childList")for(const i of a.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&n(i)}).observe(document,{childList:!0,subtree:!0});function r(o){const a={};return o.integrity&&(a.integrity=o.integrity),o.referrerPolicy&&(a.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?a.credentials="include":o.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function n(o){if(o.ep)return;o.ep=!0;const a=r(o);fetch(o.href,a)}})();function Ax(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function Qs(e){if(e.__esModule)return e;var t=e.default;if(typeof t=="function"){var r=function n(){return this instanceof n?Reflect.construct(t,arguments,this.constructor):t.apply(this,arguments)};r.prototype=t.prototype}else r={};return Object.defineProperty(r,"__esModule",{value:!0}),Object.keys(e).forEach(function(n){var o=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(r,n,o.get?o:{enumerable:!0,get:function(){return e[n]}})}),r}var Mx={exports:{}},Ad={},Ix={exports:{}},dt={};/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Xs=Symbol.for("react.element"),Mj=Symbol.for("react.portal"),Ij=Symbol.for("react.fragment"),Dj=Symbol.for("react.strict_mode"),Lj=Symbol.for("react.profiler"),Fj=Symbol.for("react.provider"),zj=Symbol.for("react.context"),Bj=Symbol.for("react.forward_ref"),Vj=Symbol.for("react.suspense"),Uj=Symbol.for("react.memo"),Wj=Symbol.for("react.lazy"),Uv=Symbol.iterator;function Hj(e){return e===null||typeof e!="object"?null:(e=Uv&&e[Uv]||e["@@iterator"],typeof e=="function"?e:null)}var Dx={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Lx=Object.assign,Fx={};function fl(e,t,r){this.props=e,this.context=t,this.refs=Fx,this.updater=r||Dx}fl.prototype.isReactComponent={};fl.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};fl.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function zx(){}zx.prototype=fl.prototype;function $h(e,t,r){this.props=e,this.context=t,this.refs=Fx,this.updater=r||Dx}var qh=$h.prototype=new zx;qh.constructor=$h;Lx(qh,fl.prototype);qh.isPureReactComponent=!0;var Wv=Array.isArray,Bx=Object.prototype.hasOwnProperty,Gh={current:null},Vx={key:!0,ref:!0,__self:!0,__source:!0};function Ux(e,t,r){var n,o={},a=null,i=null;if(t!=null)for(n in t.ref!==void 0&&(i=t.ref),t.key!==void 0&&(a=""+t.key),t)Bx.call(t,n)&&!Vx.hasOwnProperty(n)&&(o[n]=t[n]);var u=arguments.length-2;if(u===1)o.children=r;else if(1<u){for(var f=Array(u),g=0;g<u;g++)f[g]=arguments[g+2];o.children=f}if(e&&e.defaultProps)for(n in u=e.defaultProps,u)o[n]===void 0&&(o[n]=u[n]);return{$$typeof:Xs,type:e,key:a,ref:i,props:o,_owner:Gh.current}}function $j(e,t){return{$$typeof:Xs,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}function Kh(e){return typeof e=="object"&&e!==null&&e.$$typeof===Xs}function qj(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/[=:]/g,function(r){return t[r]})}var Hv=/\/+/g;function pp(e,t){return typeof e=="object"&&e!==null&&e.key!=null?qj(""+e.key):t.toString(36)}function ic(e,t,r,n,o){var a=typeof e;(a==="undefined"||a==="boolean")&&(e=null);var i=!1;if(e===null)i=!0;else switch(a){case"string":case"number":i=!0;break;case"object":switch(e.$$typeof){case Xs:case Mj:i=!0}}if(i)return i=e,o=o(i),e=n===""?"."+pp(i,0):n,Wv(o)?(r="",e!=null&&(r=e.replace(Hv,"$&/")+"/"),ic(o,t,r,"",function(g){return g})):o!=null&&(Kh(o)&&(o=$j(o,r+(!o.key||i&&i.key===o.key?"":(""+o.key).replace(Hv,"$&/")+"/")+e)),t.push(o)),1;if(i=0,n=n===""?".":n+":",Wv(e))for(var u=0;u<e.length;u++){a=e[u];var f=n+pp(a,u);i+=ic(a,t,r,f,o)}else if(f=Hj(e),typeof f=="function")for(e=f.call(e),u=0;!(a=e.next()).done;)a=a.value,f=n+pp(a,u++),i+=ic(a,t,r,f,o);else if(a==="object")throw t=String(e),Error("Objects are not valid as a React child (found: "+(t==="[object Object]"?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.");return i}function Ru(e,t,r){if(e==null)return e;var n=[],o=0;return ic(e,n,"","",function(a){return t.call(r,a,o++)}),n}function Gj(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(r){(e._status===0||e._status===-1)&&(e._status=1,e._result=r)},function(r){(e._status===0||e._status===-1)&&(e._status=2,e._result=r)}),e._status===-1&&(e._status=0,e._result=t)}if(e._status===1)return e._result.default;throw e._result}var Dr={current:null},lc={transition:null},Kj={ReactCurrentDispatcher:Dr,ReactCurrentBatchConfig:lc,ReactCurrentOwner:Gh};dt.Children={map:Ru,forEach:function(e,t,r){Ru(e,function(){t.apply(this,arguments)},r)},count:function(e){var t=0;return Ru(e,function(){t++}),t},toArray:function(e){return Ru(e,function(t){return t})||[]},only:function(e){if(!Kh(e))throw Error("React.Children.only expected to receive a single React element child.");return e}};dt.Component=fl;dt.Fragment=Ij;dt.Profiler=Lj;dt.PureComponent=$h;dt.StrictMode=Dj;dt.Suspense=Vj;dt.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=Kj;dt.cloneElement=function(e,t,r){if(e==null)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+e+".");var n=Lx({},e.props),o=e.key,a=e.ref,i=e._owner;if(t!=null){if(t.ref!==void 0&&(a=t.ref,i=Gh.current),t.key!==void 0&&(o=""+t.key),e.type&&e.type.defaultProps)var u=e.type.defaultProps;for(f in t)Bx.call(t,f)&&!Vx.hasOwnProperty(f)&&(n[f]=t[f]===void 0&&u!==void 0?u[f]:t[f])}var f=arguments.length-2;if(f===1)n.children=r;else if(1<f){u=Array(f);for(var g=0;g<f;g++)u[g]=arguments[g+2];n.children=u}return{$$typeof:Xs,type:e.type,key:o,ref:a,props:n,_owner:i}};dt.createContext=function(e){return e={$$typeof:zj,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null},e.Provider={$$typeof:Fj,_conLine truncated
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Yj=j,Qj=Symbol.for("react.element"),Xj=Symbol.for("react.fragment"),Jj=Object.prototype.hasOwnProperty,Zj=Yj.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,e2={key:!0,ref:!0,__self:!0,__source:!0};function Wx(e,t,r){var n,o={},a=null,i=null;r!==void 0&&(a=""+r),t.key!==void 0&&(a=""+t.key),t.ref!==void 0&&(i=t.ref);for(n in t)Jj.call(t,n)&&!e2.hasOwnProperty(n)&&(o[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps,t)o[n]===void 0&&(o[n]=t[n]);return{$$typeof:Qj,type:e,key:a,ref:i,props:o,_owner:Zj.current}}Ad.Fragment=Xj;Ad.jsx=Wx;Ad.jsxs=Wx;Mx.exports=Ad;var T=Mx.exports;function Hx(e,t){return function(){return e.apply(t,arguments)}}const{toString:t2}=Object.prototype,{getPrototypeOf:Qh}=Object,Md=(e=>t=>{const r=t2.call(t);return e[r]||(e[r]=r.slice(8,-1).toLowerCase())})(Object.create(null)),qn=e=>(e=e.toLowerCase(),t=>Md(t)===e),Id=e=>t=>typeof t===e,{isArray:pl}=Array,_s=Id("undefined");function r2(e){return e!==null&&!_s(e)&&e.constructor!==null&&!_s(e.constructor)&&tn(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}const $x=qn("ArrayBuffer");function n2(e){let t;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?t=ArrayBuffer.isView(e):t=e&&e.buffer&&$x(e.buffer),t}const o2=Id("string"),tn=Id("function"),qx=Id("number"),Dd=e=>e!==null&&typeof e=="object",a2=e=>e===!0||e===!1,sc=e=>{if(Md(e)!=="object")return!1;const t=Qh(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)},i2=qn("Date"),l2=qn("File"),s2=qn("Blob"),u2=qn("FileList"),c2=e=>Dd(e)&&tn(e.pipe),d2=e=>{let t;return e&&(typeof FormData=="function"&&e instanceof FormData||tn(e.append)&&((t=Md(e))==="formdata"||t==="object"&&tn(e.toString)&&e.toString()==="[object FormData]"))},f2=qn("URLSearchParams"),[p2,h2,g2,m2]=["ReadableStream","Request","Response","Headers"].map(qn),b2=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function Js(e,t,{allOwnKeys:r=!1}={}){if(e===null||typeof e>"u")return;let n,o;if(typeof e!="object"&&(e=[e]),pl(e))for(n=0,o=e.length;n<o;n++)t.call(null,e[n],n,e);else{const a=r?Object.getOwnPropertyNames(e):Object.keys(e),i=a.length;let u;for(n=0;n<i;n++)u=a[n],t.call(null,e[u],u,e)}}function Gx(e,t){t=t.toLowerCase();const r=Object.keys(e);let n=r.length,o;for(;n-- >0;)if(o=r[n],t===o.toLowerCase())return o;return null}const za=(()=>typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global)(),Kx=e=>!_s(e)&&e!==za;function d0(){const{caseless:e}=Kx(this)&&this||{},t={},r=(n,o)=>{const a=e&&Gx(t,o)||o;sc(t[a])&&sc(n)?t[a]=d0(t[a],n):sc(n)?t[a]=d0({},n):pl(n)?t[a]=n.slice():t[a]=n};for(let n=0,o=arguments.length;n<o;n++)arguments[n]&&Js(arguments[n],r);return t}const v2=(e,t,r,{allOwnKeys:n}={})=>(Js(t,(o,a)=>{r&&tn(o)?e[a]=Hx(o,r):e[a]=o},{allOwnKeys:n}),e),y2=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),w2=(e,t,r,n)=>{e.prototype=Object.create(t.prototype,n),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),r&&Object.assign(e.prototype,r)},x2=(e,t,r,n)=>{let o,a,i;const u={};if(t=t||{},e==null)return t;do{for(o=Object.getOwnPropertyNames(e),a=o.length;a-- >0;)i=o[a],(!n||n(i,e,t))&&!u[i]&&(t[i]=e[i],u[i]=!0);e=r!==!1&&Qh(e)}while(e&&(!r||r(e,t))&&e!==Object.prototype);return t},_2=(e,t,r)=>{e=String(e),(r===void 0||r>e.length)&&(r=e.length),r-=t.length;const n=e.indexOf(t,r);return n!==-1&&n===r},C2=e=>{if(!e)return null;if(pl(e))return e;let t=e.length;if(!qx(t))return null;const r=new Array(t);for(;t-- >0;)r[t]=e[t];return r},S2=(e=>t=>e&&t instanceof e)(typeof Uint8Array<"u"&&Qh(Uint8Array)),T2=(e,t)=>{const n=(e&&e[Symbol.iterator]).call(e);let o;for(;(o=n.next())&&!o.done;){const a=o.value;t.call(e,a[0],a[1])}},O2=(e,t)=>{let r;const n=[];for(;(r=e.exec(t))!==null;)n.push(r);return n},P2=qn("HTMLFormElement"),k2=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(r,n,o){return n.toUpperCase()+o}),$v=(({hasOwnProperty:e})=>(t,r)=>e.call(t,r))(Object.prototype),E2=qn("RegExp"),Yx=(e,t)=>{const r=Object.getOwnPropertyDescriptors(e),n={};Js(r,(o,a)=>{let i;(i=t(o,a,e))!==!1&&(n[a]=i||o)}),Object.defineProperties(e,n)},N2=e=>{Yx(e,(t,r)=>{if(tn(e)&&["arguments","caller","callee"].indexOf(r)!==-1)return!1;const n=e[r];if(tn(n)){if(t.enumerable=!1,"writable"in t){t.writable=!1;return}t.set||(t.set=()=>{throw Error("Can not rewrite read-only method '"+r+"'")})}})},j2=(e,t)=>{const r={},n=o=>{o.forEach(a=>{r[a]=!0})};return pl(e)?n(e):n(String(e).split(t)),r},R2=()=>{},A2=(e,t)=>e!=null&&Number.isFinite(e=+e)?e:t,hp="abcdefghijklmnopqrstuvwxyz",qv="0123456789",Qx={DIGIT:qv,ALPHA:hp,ALPHA_DIGIT:hp+hp.toUpperCase()+qv},M2=(e=16,t=Qx.ALPHA_DIGIT)=>{let r="";const{length:n}=t;for(;e--;)r+=t[Math.random()*n|0];return r};function I2(e){return!!(e&&tn(e.append)&&e[Symbol.toStringTag]==="FormData"&&e[Symbol.iterator])}const D2=e=>{const t=new Array(10),r=(n,o)=>{if(Dd(n)){if(t.indexOf(n)>=0)return;if(!("toJSON"in n)){t[o]=n;const a=pLine truncated
|
||||
`).forEach(function(i){o=i.indexOf(":"),r=i.substring(0,o).trim().toLowerCase(),n=i.substring(o+1).trim(),!(!r||t[r]&&nR[r])&&(r==="set-cookie"?t[r]?t[r].push(n):t[r]=[n]:t[r]=t[r]?t[r]+", "+n:n)}),t},Qv=Symbol("internals");function Ul(e){return e&&String(e).trim().toLowerCase()}function uc(e){return e===!1||e==null?e:ye.isArray(e)?e.map(uc):String(e)}function aR(e){const t=Object.create(null),r=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let n;for(;n=r.exec(e);)t[n[1]]=n[2];return t}const iR=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function gp(e,t,r,n,o){if(ye.isFunction(n))return n.call(this,t,r);if(o&&(t=r),!!ye.isString(t)){if(ye.isString(n))return t.indexOf(n)!==-1;if(ye.isRegExp(n))return n.test(t)}}function lR(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(t,r,n)=>r.toUpperCase()+n)}function sR(e,t){const r=ye.toCamelCase(" "+t);["get","set","has"].forEach(n=>{Object.defineProperty(e,n+r,{value:function(o,a,i){return this[n].call(this,t,o,a,i)},configurable:!0})})}class Fd{constructor(t){t&&this.set(t)}set(t,r,n){const o=this;function a(u,f,g){const b=Ul(f);if(!b)throw new Error("header name must be a non-empty string");const _=ye.findKey(o,b);(!_||o[_]===void 0||g===!0||g===void 0&&o[_]!==!1)&&(o[_||f]=uc(u))}const i=(u,f)=>ye.forEach(u,(g,b)=>a(g,b,f));if(ye.isPlainObject(t)||t instanceof this.constructor)i(t,r);else if(ye.isString(t)&&(t=t.trim())&&!iR(t))i(oR(t),r);else if(ye.isHeaders(t))for(const[u,f]of t.entries())a(f,u,n);else t!=null&&a(r,t,n);return this}get(t,r){if(t=Ul(t),t){const n=ye.findKey(this,t);if(n){const o=this[n];if(!r)return o;if(r===!0)return aR(o);if(ye.isFunction(r))return r.call(this,o,n);if(ye.isRegExp(r))return r.exec(o);throw new TypeError("parser must be boolean|regexp|function")}}}has(t,r){if(t=Ul(t),t){const n=ye.findKey(this,t);return!!(n&&this[n]!==void 0&&(!r||gp(this,this[n],n,r)))}return!1}delete(t,r){const n=this;let o=!1;function a(i){if(i=Ul(i),i){const u=ye.findKey(n,i);u&&(!r||gp(n,n[u],u,r))&&(delete n[u],o=!0)}}return ye.isArray(t)?t.forEach(a):a(t),o}clear(t){const r=Object.keys(this);let n=r.length,o=!1;for(;n--;){const a=r[n];(!t||gp(this,this[a],a,t,!0))&&(delete this[a],o=!0)}return o}normalize(t){const r=this,n={};return ye.forEach(this,(o,a)=>{const i=ye.findKey(n,a);if(i){r[i]=uc(o),delete r[a];return}const u=t?lR(a):String(a).trim();u!==a&&delete r[a],r[u]=uc(o),n[u]=!0}),this}concat(...t){return this.constructor.concat(this,...t)}toJSON(t){const r=Object.create(null);return ye.forEach(this,(n,o)=>{n!=null&&n!==!1&&(r[o]=t&&ye.isArray(n)?n.join(", "):n)}),r}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([t,r])=>t+": "+r).join(`
|
||||
`)}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(t){return t instanceof this?t:new this(t)}static concat(t,...r){const n=new this(t);return r.forEach(o=>n.set(o)),n}static accessor(t){const n=(this[Qv]=this[Qv]={accessors:{}}).accessors,o=this.prototype;function a(i){const u=Ul(i);n[u]||(sR(o,i),n[u]=!0)}return ye.isArray(t)?t.forEach(a):a(t),this}}Fd.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);ye.reduceDescriptors(Fd.prototype,({value:e},t)=>{let r=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(n){this[r]=n}}});ye.freezeMethods(Fd);const Ln=Fd;function mp(e,t){const r=this||eg,n=t||r,o=Ln.from(n.headers);let a=n.data;return ye.forEach(e,function(u){a=u.call(r,a,o.normalize(),t?t.status:void 0)}),o.normalize(),a}function a_(e){return!!(e&&e.__CANCEL__)}function hl(e,t,r){it.call(this,e??"canceled",it.ERR_CANCELED,t,r),this.name="CanceledError"}ye.inherits(hl,it,{__CANCEL__:!0});function i_(e,t,r){const n=r.config.validateStatus;!r.status||!n||n(r.status)?e(r):t(new it("Request failed with status code "+r.status,[it.ERR_BAD_REQUEST,it.ERR_BAD_RESPONSE][Math.floor(r.status/100)-4],r.config,r.request,r))}function uR(e){const t=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return t&&t[1]||""}function cR(e,t){e=e||10;const r=new Array(e),n=new Array(e);let o=0,a=0,i;return t=t!==void 0?t:1e3,function(f){const g=Date.now(),b=n[a];i||(i=g),r[o]=f,n[o]=g;let _=a,O=0;for(;_!==o;)O+=r[_++],_=_%e;if(o=(o+1)%e,o===a&&(a=(a+1)%e),g-i<t)return;const S=b&&g-b;return S?Math.round(O*1e3/S):void 0}}function dR(e,t){let r=0,n=1e3/t,o,a;const i=(g,b=Date.now())=>{r=b,o=null,a&&(clearTimeout(a),a=null),e.apply(null,g)};return[(...g)=>{const b=Date.now(),_=b-r;_>=n?i(g,b):(o=g,a||(a=setTimeout(()=>{a=null,i(o)},n-_)))},()=>o&&i(o)]}const Lc=(e,t,r=3)=>{let n=0;const o=cR(50,250);return dR(a=>{const i=a.loaded,u=a.lengthComputable?a.total:void 0,f=i-n,g=o(f),b=i<=u;n=i;const _={loaded:i,total:u,progress:u?i/u:void 0,bytes:f,rate:g||void 0,estimated:g&&u&&b?(u-i)/g:void 0,event:a,lengthComputable:u!=null,[t?"download":"upload"]:!0};e(_)},r)},Xv=(e,t)=>{const r=e!=null;return[n=>t[0]({lengthComputable:r,total:e,loaded:n}),t[1]]},Jv=e=>(...t)=>ye.asap(()=>e(...t)),fR=rn.hasStandardBrowserEnv?function(){const t=rn.navigator&&/(msie|trident)/i.test(rn.navigator.userAgent),r=document.createElement("a");let n;function o(a){let i=a;return t&&(r.setAttribute("href",i),i=r.href),r.setAttribute("href",i),{href:r.href,protocol:r.protocol?r.protocol.replace(/:$/,""):"",host:r.host,search:r.search?r.search.replace(/^\?/,""):"",hash:r.hash?r.hash.replace(/^#/,""):"",hostname:r.hostname,port:r.port,pathname:r.pathname.charAt(0)==="/"?r.pathname:"/"+r.pathname}}return n=o(window.location.href),function(i){const u=ye.isString(i)?o(i):i;return u.protocol===n.protocol&&u.host===n.host}}():function(){return function(){return!0}}(),pR=rn.hasStandardBrowserEnv?{write(e,t,r,n,o,a){const i=[e+"="+encodeURIComponent(t)];ye.isNumber(r)&&i.push("expires="+new Date(r).toGMTString()),ye.isString(n)&&i.push("path="+n),ye.isString(o)&&i.push("domain="+o),a===!0&&i.push("secure"),document.cookie=i.join("; ")},read(e){const t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove(e){this.write(e,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function hR(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}function gR(e,t){return t?e.replace(/\/?\/$/,"")+"/"+t.replace(/^\/+/,""):e}function l_(e,t){return e&&!hR(t)?gR(e,t):t}const Zv=e=>e instanceof Ln?{...e}:e;function Ga(e,t){t=t||{};const r={};function n(g,b,_){return ye.isPlainObject(g)&&ye.isPlainObject(b)?ye.merge.call({caseless:_},g,b):ye.isPlainObject(b)?ye.merge({},b):ye.isArray(b)?b.slice():b}function o(g,b,_){if(ye.isUndefined(b)){if(!ye.isUndefined(g))return n(void 0,g,_)}else return n(g,b,_)}function a(g,b){if(!ye.isUndefined(b))return n(void 0,b)}function i(g,b){if(ye.isUndefined(b)){if(!ye.isUndefined(g))return n(void 0,g)}else return n(void 0,b)}function u(g,b,_){if(_ in t)return n(g,b);if(_ in e)return n(void 0,g)}const f={url:a,method:a,data:a,baseURL:i,transformRequest:i,transformResponse:i,paramsSerializer:i,timeout:i,timeoutMessage:i,withCredentials:i,withXSRFToken:i,adapter:i,responseType:i,xsrfCookieName:i,xsrfHeaderName:i,onUploadProgress:i,onDownloadProgress:i,decompress:i,maxContentLength:i,maxBodyLength:i,beforeRedirect:i,transport:i,httpAgent:i,httpsAgent:i,cancelToken:i,socketPath:i,responseEncoding:i,validateStatus:u,headers:(g,b)=>o(Zv(g),Zv(b),!0)};return ye.forEach(Object.keys(Object.assign({},e,t)),function(b){const _=f[b]||o,O=_(e[b],t[b],b);ye.isUndefined(O)&&_!==u||(r[b]=O)}),r}const s_=e=>{const t=Ga({},e);let{data:r,withXSRFToken:n,xsrfHeaderName:o,xsrfCookieName:a,headers:i,auth:u}=t;t.headers=i=Ln.from(i),t.url=r_(l_(t.baseURL,t.url),e.params,e.paramsSerializer),u&&i.set("Authorization","Basic "+btoa((u.username||"")+":"+(u.password?unescape(enco
Line truncated
|
||||
`+a.map(ry).join(`
|
||||
`):" "+ry(a[0]):"as no adapter specified";throw new it("There is no suitable adapter to dispatch the request "+i,"ERR_NOT_SUPPORT")}return n},adapters:m0};function bp(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new hl(null,e)}function ny(e){return bp(e),e.headers=Ln.from(e.headers),e.data=mp.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),d_.getAdapter(e.adapter||eg.adapter)(e).then(function(n){return bp(e),n.data=mp.call(e,e.transformResponse,n),n.headers=Ln.from(n.headers),n},function(n){return a_(n)||(bp(e),n&&n.response&&(n.response.data=mp.call(e,e.transformResponse,n.response),n.response.headers=Ln.from(n.response.headers))),Promise.reject(n)})}const f_="1.7.5",tg={};["object","boolean","number","function","string","symbol"].forEach((e,t)=>{tg[e]=function(n){return typeof n===e||"a"+(t<1?"n ":" ")+e}});const oy={};tg.transitional=function(t,r,n){function o(a,i){return"[Axios v"+f_+"] Transitional option '"+a+"'"+i+(n?". "+n:"")}return(a,i,u)=>{if(t===!1)throw new it(o(i," has been removed"+(r?" in "+r:"")),it.ERR_DEPRECATED);return r&&!oy[i]&&(oy[i]=!0,console.warn(o(i," has been deprecated since v"+r+" and will be removed in the near future"))),t?t(a,i,u):!0}};function PR(e,t,r){if(typeof e!="object")throw new it("options must be an object",it.ERR_BAD_OPTION_VALUE);const n=Object.keys(e);let o=n.length;for(;o-- >0;){const a=n[o],i=t[a];if(i){const u=e[a],f=u===void 0||i(u,a,e);if(f!==!0)throw new it("option "+a+" must be "+f,it.ERR_BAD_OPTION_VALUE);continue}if(r!==!0)throw new it("Unknown option "+a,it.ERR_BAD_OPTION)}}const b0={assertOptions:PR,validators:tg},$o=b0.validators;class zc{constructor(t){this.defaults=t,this.interceptors={request:new Yv,response:new Yv}}async request(t,r){try{return await this._request(t,r)}catch(n){if(n instanceof Error){let o;Error.captureStackTrace?Error.captureStackTrace(o={}):o=new Error;const a=o.stack?o.stack.replace(/^.+\n/,""):"";try{n.stack?a&&!String(n.stack).endsWith(a.replace(/^.+\n.+\n/,""))&&(n.stack+=`
|
||||
`+a):n.stack=a}catch{}}throw n}}_request(t,r){typeof t=="string"?(r=r||{},r.url=t):r=t||{},r=Ga(this.defaults,r);const{transitional:n,paramsSerializer:o,headers:a}=r;n!==void 0&&b0.assertOptions(n,{silentJSONParsing:$o.transitional($o.boolean),forcedJSONParsing:$o.transitional($o.boolean),clarifyTimeoutError:$o.transitional($o.boolean)},!1),o!=null&&(ye.isFunction(o)?r.paramsSerializer={serialize:o}:b0.assertOptions(o,{encode:$o.function,serialize:$o.function},!0)),r.method=(r.method||this.defaults.method||"get").toLowerCase();let i=a&&ye.merge(a.common,a[r.method]);a&&ye.forEach(["delete","get","head","post","put","patch","common"],C=>{delete a[C]}),r.headers=Ln.concat(i,a);const u=[];let f=!0;this.interceptors.request.forEach(function(w){typeof w.runWhen=="function"&&w.runWhen(r)===!1||(f=f&&w.synchronous,u.unshift(w.fulfilled,w.rejected))});const g=[];this.interceptors.response.forEach(function(w){g.push(w.fulfilled,w.rejected)});let b,_=0,O;if(!f){const C=[ny.bind(this),void 0];for(C.unshift.apply(C,u),C.push.apply(C,g),O=C.length,b=Promise.resolve(r);_<O;)b=b.then(C[_++],C[_++]);return b}O=u.length;let S=r;for(_=0;_<O;){const C=u[_++],w=u[_++];try{S=C(S)}catch(m){w.call(this,m);break}}try{b=ny.call(this,S)}catch(C){return Promise.reject(C)}for(_=0,O=g.length;_<O;)b=b.then(g[_++],g[_++]);return b}getUri(t){t=Ga(this.defaults,t);const r=l_(t.baseURL,t.url);return r_(r,t.params,t.paramsSerializer)}}ye.forEach(["delete","get","head","options"],function(t){zc.prototype[t]=function(r,n){return this.request(Ga(n||{},{method:t,url:r,data:(n||{}).data}))}});ye.forEach(["post","put","patch"],function(t){function r(n){return function(a,i,u){return this.request(Ga(u||{},{method:t,headers:n?{"Content-Type":"multipart/form-data"}:{},url:a,data:i}))}}zc.prototype[t]=r(),zc.prototype[t+"Form"]=r(!0)});const cc=zc;class rg{constructor(t){if(typeof t!="function")throw new TypeError("executor must be a function.");let r;this.promise=new Promise(function(a){r=a});const n=this;this.promise.then(o=>{if(!n._listeners)return;let a=n._listeners.length;for(;a-- >0;)n._listeners[a](o);n._listeners=null}),this.promise.then=o=>{let a;const i=new Promise(u=>{n.subscribe(u),a=u}).then(o);return i.cancel=function(){n.unsubscribe(a)},i},t(function(a,i,u){n.reason||(n.reason=new hl(a,i,u),r(n.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;const r=this._listeners.indexOf(t);r!==-1&&this._listeners.splice(r,1)}static source(){let t;return{token:new rg(function(o){t=o}),cancel:t}}}const kR=rg;function ER(e){return function(r){return e.apply(null,r)}}function NR(e){return ye.isObject(e)&&e.isAxiosError===!0}const v0={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(v0).forEach(([e,t])=>{v0[t]=e});const jR=v0;function p_(e){const t=new cc(e),r=Hx(cc.prototype.request,t);return ye.extend(r,cc.prototype,t,{allOwnKeys:!0}),ye.extend(r,t,null,{allOwnKeys:!0}),r.create=function(o){return p_(Ga(e,o))},r}const or=p_(eg);or.Axios=cc;or.CanceledError=hl;or.CancelToken=kR;or.isCancel=a_;or.VERSION=f_;or.toFormData=Ld;or.AxiosError=it;or.Cancel=or.CanceledError;or.all=function(t){return Promise.all(t)};or.spread=ER;or.isAxiosError=NR;or.mergeConfig=Ga;or.AxiosHeaders=Ln;or.formToJSON=e=>o_(ye.isHTMLForm(e)?new FormData(e):e);or.getAdapter=d_.getAdapter;or.HttpStatusCode=jR;or.default=or;const at=or;var y0={},h_={exports:{}},sn={},g_={exports:{}},m_={};/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/(function(e){function t($,z){var H=$.length;$.push(z);e:for(;0<H;){var G=H-1>>>1,X=$[G];if(0<o(X,z))$[G]=z,$[H]=X,H=G;else break e}}function r($){return $.length===0?null:$[0]}function n($){if($.length===0)return null;var z=$[0],H=$.pop();if(H!==z){$[0]=H;e:for(var G=0,X=$.length,ie=X>>>1;G<ie;){var fe=2*(G+1)-1,pe=$[fe],oe=fe+1,se=$[oe];if(0>o(pe,H))oe<X&&0>o(se,pe)?($[G]=se,$[oe]=H,G=oe):($[G]=pe,$[fe]=H,G=fe);else if(oe<X&&0>o(se,H))$[G]=se,$[oe]=H,G=oe;else break e}}return z}function o($,z){var H=$.sortIndex-z.sortIndex;return H!==0?H:$.id-z.id}if(typeof performance=="object"&&typeof performance.now=="function"){var a=performance;e.unstable_now=function(){return a.now()}}else{var i=Date,u=i.now();e.unstable_now=function(){return i.now()-u}}var f=[],g=[],b=1,_=null,O=3,S=!1,C=!1,w=!1,m=typeof setTimeout=="function"?setTimeout:null,d=typeof clearTimeout=="function"?clearTimeout:null,l=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function c($){for(var z=r(g);z!==null;){if(z.callback===null)n(g);else if(z.startTime<=$)n(g),z.sortIndex=z.expirationTime,t(f,z);else break;z=r(g)}}function s($){if(w=!1,c($),!C)if(r(f)!==null)C=!0,U(h);else{var z=r(g);z!==null&&Y(s,z.startTime-$)}}function h($,z){C=!1,w&&(w=!1,d(E),E=-1),S=!0;var H=O;try{for(c(z),_=r(f);_!==null&&(!(_.expirationTime>z)||$&&!R());){var G=_.callback;if(typeof G=="function"){_.callback=null,O=_.priorityLevel;var X=G(_.expirationTime<=z);z=e.unstable_now(),typeof X=="function"?_.callback=X:_===r(f)&&n(f),c(z)}else n(f);_=r(f)}if(_!==null)var ie=!0;else{var fe=r(g);fe!==null&&Y(s,fe.startTime-z),ie=!1}return ie}finally{_=null,O=H,S=!1}}var v=!1,y=null,E=-1,P=5,N=-1;function R(){return!(e.unstable_now()-N<P)}function M(){if(y!==null){var $=e.unstable_now();N=$;var z=!0;try{z=y(!0,$)}finally{z?D():(v=!1,y=null)}}else v=!1}var D;if(typeof l=="function")D=function(){l(M)};else if(typeof MessageChannel<"u"){var L=new MessageChannel,W=L.port2;L.port1.onmessage=M,D=function(){W.postMessage(null)}}else D=function(){m(M,0)};function U($){y=$,v||(v=!0,D())}function Y($,z){E=m(function(){$(e.unstable_now())},z)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function($){$.callback=null},e.unstable_continueExecution=function(){C||S||(C=!0,U(h))},e.unstable_forceFrameRate=function($){0>$||125<$?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):P=0<$?Math.floor(1e3/$):5},e.unstable_getCurrentPriorityLevel=function(){return O},e.unstable_getFirstCallbackNode=function(){return r(f)},e.unstable_next=function($){switch(O){case 1:case 2:case 3:var z=3;break;default:z=O}var H=O;O=z;try{return $()}finally{O=H}},e.unstable_pauseExecution=function(){},e.unstable_requestPaint=function(){},e.unstable_runWithPriority=function($,z){switch($){case 1:case 2:case 3:case 4:case 5:break;default:$=3}var H=O;O=$;try{return z()}finally{O=H}},e.unstable_scheduleCallback=function($,z,H){var G=e.unstable_now();switch(typeof H=="object"&&H!==null?(H=H.delay,H=typeof H=="number"&&0<H?G+H:G):H=G,$){case 1:var X=-1;break;case 2:X=250;break;case 5:X=1073741823;break;case 4:X=1e4;break;default:X=5e3}return X=H+X,$={id:b++,callback:z,priorityLevel:$,startTime:H,expirationTime:X,sortIndex:-1},H>G?($.sortIndex=H,t(g,$),r(f)===null&&$===r(g)&&(w?(d(E),E=-1):w=!0,Y(s,H-G))):($.sortIndex=X,t(f,$),C||S||(C=!0,U(h))),$},e.unstable_shouldYield=R,e.unstable_wrapCallback=function($){var z=O;return function(){var H=O;O=z;try{return $.apply(this,arguments)}finally{O=H}}}})(m_);g_.exports=m_;var RR=g_.exports;/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var b_=j,an=RR;function ke(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,r=1;r<arguments.length;r++)t+="&args[]="+encodeURIComponent(arguments[r]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var v_=new Set,Cs={};function ni(e,t){Qi(e,t),Qi(e+"Capture",t)}function Qi(e,t){for(Cs[e]=t,e=0;e<t.length;e++)v_.add(t[e])}var Po=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),w0=Object.prototype.hasOwnProperty,AR=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,ay={},iy={};function MR(e){return w0.call(iy,e)?!0:w0.call(ay,e)?!1:AR.test(e)?iy[e]=!0:(ay[e]=!0,!1)}function IR(e,t,r,n){if(r!==null&&r.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return n?!1:r!==null?!r.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function DR(e,t,r,n){if(t===null||typeof t>"u"||IR(e,t,r,n))return!0;if(n)return!1;if(r!==null)switch(r.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function Lr(e,t,r,n,o,a,i){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=n,this.attributeNamespace=o,this.mustUseProperty=r,this.propertyName=e,this.type=t,this.sanitizeURL=a,this.removeEmptyString=i}var vr={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){vr[e]=new Lr(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];vr[t]=new Lr(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){vr[e]=new Lr(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){vr[e]=new Lr(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){vr[e]=new Lr(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){vr[e]=new Lr(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){vr[e]=new Lr(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){vr[e]=new Lr(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){vr[e]=new Lr(e,5,!1,e.toLowerCase(),null,!1,!1)});var ng=/[\-:]([a-z])/g;function og(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(ng,og);vr[t]=new Lr(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(ng,og);vr[t]=new Lr(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(ng,og);vr[t]=new Lr(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){vr[e]=new Lr(e,1,!1,e.toLowerCase(),null,!1,!1)});vr.xlinkHref=new Lr("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){vr[e]=new Lr(e,1,!1,e.toLowerCase(),null,!0,!0)});function ag(e,t,r,n){var o=vr.hasOwnProperty(t)?vr[t]:null;(o!==null?o.type!==0:n||!(2<t.length)||t[0]!=="o"&&t[0]!==
Line truncated
|
||||
`+vp+e}var yp=!1;function wp(e,t){if(!e||yp)return"";yp=!0;var r=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(t)if(t=function(){throw Error()},Object.defineProperty(t.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(t,[])}catch(g){var n=g}Reflect.construct(e,[],t)}else{try{t.call()}catch(g){n=g}e.call(t.prototype)}else{try{throw Error()}catch(g){n=g}e()}}catch(g){if(g&&n&&typeof g.stack=="string"){for(var o=g.stack.split(`
|
||||
`),a=n.stack.split(`
|
||||
`),i=o.length-1,u=a.length-1;1<=i&&0<=u&&o[i]!==a[u];)u--;for(;1<=i&&0<=u;i--,u--)if(o[i]!==a[u]){if(i!==1||u!==1)do if(i--,u--,0>u||o[i]!==a[u]){var f=`
|
||||
`+o[i].replace(" at new "," at ");return e.displayName&&f.includes("<anonymous>")&&(f=f.replace("<anonymous>",e.displayName)),f}while(1<=i&&0<=u);break}}}finally{yp=!1,Error.prepareStackTrace=r}return(e=e?e.displayName||e.name:"")?ts(e):""}function LR(e){switch(e.tag){case 5:return ts(e.type);case 16:return ts("Lazy");case 13:return ts("Suspense");case 19:return ts("SuspenseList");case 0:case 2:case 15:return e=wp(e.type,!1),e;case 11:return e=wp(e.type.render,!1),e;case 1:return e=wp(e.type,!0),e;default:return""}}function S0(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Oi:return"Fragment";case Ti:return"Portal";case x0:return"Profiler";case ig:return"StrictMode";case _0:return"Suspense";case C0:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case w_:return(e.displayName||"Context")+".Consumer";case y_:return(e._context.displayName||"Context")+".Provider";case lg:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case sg:return t=e.displayName||null,t!==null?t:S0(e.type)||"Memo";case Xo:t=e._payload,e=e._init;try{return S0(e(t))}catch{}}return null}function FR(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return S0(t);case 8:return t===ig?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function ma(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function __(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function zR(e){var t=__(e)?"checked":"value",r=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),n=""+e[t];if(!e.hasOwnProperty(t)&&typeof r<"u"&&typeof r.get=="function"&&typeof r.set=="function"){var o=r.get,a=r.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(i){n=""+i,a.call(this,i)}}),Object.defineProperty(e,t,{enumerable:r.enumerable}),{getValue:function(){return n},setValue:function(i){n=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Mu(e){e._valueTracker||(e._valueTracker=zR(e))}function C_(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var r=t.getValue(),n="";return e&&(n=__(e)?e.checked?"true":"false":e.value),e=n,e!==r?(t.setValue(e),!0):!1}function Bc(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function T0(e,t){var r=t.checked;return $t({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:r??e._wrapperState.initialChecked})}function sy(e,t){var r=t.defaultValue==null?"":t.defaultValue,n=t.checked!=null?t.checked:t.defaultChecked;r=ma(t.value!=null?t.value:r),e._wrapperState={initialChecked:n,initialValue:r,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function S_(e,t){t=t.checked,t!=null&&ag(e,"checked",t,!1)}function O0(e,t){S_(e,t);var r=ma(t.value),n=t.type;if(r!=null)n==="number"?(r===0&&e.value===""||e.value!=r)&&(e.value=""+r):e.value!==""+r&&(e.value=""+r);else if(n==="submit"||n==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?P0(e,t.type,r):t.hasOwnProperty("defaultValue")&&P0(e,t.type,ma(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function uy(e,t,r){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var n=t.type;if(!(n!=="submit"&&n!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,r||t===e.value||(e.value=t),e.defaultValue=t}r=e.name,r!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,r!==""&&(e.name=r)}function P0(e,t,r){(t!=="number"||Bc(e.ownerDocument)!==e)&&(r==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+r&&(e.defaultValue=""+r))}var rs=Array.isArray;function zi(e,t,r,n){if(e=e.options,t){t={};for(var o=0;o<r.length;o++)t["$"+r[o]]=!0;for(r=0;r<e.length;r++)o=t.hasOwnProperty("$"+e[r].value),e[r].selected!==o&&(e[r].selected=o),o&&n&&(e[r].defaultSelected=!0)}else{for(r=""+ma(r),t=null,o=0;o<e.length;o++){if(e[o].value===r){e[o].selected=!0,n&&(e[o].defaultSelected=!0);return}t!==null||e[o].disabled||(t=e[o])}t!==null&&(t.selected=!0)}}function k0(e,t){if(t.dan
Line truncated
|
||||
`).replace(YA,"")}function Uu(e,t,r){if(t=Ry(t),Ry(e)!==t&&r)throw Error(ke(425))}function Kc(){}var V0=null,U0=null;function W0(e,t){return e==="textarea"||e==="noscript"||typeof t.children=="string"||typeof t.children=="number"||typeof t.dangerouslySetInnerHTML=="object"&&t.dangerouslySetInnerHTML!==null&&t.dangerouslySetInnerHTML.__html!=null}var H0=typeof setTimeout=="function"?setTimeout:void 0,QA=typeof clearTimeout=="function"?clearTimeout:void 0,Ay=typeof Promise=="function"?Promise:void 0,XA=typeof queueMicrotask=="function"?queueMicrotask:typeof Ay<"u"?function(e){return Ay.resolve(null).then(e).catch(JA)}:H0;function JA(e){setTimeout(function(){throw e})}function Ap(e,t){var r=t,n=0;do{var o=r.nextSibling;if(e.removeChild(r),o&&o.nodeType===8)if(r=o.data,r==="/$"){if(n===0){e.removeChild(o),ks(t);return}n--}else r!=="$"&&r!=="$?"&&r!=="$!"||n++;r=o}while(r);ks(t)}function sa(e){for(;e!=null;e=e.nextSibling){var t=e.nodeType;if(t===1||t===3)break;if(t===8){if(t=e.data,t==="$"||t==="$!"||t==="$?")break;if(t==="/$")return null}}return e}function My(e){e=e.previousSibling;for(var t=0;e;){if(e.nodeType===8){var r=e.data;if(r==="$"||r==="$!"||r==="$?"){if(t===0)return e;t--}else r==="/$"&&t++}e=e.previousSibling}return null}var ml=Math.random().toString(36).slice(2),no="__reactFiber$"+ml,As="__reactProps$"+ml,ko="__reactContainer$"+ml,$0="__reactEvents$"+ml,ZA="__reactListeners$"+ml,eM="__reactHandles$"+ml;function Ba(e){var t=e[no];if(t)return t;for(var r=e.parentNode;r;){if(t=r[ko]||r[no]){if(r=t.alternate,t.child!==null||r!==null&&r.child!==null)for(e=My(e);e!==null;){if(r=e[no])return r;e=My(e)}return t}e=r,r=e.parentNode}return null}function tu(e){return e=e[no]||e[ko],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function Ni(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(ke(33))}function Hd(e){return e[As]||null}var q0=[],ji=-1;function wa(e){return{current:e}}function It(e){0>ji||(e.current=q0[ji],q0[ji]=null,ji--)}function kt(e,t){ji++,q0[ji]=e.current,e.current=t}var ba={},Er=wa(ba),$r=wa(!1),Ka=ba;function Xi(e,t){var r=e.type.contextTypes;if(!r)return ba;var n=e.stateNode;if(n&&n.__reactInternalMemoizedUnmaskedChildContext===t)return n.__reactInternalMemoizedMaskedChildContext;var o={},a;for(a in r)o[a]=t[a];return n&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function qr(e){return e=e.childContextTypes,e!=null}function Yc(){It($r),It(Er)}function Iy(e,t,r){if(Er.current!==ba)throw Error(ke(168));kt(Er,t),kt($r,r)}function fC(e,t,r){var n=e.stateNode;if(t=t.childContextTypes,typeof n.getChildContext!="function")return r;n=n.getChildContext();for(var o in n)if(!(o in t))throw Error(ke(108,FR(e)||"Unknown",o));return $t({},r,n)}function Qc(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||ba,Ka=Er.current,kt(Er,e),kt($r,$r.current),!0}function Dy(e,t,r){var n=e.stateNode;if(!n)throw Error(ke(169));r?(e=fC(e,t,Ka),n.__reactInternalMemoizedMergedChildContext=e,It($r),It(Er),kt(Er,e)):It($r),kt($r,r)}var yo=null,$d=!1,Mp=!1;function pC(e){yo===null?yo=[e]:yo.push(e)}function tM(e){$d=!0,pC(e)}function xa(){if(!Mp&&yo!==null){Mp=!0;var e=0,t=xt;try{var r=yo;for(xt=1;e<r.length;e++){var n=r[e];do n=n(!0);while(n!==null)}yo=null,$d=!1}catch(o){throw yo!==null&&(yo=yo.slice(e+1)),F_(cg,xa),o}finally{xt=t,Mp=!1}}return null}var Ri=[],Ai=0,Xc=null,Jc=0,bn=[],vn=0,Ya=null,wo=1,xo="";function Da(e,t){Ri[Ai++]=Jc,Ri[Ai++]=Xc,Xc=e,Jc=t}function hC(e,t,r){bn[vn++]=wo,bn[vn++]=xo,bn[vn++]=Ya,Ya=e;var n=wo;e=xo;var o=32-Fn(n)-1;n&=~(1<<o),r+=1;var a=32-Fn(t)+o;if(30<a){var i=o-o%5;a=(n&(1<<i)-1).toString(32),n>>=i,o-=i,wo=1<<32-Fn(t)+o|r<<o|n,xo=a+e}else wo=1<<a|r<<o|n,xo=e}function yg(e){e.return!==null&&(Da(e,1),hC(e,1,0))}function wg(e){for(;e===Xc;)Xc=Ri[--Ai],Ri[Ai]=null,Jc=Ri[--Ai],Ri[Ai]=null;for(;e===Ya;)Ya=bn[--vn],bn[vn]=null,xo=bn[--vn],bn[vn]=null,wo=bn[--vn],bn[vn]=null}var nn=null,en=null,Ft=!1,Mn=null;function gC(e,t){var r=wn(5,null,null,0);r.elementType="DELETED",r.stateNode=t,r.return=e,t=e.deletions,t===null?(e.deletions=[r],e.flags|=16):t.push(r)}function Ly(e,t){switch(e.tag){case 5:var r=e.type;return t=t.nodeType!==1||r.toLowerCase()!==t.nodeName.toLowerCase()?null:t,t!==null?(e.stateNode=t,nn=e,en=sa(t.firstChild),!0):!1;case 6:return t=e.pendingProps===""||t.nodeType!==3?null:t,t!==null?(e.stateNode=t,nn=e,en=null,!0):!1;case 13:return t=t.nodeType!==8?null:t,t!==null?(r=Ya!==null?{id:wo,overflow:xo}:null,e.memoizedState={dehydrated:t,treeContext:r,retryLane:1073741824},r=wn(18,null,null,0),r.stateNode=t,r.return=e,e.child=r,nn=e,en=null,!0):!1;default:return!1}}function G0(e){return(e.mode&1)!==0&&(e.flags&128)===0}function K0(e){if(Ft){var t=en;if(t){var r=t;if(!Ly(e,t)){if(G0(e))throw Error(ke(418));t=sa(r.nextSibling);var n=nn;t&&Ly(e,t)?gC(n,r):(e.flags=e.flags&-4097|2,Ft=!1,nn=e)}}else{if(G0(e))throw Error(ke(418));e.flags=e.flags&-4097|2,Ft=!1,nn=e}}}function Fy(e){
Line truncated
|
||||
Error generating stack: `+a.message+`
|
||||
`+a.stack}return{value:e,source:t,stack:o,digest:null}}function zp(e,t,r){return{value:e,source:null,stack:r??null,digest:t??null}}function J0(e,t){try{console.error(t.value)}catch(r){setTimeout(function(){throw r})}}var cM=typeof WeakMap=="function"?WeakMap:Map;function WC(e,t,r){r=So(-1,r),r.tag=3,r.payload={element:null};var n=t.value;return r.callback=function(){id||(id=!0,sh=n),J0(e,t)},r}function HC(e,t,r){r=So(-1,r),r.tag=3;var n=e.type.getDerivedStateFromError;if(typeof n=="function"){var o=t.value;r.payload=function(){return n(o)},r.callback=function(){J0(e,t)}}var a=e.stateNode;return a!==null&&typeof a.componentDidCatch=="function"&&(r.callback=function(){J0(e,t),typeof n!="function"&&(ca===null?ca=new Set([this]):ca.add(this));var i=t.stack;this.componentDidCatch(t.value,{componentStack:i!==null?i:""})}),r}function qy(e,t,r){var n=e.pingCache;if(n===null){n=e.pingCache=new cM;var o=new Set;n.set(t,o)}else o=n.get(t),o===void 0&&(o=new Set,n.set(t,o));o.has(r)||(o.add(r),e=SM.bind(null,e,t,r),t.then(e,e))}function Gy(e){do{var t;if((t=e.tag===13)&&(t=e.memoizedState,t=t!==null?t.dehydrated!==null:!0),t)return e;e=e.return}while(e!==null);return null}function Ky(e,t,r,n,o){return e.mode&1?(e.flags|=65536,e.lanes=o,e):(e===t?e.flags|=65536:(e.flags|=128,r.flags|=131072,r.flags&=-52805,r.tag===1&&(r.alternate===null?r.tag=17:(t=So(-1,1),t.tag=2,ua(r,t,1))),r.lanes|=1),e)}var dM=Ao.ReactCurrentOwner,Wr=!1;function Mr(e,t,r,n){t.child=e===null?_C(t,null,r,n):Zi(t,e.child,r,n)}function Yy(e,t,r,n,o){r=r.render;var a=t.ref;return Wi(t,o),n=jg(e,t,r,n,a,o),r=Rg(),e!==null&&!Wr?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,No(e,t,o)):(Ft&&r&&yg(t),t.flags|=1,Mr(e,t,n,o),t.child)}function Qy(e,t,r,n,o){if(e===null){var a=r.type;return typeof a=="function"&&!Ug(a)&&a.defaultProps===void 0&&r.compare===null&&r.defaultProps===void 0?(t.tag=15,t.type=a,$C(e,t,a,n,o)):(e=wc(r.type,null,n,t,t.mode,o),e.ref=t.ref,e.return=t,t.child=e)}if(a=e.child,!(e.lanes&o)){var i=a.memoizedProps;if(r=r.compare,r=r!==null?r:Ns,r(i,n)&&e.ref===t.ref)return No(e,t,o)}return t.flags|=1,e=fa(a,n),e.ref=t.ref,e.return=t,t.child=e}function $C(e,t,r,n,o){if(e!==null){var a=e.memoizedProps;if(Ns(a,n)&&e.ref===t.ref)if(Wr=!1,t.pendingProps=n=a,(e.lanes&o)!==0)e.flags&131072&&(Wr=!0);else return t.lanes=e.lanes,No(e,t,o)}return Z0(e,t,r,n,o)}function qC(e,t,r){var n=t.pendingProps,o=n.children,a=e!==null?e.memoizedState:null;if(n.mode==="hidden")if(!(t.mode&1))t.memoizedState={baseLanes:0,cachePool:null,transitions:null},kt(Di,Zr),Zr|=r;else{if(!(r&1073741824))return e=a!==null?a.baseLanes|r:r,t.lanes=t.childLanes=1073741824,t.memoizedState={baseLanes:e,cachePool:null,transitions:null},t.updateQueue=null,kt(Di,Zr),Zr|=e,null;t.memoizedState={baseLanes:0,cachePool:null,transitions:null},n=a!==null?a.baseLanes:r,kt(Di,Zr),Zr|=n}else a!==null?(n=a.baseLanes|r,t.memoizedState=null):n=r,kt(Di,Zr),Zr|=n;return Mr(e,t,o,r),t.child}function GC(e,t){var r=t.ref;(e===null&&r!==null||e!==null&&e.ref!==r)&&(t.flags|=512,t.flags|=2097152)}function Z0(e,t,r,n,o){var a=qr(r)?Ka:Er.current;return a=Xi(t,a),Wi(t,o),r=jg(e,t,r,n,a,o),n=Rg(),e!==null&&!Wr?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,No(e,t,o)):(Ft&&n&&yg(t),t.flags|=1,Mr(e,t,r,o),t.child)}function Xy(e,t,r,n,o){if(qr(r)){var a=!0;Qc(t)}else a=!1;if(Wi(t,o),t.stateNode===null)bc(e,t),wC(t,r,n),X0(t,r,n,o),n=!0;else if(e===null){var i=t.stateNode,u=t.memoizedProps;i.props=u;var f=i.context,g=r.contextType;typeof g=="object"&&g!==null?g=Sn(g):(g=qr(r)?Ka:Er.current,g=Xi(t,g));var b=r.getDerivedStateFromProps,_=typeof b=="function"||typeof i.getSnapshotBeforeUpdate=="function";_||typeof i.UNSAFE_componentWillReceiveProps!="function"&&typeof i.componentWillReceiveProps!="function"||(u!==n||f!==g)&&Uy(t,i,n,g),Jo=!1;var O=t.memoizedState;i.state=O,td(t,n,i,o),f=t.memoizedState,u!==n||O!==f||$r.current||Jo?(typeof b=="function"&&(Q0(t,r,b,n),f=t.memoizedState),(u=Jo||Vy(t,r,u,n,O,f,g))?(_||typeof i.UNSAFE_componentWillMount!="function"&&typeof i.componentWillMount!="function"||(typeof i.componentWillMount=="function"&&i.componentWillMount(),typeof i.UNSAFE_componentWillMount=="function"&&i.UNSAFE_componentWillMount()),typeof i.componentDidMount=="function"&&(t.flags|=4194308)):(typeof i.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=n,t.memoizedState=f),i.props=n,i.state=f,i.context=g,n=u):(typeof i.componentDidMount=="function"&&(t.flags|=4194308),n=!1)}else{i=t.stateNode,vC(e,t),u=t.memoizedProps,g=t.type===t.elementType?u:jn(t.type,u),i.props=g,_=t.pendingProps,O=i.context,f=r.contextType,typeof f=="object"&&f!==null?f=Sn(f):(f=qr(r)?Ka:Er.current,f=Xi(t,f));var S=r.getDerivedStateFromProps;(b=typeof S=="function"||typeof i.getSnapshotBeforeUpdate=="function")||typeof i.UNSAFE_componentWillReceiveProps!="function"&&typeof i.componentWillReceiveProps!="function"||(u!==_||O!==f)&&Uy(t,i,n,f),Jo=!1,O=t.memoizedState,i.state=O,td(t,n,i,o);var C=t.memoizedS
Line truncated
|
||||
* @remix-run/router v1.10.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/function Bs(){return Bs=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},Bs.apply(this,arguments)}var oa;(function(e){e.Pop="POP",e.Push="PUSH",e.Replace="REPLACE"})(oa||(oa={}));const dw="popstate";function IM(e){e===void 0&&(e={});function t(n,o){let{pathname:a,search:i,hash:u}=n.location;return fh("",{pathname:a,search:i,hash:u},o.state&&o.state.usr||null,o.state&&o.state.key||"default")}function r(n,o){return typeof o=="string"?o:cd(o)}return LM(t,r,null,e)}function Ht(e,t){if(e===!1||e===null||typeof e>"u")throw new Error(t)}function Gg(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function DM(){return Math.random().toString(36).substr(2,8)}function fw(e,t){return{usr:e.state,key:e.key,idx:t}}function fh(e,t,r,n){return r===void 0&&(r=null),Bs({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?bl(t):t,{state:r,key:t&&t.key||n||DM()})}function cd(e){let{pathname:t="/",search:r="",hash:n=""}=e;return r&&r!=="?"&&(t+=r.charAt(0)==="?"?r:"?"+r),n&&n!=="#"&&(t+=n.charAt(0)==="#"?n:"#"+n),t}function bl(e){let t={};if(e){let r=e.indexOf("#");r>=0&&(t.hash=e.substr(r),e=e.substr(0,r));let n=e.indexOf("?");n>=0&&(t.search=e.substr(n),e=e.substr(0,n)),e&&(t.pathname=e)}return t}function LM(e,t,r,n){n===void 0&&(n={});let{window:o=document.defaultView,v5Compat:a=!1}=n,i=o.history,u=oa.Pop,f=null,g=b();g==null&&(g=0,i.replaceState(Bs({},i.state,{idx:g}),""));function b(){return(i.state||{idx:null}).idx}function _(){u=oa.Pop;let m=b(),d=m==null?null:m-g;g=m,f&&f({action:u,location:w.location,delta:d})}function O(m,d){u=oa.Push;let l=fh(w.location,m,d);r&&r(l,m),g=b()+1;let c=fw(l,g),s=w.createHref(l);try{i.pushState(c,"",s)}catch(h){if(h instanceof DOMException&&h.name==="DataCloneError")throw h;o.location.assign(s)}a&&f&&f({action:u,location:w.location,delta:1})}function S(m,d){u=oa.Replace;let l=fh(w.location,m,d);r&&r(l,m),g=b();let c=fw(l,g),s=w.createHref(l);i.replaceState(c,"",s),a&&f&&f({action:u,location:w.location,delta:0})}function C(m){let d=o.location.origin!=="null"?o.location.origin:o.location.href,l=typeof m=="string"?m:cd(m);return Ht(d,"No window.location.(origin|href) available to create URL for href: "+l),new URL(l,d)}let w={get action(){return u},get location(){return e(o,i)},listen(m){if(f)throw new Error("A history only accepts one active listener");return o.addEventListener(dw,_),f=m,()=>{o.removeEventListener(dw,_),f=null}},createHref(m){return t(o,m)},createURL:C,encodeLocation(m){let d=C(m);return{pathname:d.pathname,search:d.search,hash:d.hash}},push:O,replace:S,go(m){return i.go(m)}};return w}var pw;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(pw||(pw={}));function FM(e,t,r){r===void 0&&(r="/");let n=typeof t=="string"?bl(t):t,o=Vs(n.pathname||"/",r);if(o==null)return null;let a=b5(e);zM(a);let i=null;for(let u=0;i==null&&u<a.length;++u)i=KM(a[u],QM(o));return i}function b5(e,t,r,n){t===void 0&&(t=[]),r===void 0&&(r=[]),n===void 0&&(n="");let o=(a,i,u)=>{let f={relativePath:u===void 0?a.path||"":u,caseSensitive:a.caseSensitive===!0,childrenIndex:i,route:a};f.relativePath.startsWith("/")&&(Ht(f.relativePath.startsWith(n),'Absolute route path "'+f.relativePath+'" nested under path '+('"'+n+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),f.relativePath=f.relativePath.slice(n.length));let g=pa([n,f.relativePath]),b=r.concat(f);a.children&&a.children.length>0&&(Ht(a.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+g+'".')),b5(a.children,t,b,g)),!(a.path==null&&!a.index)&&t.push({path:g,score:qM(g,a.index),routesMeta:b})};return e.forEach((a,i)=>{var u;if(a.path===""||!((u=a.path)!=null&&u.includes("?")))o(a,i);else for(let f of v5(a.path))o(a,i,f)}),t}function v5(e){let t=e.split("/");if(t.length===0)return[];let[r,...n]=t,o=r.endsWith("?"),a=r.replace(/\?$/,"");if(n.length===0)return o?[a,""]:[a];let i=v5(n.join("/")),u=[];return u.push(...i.map(f=>f===""?a:[a,f].join("/"))),o&&u.push(...i),u.map(f=>e.startsWith("/")&&f===""?"/":f)}function zM(e){e.sort((t,r)=>t.score!==r.score?r.score-t.score:GM(t.routesMeta.map(n=>n.childrenIndex),r.routesMeta.map(n=>n.childrenIndex)))}const BM=/^:\w+$/,VM=3,UM=2,WM=1,HM=10,$M=-2,hw=e=>e==="*";function qM(e,t){let r=e.split("/"),n=r.length;return r.some(hw)&&(n+=$M),t&&(n+=UM),r.filter(o=>!hw(o)).reduce((o,a)=>o+(BM.test(a)?VM:a===""?WM:HM),n)}function GM(e,t){return e.length===t.length&&e.slice(0,-1).every((n,o)=>n===t[o])?e[e.length-1]-t[t.length-1]:0}function KM(e,t){let{routesMeta:r}=e,n={},o="/",a=[];for(let i=0;i<r.length;++i){let u=r[i],f=i===r.length-1,g=o==="/"?t:t.slice(o.length)||"/",b=ph({path:u.relativePath,caseSensitive:u.caseSensitive,end:f},g);if(!b)return null;Object.assign(n,b.paLine truncated
|
||||
* React Router v6.17.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/function dd(){return dd=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},dd.apply(this,arguments)}const tf=j.createContext(null),w5=j.createContext(null),ai=j.createContext(null),rf=j.createContext(null),Mo=j.createContext({outlet:null,matches:[],isDataRoute:!1}),x5=j.createContext(null);function aI(e,t){let{relative:r}=t===void 0?{}:t;vl()||Ht(!1);let{basename:n,navigator:o}=j.useContext(ai),{hash:a,pathname:i,search:u}=nf(e,{relative:r}),f=i;return n!=="/"&&(f=i==="/"?n:pa([n,i])),o.createHref({pathname:f,search:u,hash:a})}function vl(){return j.useContext(rf)!=null}function _a(){return vl()||Ht(!1),j.useContext(rf).location}function _5(e){j.useContext(ai).static||j.useLayoutEffect(e)}function ii(){let{isDataRoute:e}=j.useContext(Mo);return e?vI():iI()}function iI(){vl()||Ht(!1);let e=j.useContext(tf),{basename:t,navigator:r}=j.useContext(ai),{matches:n}=j.useContext(Mo),{pathname:o}=_a(),a=JSON.stringify(Kg(n).map(f=>f.pathnameBase)),i=j.useRef(!1);return _5(()=>{i.current=!0}),j.useCallback(function(f,g){if(g===void 0&&(g={}),!i.current)return;if(typeof f=="number"){r.go(f);return}let b=Yg(f,JSON.parse(a),o,g.relative==="path");e==null&&t!=="/"&&(b.pathname=b.pathname==="/"?t:pa([t,b.pathname])),(g.replace?r.replace:r.push)(b,g.state,g)},[t,r,a,o,e])}function C5(){let{matches:e}=j.useContext(Mo),t=e[e.length-1];return t?t.params:{}}function nf(e,t){let{relative:r}=t===void 0?{}:t,{matches:n}=j.useContext(Mo),{pathname:o}=_a(),a=JSON.stringify(Kg(n).map(i=>i.pathnameBase));return j.useMemo(()=>Yg(e,JSON.parse(a),o,r==="path"),[e,a,o,r])}function lI(e,t){return sI(e,t)}function sI(e,t,r){vl()||Ht(!1);let{navigator:n}=j.useContext(ai),{matches:o}=j.useContext(Mo),a=o[o.length-1],i=a?a.params:{};a&&a.pathname;let u=a?a.pathnameBase:"/";a&&a.route;let f=_a(),g;if(t){var b;let w=typeof t=="string"?bl(t):t;u==="/"||(b=w.pathname)!=null&&b.startsWith(u)||Ht(!1),g=w}else g=f;let _=g.pathname||"/",O=u==="/"?_:_.slice(u.length)||"/",S=FM(e,{pathname:O}),C=pI(S&&S.map(w=>Object.assign({},w,{params:Object.assign({},i,w.params),pathname:pa([u,n.encodeLocation?n.encodeLocation(w.pathname).pathname:w.pathname]),pathnameBase:w.pathnameBase==="/"?u:pa([u,n.encodeLocation?n.encodeLocation(w.pathnameBase).pathname:w.pathnameBase])})),o,r);return t&&C?j.createElement(rf.Provider,{value:{location:dd({pathname:"/",search:"",hash:"",state:null,key:"default"},g),navigationType:oa.Pop}},C):C}function uI(){let e=bI(),t=nI(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),r=e instanceof Error?e.stack:null,o={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"},a=null;return j.createElement(j.Fragment,null,j.createElement("h2",null,"Unexpected Application Error!"),j.createElement("h3",{style:{fontStyle:"italic"}},t),r?j.createElement("pre",{style:o},r):null,a)}const cI=j.createElement(uI,null);class dI extends j.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,r){return r.location!==t.location||r.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error||r.error,location:r.location,revalidation:t.revalidation||r.revalidation}}componentDidCatch(t,r){console.error("React Router caught the following error during render",t,r)}render(){return this.state.error?j.createElement(Mo.Provider,{value:this.props.routeContext},j.createElement(x5.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function fI(e){let{routeContext:t,match:r,children:n}=e,o=j.useContext(tf);return o&&o.static&&o.staticContext&&(r.route.errorElement||r.route.ErrorBoundary)&&(o.staticContext._deepestRenderedBoundaryId=r.route.id),j.createElement(Mo.Provider,{value:t},n)}function pI(e,t,r){var n;if(t===void 0&&(t=[]),r===void 0&&(r=null),e==null){var o;if((o=r)!=null&&o.errors)e=r.matches;else return null}let a=e,i=(n=r)==null?void 0:n.errors;if(i!=null){let u=a.findIndex(f=>f.route.id&&(i==null?void 0:i[f.route.id]));u>=0||Ht(!1),a=a.slice(0,Math.min(a.length,u+1))}return a.reduceRight((u,f,g)=>{let b=f.route.id?i==null?void 0:i[f.route.id]:null,_=null;r&&(_=f.route.errorElement||cI);let O=t.concat(a.slice(0,g+1)),S=()=>{let C;return b?C=_:f.route.Component?C=j.createElement(f.route.Component,null):f.route.element?C=f.route.element:C=u,j.createElement(fI,{match:f,routeContext:{outlet:u,matches:O,isDataRoute:r!=null},children:C})};return r&&(f.route.ErrorBoundary||f.route.errorElement||g===0)?j.createElement(dI,{location:r.location,revalidation:r.revalidation,component:_,error:b,children:S(),routeContext:{outlet:null,matches:O,isDataRoute:!0}}):S()},null)}var S5=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable=Line truncated
|
||||
* React Router DOM v6.17.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/function pd(){return pd=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},pd.apply(this,arguments)}function O5(e,t){if(e==null)return{};var r={},n=Object.keys(e),o,a;for(a=0;a<n.length;a++)o=n[a],!(t.indexOf(o)>=0)&&(r[o]=e[o]);return r}function xI(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function _I(e,t){return e.button===0&&(!t||t==="_self")&&!xI(e)}const CI=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","unstable_viewTransition"],SI=["aria-current","caseSensitive","className","end","style","to","unstable_viewTransition","children"],TI=j.createContext({isTransitioning:!1}),OI="startTransition",gw=Yh[OI];function PI(e){let{basename:t,children:r,future:n,window:o}=e,a=j.useRef();a.current==null&&(a.current=IM({window:o,v5Compat:!0}));let i=a.current,[u,f]=j.useState({action:i.action,location:i.location}),{v7_startTransition:g}=n||{},b=j.useCallback(_=>{g&&gw?gw(()=>f(_)):f(_)},[f,g]);return j.useLayoutEffect(()=>i.listen(b),[i,b]),j.createElement(wI,{basename:t,children:r,location:u.location,navigationType:u.action,navigator:i})}const kI=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",EI=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,P5=j.forwardRef(function(t,r){let{onClick:n,relative:o,reloadDocument:a,replace:i,state:u,target:f,to:g,preventScrollReset:b,unstable_viewTransition:_}=t,O=O5(t,CI),{basename:S}=j.useContext(ai),C,w=!1;if(typeof g=="string"&&EI.test(g)&&(C=g,kI))try{let c=new URL(window.location.href),s=g.startsWith("//")?new URL(c.protocol+g):new URL(g),h=Vs(s.pathname,S);s.origin===c.origin&&h!=null?g=h+s.search+s.hash:w=!0}catch{}let m=aI(g,{relative:o}),d=RI(g,{replace:i,state:u,target:f,preventScrollReset:b,relative:o,unstable_viewTransition:_});function l(c){n&&n(c),c.defaultPrevented||d(c)}return j.createElement("a",pd({},O,{href:C||m,onClick:w||a?n:l,ref:r,target:f}))}),NI=j.forwardRef(function(t,r){let{"aria-current":n="page",caseSensitive:o=!1,className:a="",end:i=!1,style:u,to:f,unstable_viewTransition:g,children:b}=t,_=O5(t,SI),O=nf(f,{relative:_.relative}),S=_a(),C=j.useContext(w5),{navigator:w}=j.useContext(ai),m=C!=null&&AI(O)&&g===!0,d=w.encodeLocation?w.encodeLocation(O).pathname:O.pathname,l=S.pathname,c=C&&C.navigation&&C.navigation.location?C.navigation.location.pathname:null;o||(l=l.toLowerCase(),c=c?c.toLowerCase():null,d=d.toLowerCase());let s=l===d||!i&&l.startsWith(d)&&l.charAt(d.length)==="/",h=c!=null&&(c===d||!i&&c.startsWith(d)&&c.charAt(d.length)==="/"),v={isActive:s,isPending:h,isTransitioning:m},y=s?n:void 0,E;typeof a=="function"?E=a(v):E=[a,s?"active":null,h?"pending":null,m?"transitioning":null].filter(Boolean).join(" ");let P=typeof u=="function"?u(v):u;return j.createElement(P5,pd({},_,{"aria-current":y,className:E,ref:r,style:P,to:f,unstable_viewTransition:g}),typeof b=="function"?b(v):b)});var gh;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(gh||(gh={}));var mw;(function(e){e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(mw||(mw={}));function jI(e){let t=j.useContext(tf);return t||Ht(!1),t}function RI(e,t){let{target:r,replace:n,state:o,preventScrollReset:a,relative:i,unstable_viewTransition:u}=t===void 0?{}:t,f=ii(),g=_a(),b=nf(e,{relative:i});return j.useCallback(_=>{if(_I(_,r)){_.preventDefault();let O=n!==void 0?n:cd(g)===cd(b);f(e,{replace:O,state:o,preventScrollReset:a,relative:i,unstable_viewTransition:u})}},[g,f,b,n,o,r,e,a,i,u])}function AI(e,t){t===void 0&&(t={});let r=j.useContext(TI);r==null&&Ht(!1);let{basename:n}=jI(gh.useViewTransitionState),o=nf(e,{relative:t.relative});if(!r.isTransitioning)return!1;let a=Vs(r.currentLocation.pathname,n)||r.currentLocation.pathname,i=Vs(r.nextLocation.pathname,n)||r.nextLocation.pathname;return ph(o.pathname,i)!=null||ph(o.pathname,a)!=null}function MI({title:e,titleId:t,...r},n){return j.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",ref:n,"aria-labelledby":t},r),e?j.createElement("title",{id:t},e):null,j.createElement("path",{fillRule:"evenodd",d:"M3 6.75A.75.75 0 013.75 6h16.5a.75.75 0 010 1.5H3.75A.75.75 0 013 6.75zM3 12a.75.75 0 01.75-.75h16.5a.75.75 0 010 1.5H3.75A.75.75 0 013 12zm0 5.25a.75.75 0 01.75-.75h16.5a.75.75 0 010 1.5H3.75a.75.75 0 01-.75-.75z",clipRule:"evenodd"}))}const II=j.forwardRef(MI),DI=II;function LI({title:e,titleId:t,...r},n){return j.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",ref:n,"aria-labelledby":t},r),e?j.createElement("title",{id:t},e):null,j.createElement("path",{fillRule:"evenodd",d:"M11.078 2.25c-.917 0-1.69Line truncated
|
||||
Copyright (c) 2018 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/(function(e){(function(){var t={}.hasOwnProperty;function r(){for(var n=[],o=0;o<arguments.length;o++){var a=arguments[o];if(a){var i=typeof a;if(i==="string"||i==="number")n.push(a);else if(Array.isArray(a)){if(a.length){var u=r.apply(null,a);u&&n.push(u)}}else if(i==="object"){if(a.toString!==Object.prototype.toString&&!a.toString.toString().includes("[native code]")){n.push(a.toString());continue}for(var f in a)t.call(a,f)&&a[f]&&n.push(f)}}}return n.join(" ")}e.exports?(r.default=r,e.exports=r):window.classNames=r})()})(N5);var Xe=N5.exports,j5={exports:{}},Nn={};function Xg(){for(var e,t,r=0,n="";r<arguments.length;)(e=arguments[r++])&&(t=R5(e))&&(n&&(n+=" "),n+=t);return n}function R5(e){if(typeof e=="string")return e;for(var t,r="",n=0;n<e.length;n++)e[n]&&(t=R5(e[n]))&&(r&&(r+=" "),r+=t);return r}function mh(){return mh=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},mh.apply(this,arguments)}function i4(e){var t=function(r){var n=r.theme,o=r.prefix,a={nextPart:new Map,validators:[]};return function(i,u){return u?i.map(function(f){return[f[0],f[1].map(function(g){return typeof g=="string"?u+g:typeof g=="object"?Object.fromEntries(Object.entries(g).map(function(b){return[u+b[0],b[1]]})):g})]}):i}(Object.entries(r.classGroups),o).forEach(function(i){bh(i[1],a,i[0],n)}),a}(e);return{getClassGroupId:function(r){var n=r.split("-");return n[0]===""&&n.length!==1&&n.shift(),A5(n,t)||function(o){if(bw.test(o)){var a=bw.exec(o)[1],i=a==null?void 0:a.substring(0,a.indexOf(":"));if(i)return"arbitrary.."+i}}(r)},getConflictingClassGroupIds:function(r){return e.conflictingClassGroups[r]||[]}}}function A5(e,t){var r;if(e.length===0)return t.classGroupId;var n=t.nextPart.get(e[0]),o=n?A5(e.slice(1),n):void 0;if(o)return o;if(t.validators.length!==0){var a=e.join("-");return(r=t.validators.find(function(i){return(0,i.validator)(a)}))==null?void 0:r.classGroupId}}Object.defineProperty(Nn,"__esModule",{value:!0});var bw=/^\[(.+)\]$/;function bh(e,t,r,n){e.forEach(function(o){if(typeof o!="string"){if(typeof o=="function")return o.isThemeGetter?void bh(o(n),t,r,n):void t.validators.push({validator:o,classGroupId:r});Object.entries(o).forEach(function(a){bh(a[1],vw(t,a[0]),r,n)})}else(o===""?t:vw(t,o)).classGroupId=r})}function vw(e,t){var r=e;return t.split("-").forEach(function(n){r.nextPart.has(n)||r.nextPart.set(n,{nextPart:new Map,validators:[]}),r=r.nextPart.get(n)}),r}function l4(e){if(e<1)return{get:function(){},set:function(){}};var t=0,r=new Map,n=new Map;function o(a,i){r.set(a,i),++t>e&&(t=0,n=r,r=new Map)}return{get:function(a){var i=r.get(a);return i!==void 0?i:(i=n.get(a))!==void 0?(o(a,i),i):void 0},set:function(a,i){r.has(a)?r.set(a,i):o(a,i)}}}function s4(e){var t=e.separator||":";return function(r){for(var n=0,o=[],a=0,i=0;i<r.length;i++){var u=r[i];n===0&&u===t[0]&&(t.length!==1&&r.slice(i,i+t.length)!==t||(o.push(r.slice(a,i)),a=i+t.length)),u==="["?n++:u==="]"&&n--}var f=o.length===0?r:r.substring(a),g=f.startsWith("!");return{modifiers:o,hasImportantModifier:g,baseClassName:g?f.substring(1):f}}}function u4(e){return mh({cache:l4(e.cacheSize),splitModifiers:s4(e)},i4(e))}var c4=/\s+/;function d4(e,t){var r=t.splitModifiers,n=t.getClassGroupId,o=t.getConflictingClassGroupIds,a=new Set;return e.trim().split(c4).map(function(i){var u=r(i),f=u.modifiers,g=u.hasImportantModifier,b=n(u.baseClassName);if(!b)return{isTailwindClass:!1,originalClassName:i};var _=function(O){if(O.length<=1)return O;var S=[],C=[];return O.forEach(function(w){w[0]==="["?(S.push.apply(S,C.sort().concat([w])),C=[]):C.push(w)}),S.push.apply(S,C.sort()),S}(f).join(":");return{isTailwindClass:!0,modifierId:g?_+"!":_,classGroupId:b,originalClassName:i}}).reverse().filter(function(i){if(!i.isTailwindClass)return!0;var u=i.modifierId,f=i.classGroupId,g=u+f;return!a.has(g)&&(a.add(g),o(f).forEach(function(b){return a.add(u+b)}),!0)}).reverse().map(function(i){return i.originalClassName}).join(" ")}function vh(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];var n,o,a,i=u;function u(g){var b=t[0],_=t.slice(1).reduce(function(O,S){return S(O)},b());return n=u4(_),o=n.cache.get,a=n.cache.set,i=f,f(g)}function f(g){var b=o(g);if(b)return b;var _=d4(g,n);return a(g,_),_}return function(){return i(Xg.apply(null,arguments))}}function Mt(e){var t=function(r){return r[e]||[]};return t.isThemeGetter=!0,t}var Ca=/^\[(.+)\]$/,f4=/^\d+\/\d+$/,p4=new Set(["px","full","screen"]),h4=/^(\d+)?(xs|sm|md|lg|xl)$/,g4=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh)/,m4=/^-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/;function gn(e){return!Number.isNaN(Number(e))||p4.has(e)||f4.test(e)||Zo(e)}function Zo(e){var t,r=(t=Ca.exec(e))==null?void 0:t[1];return!!r&&(r.startsWith("length:")||g4.test(r))}function M5(e){var t,r=(t=Ca.exec(e))==null?void 0:t[1];return!!r&&r.startsWLine truncated
|
||||
* tabbable 6.2.0
|
||||
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE
|
||||
*/var iF=["input:not([inert])","select:not([inert])","textarea:not([inert])","a[href]:not([inert])","button:not([inert])","[tabindex]:not(slot):not([inert])","audio[controls]:not([inert])","video[controls]:not([inert])",'[contenteditable]:not([contenteditable="false"]):not([inert])',"details>summary:first-of-type:not([inert])","details:not([inert])"],Ah=iF.join(","),VO=typeof Element>"u",Gs=VO?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,Cd=!VO&&Element.prototype.getRootNode?function(e){var t;return e==null||(t=e.getRootNode)===null||t===void 0?void 0:t.call(e)}:function(e){return e==null?void 0:e.ownerDocument},Sd=function e(t,r){var n;r===void 0&&(r=!0);var o=t==null||(n=t.getAttribute)===null||n===void 0?void 0:n.call(t,"inert"),a=o===""||o==="true",i=a||r&&t&&e(t.parentNode);return i},lF=function(t){var r,n=t==null||(r=t.getAttribute)===null||r===void 0?void 0:r.call(t,"contenteditable");return n===""||n==="true"},sF=function(t,r,n){if(Sd(t))return[];var o=Array.prototype.slice.apply(t.querySelectorAll(Ah));return r&&Gs.call(t,Ah)&&o.unshift(t),o=o.filter(n),o},uF=function e(t,r,n){for(var o=[],a=Array.from(t);a.length;){var i=a.shift();if(!Sd(i,!1))if(i.tagName==="SLOT"){var u=i.assignedElements(),f=u.length?u:i.children,g=e(f,!0,n);n.flatten?o.push.apply(o,g):o.push({scopeParent:i,candidates:g})}else{var b=Gs.call(i,Ah);b&&n.filter(i)&&(r||!t.includes(i))&&o.push(i);var _=i.shadowRoot||typeof n.getShadowRoot=="function"&&n.getShadowRoot(i),O=!Sd(_,!1)&&(!n.shadowRootFilter||n.shadowRootFilter(i));if(_&&O){var S=e(_===!0?i.children:_.children,!0,n);n.flatten?o.push.apply(o,S):o.push({scopeParent:i,candidates:S})}else a.unshift.apply(a,i.children)}}return o},UO=function(t){return!isNaN(parseInt(t.getAttribute("tabindex"),10))},WO=function(t){if(!t)throw new Error("No node provided");return t.tabIndex<0&&(/^(AUDIO|VIDEO|DETAILS)$/.test(t.tagName)||lF(t))&&!UO(t)?0:t.tabIndex},cF=function(t,r){var n=WO(t);return n<0&&r&&!UO(t)?0:n},dF=function(t,r){return t.tabIndex===r.tabIndex?t.documentOrder-r.documentOrder:t.tabIndex-r.tabIndex},HO=function(t){return t.tagName==="INPUT"},fF=function(t){return HO(t)&&t.type==="hidden"},pF=function(t){var r=t.tagName==="DETAILS"&&Array.prototype.slice.apply(t.children).some(function(n){return n.tagName==="SUMMARY"});return r},hF=function(t,r){for(var n=0;n<t.length;n++)if(t[n].checked&&t[n].form===r)return t[n]},gF=function(t){if(!t.name)return!0;var r=t.form||Cd(t),n=function(u){return r.querySelectorAll('input[type="radio"][name="'+u+'"]')},o;if(typeof window<"u"&&typeof window.CSS<"u"&&typeof window.CSS.escape=="function")o=n(window.CSS.escape(t.name));else try{o=n(t.name)}catch(i){return console.error("Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s",i.message),!1}var a=hF(o,t.form);return!a||a===t},mF=function(t){return HO(t)&&t.type==="radio"},bF=function(t){return mF(t)&&!gF(t)},vF=function(t){var r,n=t&&Cd(t),o=(r=n)===null||r===void 0?void 0:r.host,a=!1;if(n&&n!==t){var i,u,f;for(a=!!((i=o)!==null&&i!==void 0&&(u=i.ownerDocument)!==null&&u!==void 0&&u.contains(o)||t!=null&&(f=t.ownerDocument)!==null&&f!==void 0&&f.contains(t));!a&&o;){var g,b,_;n=Cd(o),o=(g=n)===null||g===void 0?void 0:g.host,a=!!((b=o)!==null&&b!==void 0&&(_=b.ownerDocument)!==null&&_!==void 0&&_.contains(o))}}return a},qw=function(t){var r=t.getBoundingClientRect(),n=r.width,o=r.height;return n===0&&o===0},yF=function(t,r){var n=r.displayCheck,o=r.getShadowRoot;if(getComputedStyle(t).visibility==="hidden")return!0;var a=Gs.call(t,"details>summary:first-of-type"),i=a?t.parentElement:t;if(Gs.call(i,"details:not([open]) *"))return!0;if(!n||n==="full"||n==="legacy-full"){if(typeof o=="function"){for(var u=t;t;){var f=t.parentElement,g=Cd(t);if(f&&!f.shadowRoot&&o(f)===!0)return qw(t);t.assignedSlot?t=t.assignedSlot:!f&&g!==t.ownerDocument?t=g.host:t=f}t=u}if(vF(t))return!t.getClientRects().length;if(n!=="legacy-full")return!0}else if(n==="non-zero-area")return qw(t);return!1},wF=function(t){if(/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(t.tagName))for(var r=t.parentElement;r;){if(r.tagName==="FIELDSET"&&r.disabled){for(var n=0;n<r.children.length;n++){var o=r.children.item(n);if(o.tagName==="LEGEND")return Gs.call(r,"fieldset[disabled] *")?!0:!o.contains(t)}return!0}r=r.parentElement}return!1},xF=function(t,r){return!(r.disabled||Sd(r)||fF(r)||yF(r,t)||pF(r)||wF(r))},Gw=function(t,r){return!(bF(r)||WO(r)<0||!xF(t,r))},_F=function(t){var r=parseInt(t.getAttribute("tabindex"),10);return!!(isNaN(r)||r>=0)},CF=function e(t){var r=[],n=[];return t.forEach(function(o,a){var i=!!o.scopeParent,u=i?o.scopeParent:o,f=cF(u,i),g=i?e(o.candidates):u;f===0?i?r.push.apply(r,g):r.push(u):n.push({documentOrder:a,tabIndex:f,item:o,isScope:i,content:g})}),n.sort(dF).reduce(function(o,a){return a.isScope?o.push.apply(o,a.content):o.push(a.content),o},[]).concatLine truncated
|
||||
|
||||
More details: https://www.material-tailwind.com/docs/react/collapse
|
||||
`);var P=r.default.useRef(null),N=(0,g.useTheme)().navbar,R=N.styles,M=R.base.mobileNav;s=s??{},h=h??"";var D=(0,u.twMerge)((0,i.default)((0,f.default)(M)),h),L={unmount:{height:0,opacity:0,transition:{duration:.3,times:"[0.4, 0, 0.2, 1]"}},mount:{opacity:1,height:"".concat((E=P.current)===null||E===void 0?void 0:E.scrollHeight,"px"),transition:{duration:.3,times:"[0.4, 0, 0.2, 1]"}}},W=(0,a.default)(L,s),U=n.AnimatePresence,Y=(0,o.useMergeRefs)([l,P]);return r.default.createElement(n.LazyMotion,{features:n.domAnimation},r.default.createElement(U,null,r.default.createElement(n.m.div,_({},y,{ref:Y,className:D,initial:"unmount",exit:"unmount",animate:c?"mount":"unmount",variants:W}),v)))});w.displayName="MaterialTailwind.MobileNav",w.propTypes={open:b.propTypesOpen,animate:b.propTypesAnimate,className:b.propTypesClassName,children:b.propTypesChildren};var m=w})(AP);(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(l,c){for(var s in c)Object.defineProperty(l,s,{enumerable:!0,get:c[s]})}t(e,{Navbar:function(){return m},MobileNav:function(){return g.MobileNav},default:function(){return d}});var r=S(j),n=S($e),o=S(Xe),a=He,i=S(zt),u=S(Be),f=ze,g=AP,b=of;function _(l,c,s){return c in l?Object.defineProperty(l,c,{value:s,enumerable:!0,configurable:!0,writable:!0}):l[c]=s,l}function O(){return O=Object.assign||function(l){for(var c=1;c<arguments.length;c++){var s=arguments[c];for(var h in s)Object.prototype.hasOwnProperty.call(s,h)&&(l[h]=s[h])}return l},O.apply(this,arguments)}function S(l){return l&&l.__esModule?l:{default:l}}function C(l,c){if(l==null)return{};var s=w(l,c),h,v;if(Object.getOwnPropertySymbols){var y=Object.getOwnPropertySymbols(l);for(v=0;v<y.length;v++)h=y[v],!(c.indexOf(h)>=0)&&Object.prototype.propertyIsEnumerable.call(l,h)&&(s[h]=l[h])}return s}function w(l,c){if(l==null)return{};var s={},h=Object.keys(l),v,y;for(y=0;y<h.length;y++)v=h[y],!(c.indexOf(v)>=0)&&(s[v]=l[v]);return s}var m=r.default.forwardRef(function(l,c){var s=l.variant,h=l.color,v=l.shadow,y=l.blurred,E=l.fullWidth,P=l.className,N=l.children,R=C(l,["variant","color","shadow","blurred","fullWidth","className","children"]),M=(0,f.useTheme)().navbar,D=M.defaultProps,L=M.valid,W=M.styles,U=W.base,Y=W.variants;s=s??D.variant,h=h??D.color,v=v??D.shadow,y=y??D.blurred,E=E??D.fullWidth,P=P??D.className;var $,z=(0,o.default)((0,u.default)(U.navbar.initial),($={},_($,(0,u.default)(U.navbar.shadow),v),_($,(0,u.default)(U.navbar.blurred),y&&h==="white"),_($,(0,u.default)(U.navbar.fullWidth),E),$)),H=(0,o.default)((0,u.default)(Y[(0,i.default)(L.variants,s,"filled")][(0,i.default)(L.colors,h,"white")])),G=(0,a.twMerge)((0,o.default)(z,H),P);return r.default.createElement("nav",O({},R,{ref:c,className:G}),N)});m.propTypes={variant:n.default.oneOf(b.propTypesVariant),color:n.default.oneOf(b.propTypesColor),shadow:b.propTypesShadow,blurred:b.propTypesBlurred,fullWidth:b.propTypesFullWidth,className:b.propTypesClassName,children:b.propTypesChildren},m.displayName="MaterialTailwind.Navbar";var d=Object.assign(m,{MobileNav:g.MobileNav})})(RP);var MP={},Of={},Ol={};(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(m,d){for(var l in d)Object.defineProperty(m,l,{enumerable:!0,get:d[l]})}t(e,{propTypesOpen:function(){return a},propTypesHandler:function(){return i},propTypesPlacement:function(){return u},propTypesOffset:function(){return f},propTypesDismiss:function(){return g},propTypesAnimate:function(){return b},propTypesContent:function(){return _},propTypesInteractive:function(){return O},propTypesClassName:function(){return S},propTypesChildren:function(){return C},propTypesContextValue:function(){return w}});var r=o($e),n=Dt;function o(m){return m&&m.__esModule?m:{default:m}}var a=r.default.bool,i=r.default.func,u=n.propTypesPlacements,f=n.propTypesOffsetType,g=n.propTypesDismissType,b=n.propTypesAnimation,_=r.default.node,O=r.default.bool,S=r.default.string,C=r.default.node.isRequired,w=r.default.shape({open:r.default.bool.isRequired,strategy:r.default.oneOf(["fixed","absolute"]).isRequired,x:r.default.number,y:r.default.number,context:r.default.instanceOf(Object).isRequired,reference:r.default.func.isRequired,floating:r.default.func.isRequired,getReferenceProps:r.default.func.isRequired,getFloatingProps:r.default.func.isRequired,appliedAnimation:b.isRequired,labelId:r.default.string.isRequired,descriptionId:r.default.string.isRequired}).isRequired})(Ol);(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(f,g){for(var b in g)Object.defineProperty(f,b,{enumerable:!0,get:g[b]})}t(e,{PopoverContext:function(){return a},usePopover:function(){return i},PopoverContextProvider:function(){return u}});var r=o(j),n=Ol;function o(f){return f&&f.__esModule?f:{default:f}}var a=r.default.createContext(null);a.displayName="MaterialTailwind.PopoverContext";function i(){var f=r.default.useContext(a);if(!f)throw new Error("usePopover() must be used within a Popover. It happens when you us
Line truncated
|
||||
${we?"border-purple-500 ring-1 ring-purple-500/40":"border-transparent"}
|
||||
${!Ze||Ce?"opacity-75 bg-stormGrey-200/20":""}`,children:[T.jsxs("div",{className:"flex items-center space-x-4",id:"left",children:[T.jsx("input",{type:"checkbox",checked:we,onChange:()=>Te(te.Id,!Ze),className:`h-5 w-5 text-purple-500 focus:ring-purple-400 border-gray-600 bg-gray-900 rounded ${Ze?"":"opacity-40 cursor-not-allowed"}`,disabled:!Ze}),T.jsx("div",{className:`w-12 h-12 flex items-center justify-center rounded-full
|
||||
${te.updateStatus==="✅"?"bg-mossGreen-600":te.updateStatus==="⚠️"?"bg-warmAmberGlow-400":"bg-sunsetCoral-500"}`}),T.jsxs("div",{children:[T.jsx("p",{className:"text-lg antialiased font-sans font-semibold leading-normal text-inherit",children:te.Name}),T.jsxs("p",{className:"text-sm antialiased font-sans font-light leading-normal text-inherit",children:["ID: ",te.Id]}),te.duplicateName&&T.jsx("p",{className:"text-xs text-amber-700",children:"⚠️ Doppelter Name erkannt"})]})]}),T.jsx("div",{className:"ml-auto flex flex-col items-end gap-1 text-sm",id:"right",children:Pe?T.jsxs(T.Fragment,{children:[T.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-warmAmberGlow-600",children:"Redeploy"}),T.jsx("span",{className:"text-warmAmberGlow-500",children:"läuft…"})]}):_e?T.jsxs(T.Fragment,{children:[T.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-warmAmberGlow-600",children:"Redeploy"}),T.jsx("span",{className:"text-warmAmberGlow-500",children:"in Warteliste…"})]}):Ue?T.jsxs(T.Fragment,{children:[T.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-stormGrey-500",children:"System"}),T.jsx("span",{className:"text-gray-300",children:"Redeploy deaktiviert"})]}):Ie?T.jsxs(T.Fragment,{children:[T.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-stormGrey-500",children:"Status"}),T.jsx("span",{className:"text-mossGreen-600",children:"Aktuell"})]}):T.jsx(Q.Button,{onClick:()=>qe(te.Id),disabled:Ce,className:"disabled:opacity-50 disabled:cursor-not-allowed",children:"Redeploy"})})]})},te.Id)})}),T.jsx(jf,{disabled:r||!!o})]})}const NB={idle:"Bereit",running:"Läuft",success:"Erfolgreich",error:"Fehlgeschlagen"},es={idle:"bg-stormGrey-700 text-gray-200",running:"bg-arcticBlue-600 text-white",success:"bg-mossGreen-600 text-white",error:"bg-sunsetCoral-600 text-white"},jB={initializing:"Vorbereitung","activating-maintenance":"Wartungsmodus aktivieren","executing-script":"Skript wird ausgeführt",waiting:"Warte auf Portainer",completed:"Abgeschlossen",failed:"Fehlgeschlagen"},RB={info:"text-arcticBlue-700",success:"text-mossGreen-700",warning:"text-warmAmberGlow-700",error:"text-sunsetCoral-700",stdout:"text-stormGrey-700",stderr:"text-warmAmberGlow-700",debug:"text-lavenderSmoke-700"},AB=e=>{if(!e)return"-";const t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString("de-DE",{hour:"2-digit",minute:"2-digit",second:"2-digit"})},Ma=e=>{if(!e&&e!==0)return"-";const r=(n=>{if(n instanceof Date)return n;if(typeof n=="number"){const o=n>1e12?n:n*1e3;return new Date(o)}if(typeof n=="string"){const o=Number(n);if(!Number.isNaN(o)){const a=o>1e12?o:o*1e3;return new Date(a)}return new Date(n)}return null})(e);return!r||Number.isNaN(r.getTime())?String(e):r.toLocaleString("de-DE",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"})},kx=e=>e===1?"Git":e===2?"Compose":e??"-",c0=()=>({host:"",port:"22",username:"",password:"",extraSshArgs:""});function MB(){var Xn,Ea,Rl;const{showToast:e}=El(),{maintenance:t,update:r,script:n,ssh:o,loading:a,error:i,setMaintenanceMode:u,triggerUpdate:f,saveScript:g,resetScript:b,saveSshConfig:_,deleteSshConfig:O,testSshConnection:S,fetchSuperuserStatus:C,fetchSetupStatus:w,deleteSetupEndpoint:m,deleteSetupServer:d,updateSetupApiKey:l,removeSuperuserAccount:c}=pi(),s=!!(t!=null&&t.active),h=t==null?void 0:t.message,v=(Xn=t==null?void 0:t.extra)==null?void 0:Xn.type,y=!!(r!=null&&r.running),[E,P]=j.useState(""),[N,R]=j.useState(!1),[M,D]=j.useState(!1),[L,W]=j.useState(""),[U,Y]=j.useState(!1),[$,z]=j.useState(()=>c0()),[H,G]=j.useState(!1),[X,ie]=j.useState(!1),[fe,pe]=j.useState(!1),[oe,se]=j.useState(!1),[ee,ae]=j.useState(!1),[ue,de]=j.useState(null);j.useEffect(()=>{if(!n)return;const he=n.custom??n.default??n.effective??"";P(he)},[n]),j.useEffect(()=>{if(!o){z(c0()),G(!1),ie(!1),de(null);return}z({host:o.host??"",port:String(o.port??"22"),username:o.username??"",password:"",extraSshArgs:Array.isArray(o.extraSshArgs)?o.extraSshArgs.join(`
|
||||
`):""}),G(!!o.passwordStored),ie(!1),de(null)},[o]);const Te=j.useMemo(()=>n?n.source==="custom"&&typeof n.custom=="string"?n.custom:n.default??n.effective??"":"",[n]),xe=n?E!==Te:!1,be=(n==null?void 0:n.source)==="custom"?"Benutzerdefiniert":"Standard",[Se,je]=j.useState(!0),[qe,nt]=j.useState(!1),[ut,Ye]=j.useState(null),[rt,Je]=j.useState(""),[Ve,me]=j.useState(!1),[te,ge]=j.useState(null),[_e,Pe]=j.useState(!0),[Ce,we]=j.useState(""),[Ie,Ue]=j.useState({}),[Ze,Bt]=j.useState(null),[et,Xt]=j.useState(null),[qt,Et]=j.useState(null),[tr,Jt]=j.useState([]),[Nt,Nr]=j.useState(!0),[bt,Gt]=j.useState(""),[On,Gn]=j.useState(!1),[fo,Kr]=j.useState(null),[Kn,wr]=j.useState(null),Yr=j.useRef(null),[pt,po]=j.useState(null),[Z,Oe]=j.useState(!0),[Re,_t]=j.useState(""),[jt,vt]=j.useState(!1),[Ct,cr]=j.useState(null),lt=j.useRef(null),xr=j.useCallback(async({silent:he=!1}={})=>{var Ne,Ae;je(!0),Je("");try{const Me=await C(),Ke=!!(Me!=null&&Me.exists);nt(Ke),Ye(Ke?Me.user??null:null)}catch(Me){const Ke=((Ae=(Ne=Me.response)==null?void 0:Ne.data)==null?void 0:Ae.error)||Me.message||"Superuser-Status konnte nicht geladen werden";nt(!1),Ye(null),Je(Ke),he||e({variant:"error",title:"Statusabfrage fehlgeschlagen",description:Ke})}finally{je(!1)}},[C,e]),Zt=j.useCallback(async({silent:he=!1}={})=>{var Ne,Ae;he||Pe(!0),we("");try{const Me=await w();ge(Me),Ue({})}catch(Me){const Ke=((Ae=(Ne=Me.response)==null?void 0:Ne.data)==null?void 0:Ae.error)||Me.message||"Setup-Daten konnten nicht geladen werden";we(Ke),he||e({variant:"error",title:"Setup-Daten",description:Ke})}finally{Pe(!1)}},[w,e]),ar=j.useCallback(async({silent:he=!1}={})=>{if(lt.current)return lt.current;const Ne=(async()=>{var Ae,Me;he?vt(!0):Oe(!0),_t("");try{const Tt=(await at.get("/api/maintenance/portainer-status")).data??{};po(Tt),cr(new Date)}catch(Ke){const Tt=((Me=(Ae=Ke.response)==null?void 0:Ae.data)==null?void 0:Me.error)||Ke.message||"Fehler beim Prüfen des Portainer-Status";_t(Tt),e({variant:"error",title:"Statusaktualisierung fehlgeschlagen",description:Tt})}finally{he?vt(!1):Oe(!1),lt.current=null}})();return lt.current=Ne,Ne},[e]),Fr=j.useCallback(async({silent:he=!1}={})=>{if(s||y){Jt([]),Gt("Wartungsmodus aktiv – Duplikat-Verwaltung ist vorübergehend deaktiviert."),Nr(!1),Gn(!1);return}if(Yr.current)return Yr.current;const Ne=(async()=>{var Ae,Me,Ke;he?Gn(!0):Nr(!0),Gt("");try{const wt=(await at.get("/api/maintenance/duplicates")).data,Yt=Array.isArray(wt)?wt:(wt==null?void 0:wt.items)??[];Jt(Yt),wr(new Date)}catch(Tt){if(((Ae=Tt.response)==null?void 0:Ae.status)===423){const wt="Wartungsmodus aktiv – Duplikat-Verwaltung ist vorübergehend deaktiviert.";Jt([]),Gt(wt),e({variant:"warning",title:"Duplikat-Verwaltung gesperrt",description:wt})}else{const wt=((Ke=(Me=Tt.response)==null?void 0:Me.data)==null?void 0:Ke.error)||Tt.message||"Fehler beim Laden der Wartungsdaten";Gt(wt),e({variant:"error",title:"Duplikate konnten nicht geladen werden",description:wt})}}finally{he?Gn(!1):Nr(!1),Yr.current=null}})();return Yr.current=Ne,Ne},[s,y,e]);j.useEffect(()=>{xr({silent:!0})},[xr]),j.useEffect(()=>{Zt()},[Zt]),j.useEffect(()=>{ar()},[ar]),j.useEffect(()=>{Fr()},[Fr]);const Pn=j.useMemo(()=>{var he;return((he=te==null?void 0:te.servers)==null?void 0:he.items)??[]},[te]),Qr=j.useMemo(()=>{var he;return((he=te==null?void 0:te.endpoints)==null?void 0:he.items)??[]},[te]),Sa=j.useMemo(()=>{var Ne;const he=((Ne=te==null?void 0:te.apiKeys)==null?void 0:Ne.items)??[];return new Map(he.map(Ae=>[Ae.serverId,Ae]))},[te]),Ta=j.useMemo(()=>!!(te!=null&&te.setupComplete),[te]),Yn=j.useMemo(()=>{const he=Array.isArray(tr)?tr.length:0,Ne=Array.isArray(tr)?tr.reduce((Ae,Me)=>{var Ke;return Ae+(((Ke=Me==null?void 0:Me.duplicates)==null?void 0:Ke.length)||0)},0):0;return{groups:he,duplicateCount:Ne}},[tr]),Xr=j.useCallback((he,Ne)=>{z(Ae=>({...Ae,[he]:Ne})),he==="password"&&G(!1)},[]),Qn=j.useMemo(()=>{const he={host:$.host.trim(),port:Number.parseInt($.port,10)||22,username:$.username.trim(),extraSshArgs:($.extraSshArgs||"").split(/\r?\n/).map(Ae=>Ae.trim()).filter(Boolean)},Ne=$.password??"";return Ne?he.password=Ne:H||(he.password=""),he},[$,H]),hi=j.useCallback(async()=>{var he,Ne;if(n)try{R(!0),await g(E),e({variant:"success",title:"Skript gespeichert",description:"Das benutzerdefinierte Portainer-Update-Skript wurde aktualisiert."})}catch(Ae){const Me=((Ne=(he=Ae.response)==null?void 0:he.data)==null?void 0:Ne.error)||Ae.message||"Skript konnte nicht gespeichert werden";e({variant:"error",title:"Speichern fehlgeschlagen",description:Me})}finally{R(!1)}},[g,E,n,e]),ho=j.useCallback(async()=>{var he,Ne;try{R(!0),await b(),e({variant:"info",title:"Standardskript wiederhergestellt",description:"Es wird wieder das Standard-Update-Skript verwendet."})}catch(Ae){const Me=((Ne=(he=Ae.response)==null?void 0:he.data)==null?void 0:Ne.error)||Ae.message||"Standardskript konnte nicht wiederhergestellt werden";e({variant:"error",title:"Zurücksetzen fehlgeschlagen",descr
Line truncated
|
||||
Dies setzt die Superuser-Einrichtung zurück.`)))try{me(!0);const Me=await c(),Ke=Number((Me==null?void 0:Me.usersRemoved)??0);e({variant:"success",title:"Superuser gelöscht",description:Ke>1?`${Ke} Konten aus der Superuser-Gruppe wurden entfernt.`:"Superuser-Konto wurde entfernt."}),await xr({silent:!0})}catch(Me){if(((he=Me.response)==null?void 0:he.status)===404)await xr({silent:!0}),e({variant:"info",title:"Kein Superuser vorhanden",description:"Es ist kein Superuser mehr vorhanden."});else{const Tt=((Ae=(Ne=Me.response)==null?void 0:Ne.data)==null?void 0:Ae.error)||Me.message||"Superuser konnte nicht gelöscht werden";e({variant:"error",title:"Löschen fehlgeschlagen",description:Tt})}}finally{me(!1)}},[Ve,Se,qe,c,xr,e]),jl=j.useCallback(async he=>{var Ke,Tt;if(qt===he)return;const Ne=Qr.find(wt=>wt.id===he);if(!Ne)return;const Ae=Ne.name||`Endpoint ${Ne.external_id}`,Me=`Endpoint "${Ae}" wirklich löschen?${Ne.is_default?`
|
||||
Dieser Endpoint ist aktuell als Standard markiert.`:""}`;if(!(typeof window<"u"&&!window.confirm(Me))){Et(he);try{await m(he),e({variant:"success",title:"Endpoint gelöscht",description:`Endpoint "${Ae}" wurde entfernt.`}),await Zt({silent:!0})}catch(wt){const Yt=((Tt=(Ke=wt.response)==null?void 0:Ke.data)==null?void 0:Tt.error)||wt.message||"Endpoint konnte nicht gelöscht werden.";e({variant:"error",title:"Endpoint löschen fehlgeschlagen",description:Yt})}finally{Et(null)}}},[m,qt,Zt,Qr,e]),Pa=j.useCallback(async he=>{var Tt,wt;if(et===he)return;const Ne=Pn.find(Yt=>Yt.id===he);if(!Ne)return;const Ae=Qr.filter(Yt=>Yt.server_id===he),Me=Ne.name||Ne.url||`Server ${he}`,Ke=[`Server "${Me}" wirklich löschen?`,Ae.length?`Dabei werden ${Ae.length} zugeordnete Endpoint(s) entfernt.`:null,"Das System benötigt anschließend erneut einen gültigen Server/Endpoint im Setup."].filter(Boolean).join(`
|
||||
`);if(!(typeof window<"u"&&!window.confirm(Ke))){Xt(he);try{await d(he),e({variant:"success",title:"Server gelöscht",description:`Server "${Me}" wurde entfernt.`}),await Zt({silent:!0})}catch(Yt){const _r=((wt=(Tt=Yt.response)==null?void 0:Tt.data)==null?void 0:wt.error)||Yt.message||"Server konnte nicht gelöscht werden.";e({variant:"error",title:"Server löschen fehlgeschlagen",description:_r})}finally{Xt(null)}}},[d,Zt,et,Qr,Pn,e]),go=j.useCallback((he,Ne)=>{Ue(Ae=>({...Ae,[he]:Ne}))},[]),Pt=j.useCallback(async he=>{var Ae,Me;const Ne=(Ie[he]??"").trim();if(!Ne){e({variant:"warning",title:"API-Key fehlt",description:"Bitte gib einen API-Key ein."});return}if(Ze!==he){Bt(he);try{await l(he,Ne),e({variant:"success",title:"API-Key aktualisiert",description:"Der API-Key wurde gespeichert."}),Ue(Ke=>({...Ke,[he]:""})),await Zt({silent:!0})}catch(Ke){const Tt=((Me=(Ae=Ke.response)==null?void 0:Ae.data)==null?void 0:Me.error)||Ke.message||"API-Key konnte nicht aktualisiert werden.";e({variant:"error",title:"Aktualisierung fehlgeschlagen",description:Tt})}finally{Bt(null)}}},[Ie,Ze,Zt,e,l]),Kt=j.useCallback(async he=>{var Ne,Ae;if(!(a||U)&&he!==s){if(y){e({variant:"warning",title:"Aktion nicht möglich",description:"Während eines laufenden Updates kann der Wartungsmodus nicht geändert werden."});return}Y(!0);try{const Me=he&&!!v,Ke={active:he};Me&&h?Ke.message=h:Ke.message=null,await u(Ke),e({variant:he?"info":"success",title:he?"Wartungsmodus aktiviert":"Wartungsmodus deaktiviert",description:he?"Benutzer sehen jetzt die Wartungsseite.":"StackPulse steht wieder zur Verfügung."})}catch(Me){const Ke=((Ae=(Ne=Me.response)==null?void 0:Ne.data)==null?void 0:Ae.error)||Me.message||"Wartungsmodus konnte nicht geändert werden";e({variant:"error",title:"Änderung fehlgeschlagen",description:Ke})}finally{Y(!1)}}},[a,U,s,y,u,h,v,e]),Rt=j.useCallback(async()=>{var Ne,Ae;if(s||y){e({variant:"warning",title:"Update nicht möglich",description:"Während eines aktiven Wartungsmodus kann kein weiteres Update gestartet werden."});return}const he=(pt==null?void 0:pt.latestVersion)??(pt==null?void 0:pt.currentVersion)??"unbekannt";if(!(typeof window<"u"&&!window.confirm(`Portainer-Update starten?
|
||||
Zielversion: ${he}.
|
||||
Während des Updates befindet sich StackPulse im Wartungsmodus.
|
||||
Fortfahren?`)))try{W(""),D(!0),await f(),e({variant:"info",title:"Update gestartet",description:"Das Portainer-Update wurde gestartet."})}catch(Me){const Ke=((Ae=(Ne=Me.response)==null?void 0:Ne.data)==null?void 0:Ae.error)||Me.message||"Portainer-Update konnte nicht gestartet werden";W(Ke),e({variant:"error",title:"Update fehlgeschlagen",description:Ke})}finally{D(!1)}},[s,y,pt,f,e]),cn=j.useCallback(async he=>{var Ke,Tt,wt,Yt;if(!he)return;if(s||y){e({variant:"warning",title:"Aktion nicht möglich",description:"Während des Wartungsmodus sind Bereinigungen deaktiviert."});return}const Ne=(Ke=he.canonical)==null?void 0:Ke.Id;if(!Ne)return;const Ae=(he.duplicates||[]).map(_r=>_r.Id).filter(Boolean);if(!Ae.length)return;const Me=((Tt=he.canonical)==null?void 0:Tt.Name)||he.name||`Stack ${Ne}`;if(!(typeof window<"u"&&!window.confirm(`Bereinigung für "${Me}" starten?
|
||||
Es werden ${Ae.length} Duplikate entfernt: ${Ae.join(", ")}`))){Kr(String(Ne));try{const mo=(await at.post("/api/maintenance/duplicates/cleanup",{canonicalId:Ne,duplicateIds:Ae})).data??{};if(mo.success===!1)throw new Error(mo.error||"Bereinigung fehlgeschlagen");const Al=Array.isArray(mo.results)?mo.results.filter(Na=>Na.status==="deleted").map(Na=>Na.id):Ae;e({variant:"success",title:"Bereinigung abgeschlossen",description:`${Me} – entfernte IDs: ${Al.join(", ")}`}),await Fr({silent:!0})}catch(_r){const mo=((Yt=(wt=_r.response)==null?void 0:wt.data)==null?void 0:Yt.error)||_r.message||"Bereinigung fehlgeschlagen";e({variant:"error",title:"Bereinigung fehlgeschlagen",description:mo})}finally{Kr(null)}}},[Fr,s,e,y]);t!=null&&t.activatedAt&&Ma(t.activatedAt),t!=null&&t.updatedAt&&Ma(t.updatedAt);const jr=s||y,dn=(r==null?void 0:r.status)??(y?"running":"idle"),ka=NB[dn]??dn,gi=es[dn]??es.idle,mi=r!=null&&r.stage?jB[r.stage]??r.stage:"–",bi=(r==null?void 0:r.logs)??[],Rf=(r==null?void 0:r.targetVersion)??"-",Af=(r==null?void 0:r.resultVersion)??"-",Mf=r!=null&&r.startedAt?Ma(r.startedAt):"-",If=r!=null&&r.finishedAt?Ma(r.finishedAt):"-",vu=(r==null?void 0:r.message)??(y?"Update läuft…":""),yu=y||N||a;return T.jsxs("div",{className:"mt-12 mb-8 flex flex-col gap-12",children:[(s||y)&&T.jsx("div",{className:"rounded-lg border border-cyan-500/60 bg-cyan-900/30 px-4 py-3 text-sm text-bluegray-100",children:T.jsxs("div",{className:"flex flex-col gap-1",children:[T.jsxs("span",{children:["Wartungsmodus aktiv",h?` – ${h}`:y?" – Portainer-Update läuft":"","."]}),y&&T.jsxs("span",{className:"text-xs text-indigo-900",children:["Phase: ",mi]})]})}),T.jsxs(Q.Card,{children:[T.jsx(Q.CardHeader,{variant:"gradient",color:"gray",className:"mb-5 p-4",children:T.jsx(Q.Typography,{variant:"h6",color:"white",className:"flex items-center justify-between",children:T.jsx("span",{children:"Wartungsmodus"})})}),T.jsxs(Q.CardBody,{className:"flex flex-col gap-4 p-4",children:[T.jsxs("div",{className:"flex items-start justify-between gap-4",children:[T.jsx("div",{className:"space-y-1",children:T.jsx("p",{className:"text-sm antialiased font-sans text-sm font-light leading-normal text-inherit",children:"Schaltet StackPulse für Benutzer in den Wartungsmodus."})}),T.jsx(Q.Switch,{checked:s,disabled:U||a||y,onChange:he=>Kt(he.target.checked),ripple:!1,color:"amber"})]}),U&&T.jsx("p",{className:"text-xs text-stormGrey-500",children:"Wartungsmodus wird aktualisiert…"})]})]}),T.jsxs(Q.Card,{children:[T.jsx(Q.CardHeader,{variant:"gradient",color:"gray",className:"mb-5 p-4",children:T.jsx(Q.Typography,{variant:"h6",color:"white",className:"flex items-center justify-between",children:T.jsx("span",{children:"Server & Endpoints"})})}),T.jsxs(Q.CardBody,{className:"flex flex-col gap-4 p-4",children:[_e&&T.jsx("p",{className:"text-sm text-stormGrey-500",children:"Daten werden geladen…"}),!_e&&Ce&&T.jsx(Alert,{color:"red",className:"border border-red-200 bg-red-50 text-red-700",children:Ce}),!_e&&!Ce&&T.jsxs("div",{className:"flex flex-col gap-6",children:[T.jsxs("div",{className:"flex flex-col gap-3",children:[T.jsxs("div",{className:"flex items-center justify-between",children:[T.jsx(Q.Typography,{variant:"small",color:"blue-gray",className:"font-semibold uppercase",children:"Server"}),T.jsxs(Q.Typography,{variant:"small",color:"blue-gray",children:[Pn.length," vorhanden"]})]}),Pn.length===0?T.jsx("p",{className:"text-sm text-stormGrey-500",children:"Es sind derzeit keine Server hinterlegt."}):T.jsx("div",{className:"flex flex-col gap-3",children:Pn.map(he=>{const Ne=Sa.get(he.id)||null,Ae=!!(Ne!=null&&Ne.hasKey),Me=Ne!=null&&Ne.updatedAt?new Date(Ne.updatedAt).toLocaleString("de-DE",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"}):null;return T.jsxs("div",{className:"flex flex-col gap-3 rounded-md border border-blue-gray-100 p-3 md:flex-row md:items-start md:justify-between",children:[T.jsxs("div",{className:"flex flex-col gap-1",children:[T.jsx("p",{className:"text-sm font-medium text-stormGrey-900",children:he.name||"Ohne Namen"}),T.jsx("p",{className:"text-xs text-stormGrey-500 break-all",children:he.url}),T.jsxs("p",{className:"text-xs text-stormGrey-500",children:["API-Key: ",Ae?"vorhanden":"nicht hinterlegt",Me?` (aktualisiert am ${Me})`:""]})]}),T.jsxs("div",{className:"flex w-full flex-col gap-2 md:max-w-xs",children:[T.jsx(Q.Input,{type:"password",label:"Neuer API-Key",value:Ie[he.id]??"",onChange:Ke=>go(he.id,Ke.target.value),disabled:_e||Ze===he.id}),T.jsxs("div",{className:"flex items-center gap-2 justify-end",children:[T.jsx(Q.Button,{variant:"text",color:"red",size:"sm",onClick:()=>Pa(he.id),disabled:_e||et===he.id||qt!==null||Ze===he.id,children:et===he.id?"Lösche…":"Löschen"}),T.jsx(Q.Button,{color:"blue",size:"sm",onClick:()=>Pt(he.id),disabled:_e||Ze===he.id,children:Ze===he.id?"Speichere…":"API-Key speichern"})]})]})]},he.id)})})]}),T.jsxs("div",{className:"flex flex-col gap-3"
Line truncated
|
||||
@@ -0,0 +1 @@
|
||||
*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#eee}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#bdbdbd}input::placeholder,textarea::placeholder{opacity:1;color:#bdbdbd}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 540px){.container{max-width:540px}}@media (min-width: 720px){.container{max-width:720px}}@media (min-width: 960px){.container{max-width:960px}}@media (min-width: 1140px){.container{max-width:1140px}}@media (min-width: 1320px){.container{max-width:1320px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.\!invisible{visibility:hidden!important}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.\!absolute{position:absolute!important}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bLine truncated
|
||||
@@ -0,0 +1,94 @@
|
||||
function Vj(e,t){for(var r=0;r<t.length;r++){const n=t[r];if(typeof n!="string"&&!Array.isArray(n)){for(const o in n)if(o!=="default"&&!(o in e)){const a=Object.getOwnPropertyDescriptor(n,o);a&&Object.defineProperty(e,o,a.get?a:{enumerable:!0,get:()=>n[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))n(o);new MutationObserver(o=>{for(const a of o)if(a.type==="childList")for(const i of a.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&n(i)}).observe(document,{childList:!0,subtree:!0});function r(o){const a={};return o.integrity&&(a.integrity=o.integrity),o.referrerPolicy&&(a.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?a.credentials="include":o.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function n(o){if(o.ep)return;o.ep=!0;const a=r(o);fetch(o.href,a)}})();function Yw(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function ou(e){if(e.__esModule)return e;var t=e.default;if(typeof t=="function"){var r=function n(){return this instanceof n?Reflect.construct(t,arguments,this.constructor):t.apply(this,arguments)};r.prototype=t.prototype}else r={};return Object.defineProperty(r,"__esModule",{value:!0}),Object.keys(e).forEach(function(n){var o=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(r,n,o.get?o:{enumerable:!0,get:function(){return e[n]}})}),r}var Qw={exports:{}},Hd={},Xw={exports:{}},vt={};/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var au=Symbol.for("react.element"),Uj=Symbol.for("react.portal"),Wj=Symbol.for("react.fragment"),$j=Symbol.for("react.strict_mode"),Hj=Symbol.for("react.profiler"),qj=Symbol.for("react.provider"),Gj=Symbol.for("react.context"),Kj=Symbol.for("react.forward_ref"),Yj=Symbol.for("react.suspense"),Qj=Symbol.for("react.memo"),Xj=Symbol.for("react.lazy"),oy=Symbol.iterator;function Zj(e){return e===null||typeof e!="object"?null:(e=oy&&e[oy]||e["@@iterator"],typeof e=="function"?e:null)}var Zw={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Jw=Object.assign,e_={};function yl(e,t,r){this.props=e,this.context=t,this.refs=e_,this.updater=r||Zw}yl.prototype.isReactComponent={};yl.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};yl.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function t_(){}t_.prototype=yl.prototype;function sg(e,t,r){this.props=e,this.context=t,this.refs=e_,this.updater=r||Zw}var ug=sg.prototype=new t_;ug.constructor=sg;Jw(ug,yl.prototype);ug.isPureReactComponent=!0;var ay=Array.isArray,r_=Object.prototype.hasOwnProperty,cg={current:null},n_={key:!0,ref:!0,__self:!0,__source:!0};function o_(e,t,r){var n,o={},a=null,i=null;if(t!=null)for(n in t.ref!==void 0&&(i=t.ref),t.key!==void 0&&(a=""+t.key),t)r_.call(t,n)&&!n_.hasOwnProperty(n)&&(o[n]=t[n]);var s=arguments.length-2;if(s===1)o.children=r;else if(1<s){for(var f=Array(s),g=0;g<s;g++)f[g]=arguments[g+2];o.children=f}if(e&&e.defaultProps)for(n in s=e.defaultProps,s)o[n]===void 0&&(o[n]=s[n]);return{$$typeof:au,type:e,key:a,ref:i,props:o,_owner:cg.current}}function Jj(e,t){return{$$typeof:au,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}function dg(e){return typeof e=="object"&&e!==null&&e.$$typeof===au}function e2(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/[=:]/g,function(r){return t[r]})}var iy=/\/+/g;function kp(e,t){return typeof e=="object"&&e!==null&&e.key!=null?e2(""+e.key):t.toString(36)}function vc(e,t,r,n,o){var a=typeof e;(a==="undefined"||a==="boolean")&&(e=null);var i=!1;if(e===null)i=!0;else switch(a){case"string":case"number":i=!0;break;case"object":switch(e.$$typeof){case au:case Uj:i=!0}}if(i)return i=e,o=o(i),e=n===""?"."+kp(i,0):n,ay(o)?(r="",e!=null&&(r=e.replace(iy,"$&/")+"/"),vc(o,t,r,"",function(g){return g})):o!=null&&(dg(o)&&(o=Jj(o,r+(!o.key||i&&i.key===o.key?"":(""+o.key).replace(iy,"$&/")+"/")+e)),t.push(o)),1;if(i=0,n=n===""?".":n+":",ay(e))for(var s=0;s<e.length;s++){a=e[s];var f=n+kp(a,s);i+=vc(a,t,r,f,o)}else if(f=Zj(e),typeof f=="function")for(e=f.call(e),s=0;!(a=e.next()).done;)a=a.value,f=n+kp(a,s++),i+=vc(a,t,r,f,o);else if(a==="object")throw t=String(e),Error("Objects are not valid as a React child (found: "+(t==="[object Object]"?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.");return i}function Wu(e,t,r){if(e==null)return e;var n=[],o=0;return vc(e,n,"","",function(a){return t.call(r,a,o++)}),n}function t2(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(r){(e._status===0||e._status===-1)&&(e._status=1,e._result=r)},function(r){(e._status===0||e._status===-1)&&(e._status=2,e._result=r)}),e._status===-1&&(e._status=0,e._result=t)}if(e._status===1)return e._result.default;throw e._result}var Vr={current:null},yc={transition:null},r2={ReactCurrentDispatcher:Vr,ReactCurrentBatchConfig:yc,ReactCurrentOwner:cg};vt.Children={map:Wu,forEach:function(e,t,r){Wu(e,function(){t.apply(this,arguments)},r)},count:function(e){var t=0;return Wu(e,function(){t++}),t},toArray:function(e){return Wu(e,function(t){return t})||[]},only:function(e){if(!dg(e))throw Error("React.Children.only expected to receive a single React element child.");return e}};vt.Component=yl;vt.Fragment=Wj;vt.Profiler=Hj;vt.PureComponent=sg;vt.StrictMode=$j;vt.Suspense=Yj;vt.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=r2;vt.cloneElement=function(e,t,r){if(e==null)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+e+".");var n=Jw({},e.props),o=e.key,a=e.ref,i=e._owner;if(t!=null){if(t.ref!==void 0&&(a=t.ref,i=cg.current),t.key!==void 0&&(o=""+t.key),e.type&&e.type.defaultProps)var s=e.type.defaultProps;for(f in t)r_.call(t,f)&&!n_.hasOwnProperty(f)&&(n[f]=t[f]===void 0&&s!==void 0?s[f]:t[f])}var f=arguments.length-2;if(f===1)n.children=r;else if(1<f){s=Array(f);for(var g=0;g<f;g++)s[g]=arguments[g+2];n.children=s}return{$$typeof:au,type:e.type,key:o,ref:a,props:n,_owner:i}};vt.createContext=function(e){return e={$$typeof:Gj,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null},e.Provider={$$typeof:qj,_conLine truncated
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var n2=k,o2=Symbol.for("react.element"),a2=Symbol.for("react.fragment"),i2=Object.prototype.hasOwnProperty,l2=n2.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,s2={key:!0,ref:!0,__self:!0,__source:!0};function a_(e,t,r){var n,o={},a=null,i=null;r!==void 0&&(a=""+r),t.key!==void 0&&(a=""+t.key),t.ref!==void 0&&(i=t.ref);for(n in t)i2.call(t,n)&&!s2.hasOwnProperty(n)&&(o[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps,t)o[n]===void 0&&(o[n]=t[n]);return{$$typeof:o2,type:e,key:a,ref:i,props:o,_owner:l2.current}}Hd.Fragment=a2;Hd.jsx=a_;Hd.jsxs=a_;Qw.exports=Hd;var x=Qw.exports;function i_(e,t){return function(){return e.apply(t,arguments)}}const{toString:u2}=Object.prototype,{getPrototypeOf:pg}=Object,qd=(e=>t=>{const r=u2.call(t);return e[r]||(e[r]=r.slice(8,-1).toLowerCase())})(Object.create(null)),to=e=>(e=e.toLowerCase(),t=>qd(t)===e),Gd=e=>t=>typeof t===e,{isArray:xl}=Array,Ns=Gd("undefined");function c2(e){return e!==null&&!Ns(e)&&e.constructor!==null&&!Ns(e.constructor)&&sn(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}const l_=to("ArrayBuffer");function d2(e){let t;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?t=ArrayBuffer.isView(e):t=e&&e.buffer&&l_(e.buffer),t}const f2=Gd("string"),sn=Gd("function"),s_=Gd("number"),Kd=e=>e!==null&&typeof e=="object",p2=e=>e===!0||e===!1,xc=e=>{if(qd(e)!=="object")return!1;const t=pg(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)},h2=to("Date"),g2=to("File"),m2=to("Blob"),b2=to("FileList"),v2=e=>Kd(e)&&sn(e.pipe),y2=e=>{let t;return e&&(typeof FormData=="function"&&e instanceof FormData||sn(e.append)&&((t=qd(e))==="formdata"||t==="object"&&sn(e.toString)&&e.toString()==="[object FormData]"))},x2=to("URLSearchParams"),[w2,_2,S2,C2]=["ReadableStream","Request","Response","Headers"].map(to),T2=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function iu(e,t,{allOwnKeys:r=!1}={}){if(e===null||typeof e>"u")return;let n,o;if(typeof e!="object"&&(e=[e]),xl(e))for(n=0,o=e.length;n<o;n++)t.call(null,e[n],n,e);else{const a=r?Object.getOwnPropertyNames(e):Object.keys(e),i=a.length;let s;for(n=0;n<i;n++)s=a[n],t.call(null,e[s],s,e)}}function u_(e,t){t=t.toLowerCase();const r=Object.keys(e);let n=r.length,o;for(;n-- >0;)if(o=r[n],t===o.toLowerCase())return o;return null}const Ua=(()=>typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global)(),c_=e=>!Ns(e)&&e!==Ua;function kh(){const{caseless:e}=c_(this)&&this||{},t={},r=(n,o)=>{const a=e&&u_(t,o)||o;xc(t[a])&&xc(n)?t[a]=kh(t[a],n):xc(n)?t[a]=kh({},n):xl(n)?t[a]=n.slice():t[a]=n};for(let n=0,o=arguments.length;n<o;n++)arguments[n]&&iu(arguments[n],r);return t}const O2=(e,t,r,{allOwnKeys:n}={})=>(iu(t,(o,a)=>{r&&sn(o)?e[a]=i_(o,r):e[a]=o},{allOwnKeys:n}),e),P2=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),k2=(e,t,r,n)=>{e.prototype=Object.create(t.prototype,n),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),r&&Object.assign(e.prototype,r)},E2=(e,t,r,n)=>{let o,a,i;const s={};if(t=t||{},e==null)return t;do{for(o=Object.getOwnPropertyNames(e),a=o.length;a-- >0;)i=o[a],(!n||n(i,e,t))&&!s[i]&&(t[i]=e[i],s[i]=!0);e=r!==!1&&pg(e)}while(e&&(!r||r(e,t))&&e!==Object.prototype);return t},N2=(e,t,r)=>{e=String(e),(r===void 0||r>e.length)&&(r=e.length),r-=t.length;const n=e.indexOf(t,r);return n!==-1&&n===r},j2=e=>{if(!e)return null;if(xl(e))return e;let t=e.length;if(!s_(t))return null;const r=new Array(t);for(;t-- >0;)r[t]=e[t];return r},R2=(e=>t=>e&&t instanceof e)(typeof Uint8Array<"u"&&pg(Uint8Array)),A2=(e,t)=>{const n=(e&&e[Symbol.iterator]).call(e);let o;for(;(o=n.next())&&!o.done;){const a=o.value;t.call(e,a[0],a[1])}},M2=(e,t)=>{let r;const n=[];for(;(r=e.exec(t))!==null;)n.push(r);return n},I2=to("HTMLFormElement"),D2=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(r,n,o){return n.toUpperCase()+o}),ly=(({hasOwnProperty:e})=>(t,r)=>e.call(t,r))(Object.prototype),L2=to("RegExp"),d_=(e,t)=>{const r=Object.getOwnPropertyDescriptors(e),n={};iu(r,(o,a)=>{let i;(i=t(o,a,e))!==!1&&(n[a]=i||o)}),Object.defineProperties(e,n)},F2=e=>{d_(e,(t,r)=>{if(sn(e)&&["arguments","caller","callee"].indexOf(r)!==-1)return!1;const n=e[r];if(sn(n)){if(t.enumerable=!1,"writable"in t){t.writable=!1;return}t.set||(t.set=()=>{throw Error("Can not rewrite read-only method '"+r+"'")})}})},z2=(e,t)=>{const r={},n=o=>{o.forEach(a=>{r[a]=!0})};return xl(e)?n(e):n(String(e).split(t)),r},B2=()=>{},V2=(e,t)=>e!=null&&Number.isFinite(e=+e)?e:t,Ep="abcdefghijklmnopqrstuvwxyz",sy="0123456789",f_={DIGIT:sy,ALPHA:Ep,ALPHA_DIGIT:Ep+Ep.toUpperCase()+sy},U2=(e=16,t=f_.ALPHA_DIGIT)=>{let r="";const{length:n}=t;for(;e--;)r+=t[Math.random()*n|0];return r};function W2(e){return!!(e&&sn(e.append)&&e[Symbol.toStringTag]==="FormData"&&e[Symbol.iterator])}const $2=e=>{const t=new Array(10),r=(n,o)=>{if(Kd(n)){if(t.indexOf(n)>=0)return;if(!("toJSON"in n)){t[o]=n;const a=xLine truncated
|
||||
`).forEach(function(i){o=i.indexOf(":"),r=i.substring(0,o).trim().toLowerCase(),n=i.substring(o+1).trim(),!(!r||t[r]&&dR[r])&&(r==="set-cookie"?t[r]?t[r].push(n):t[r]=[n]:t[r]=t[r]?t[r]+", "+n:n)}),t},fy=Symbol("internals");function Ql(e){return e&&String(e).trim().toLowerCase()}function wc(e){return e===!1||e==null?e:_e.isArray(e)?e.map(wc):String(e)}function pR(e){const t=Object.create(null),r=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let n;for(;n=r.exec(e);)t[n[1]]=n[2];return t}const hR=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function Np(e,t,r,n,o){if(_e.isFunction(n))return n.call(this,t,r);if(o&&(t=r),!!_e.isString(t)){if(_e.isString(n))return t.indexOf(n)!==-1;if(_e.isRegExp(n))return n.test(t)}}function gR(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(t,r,n)=>r.toUpperCase()+n)}function mR(e,t){const r=_e.toCamelCase(" "+t);["get","set","has"].forEach(n=>{Object.defineProperty(e,n+r,{value:function(o,a,i){return this[n].call(this,t,o,a,i)},configurable:!0})})}class Qd{constructor(t){t&&this.set(t)}set(t,r,n){const o=this;function a(s,f,g){const b=Ql(f);if(!b)throw new Error("header name must be a non-empty string");const S=_e.findKey(o,b);(!S||o[S]===void 0||g===!0||g===void 0&&o[S]!==!1)&&(o[S||f]=wc(s))}const i=(s,f)=>_e.forEach(s,(g,b)=>a(g,b,f));if(_e.isPlainObject(t)||t instanceof this.constructor)i(t,r);else if(_e.isString(t)&&(t=t.trim())&&!hR(t))i(fR(t),r);else if(_e.isHeaders(t))for(const[s,f]of t.entries())a(f,s,n);else t!=null&&a(r,t,n);return this}get(t,r){if(t=Ql(t),t){const n=_e.findKey(this,t);if(n){const o=this[n];if(!r)return o;if(r===!0)return pR(o);if(_e.isFunction(r))return r.call(this,o,n);if(_e.isRegExp(r))return r.exec(o);throw new TypeError("parser must be boolean|regexp|function")}}}has(t,r){if(t=Ql(t),t){const n=_e.findKey(this,t);return!!(n&&this[n]!==void 0&&(!r||Np(this,this[n],n,r)))}return!1}delete(t,r){const n=this;let o=!1;function a(i){if(i=Ql(i),i){const s=_e.findKey(n,i);s&&(!r||Np(n,n[s],s,r))&&(delete n[s],o=!0)}}return _e.isArray(t)?t.forEach(a):a(t),o}clear(t){const r=Object.keys(this);let n=r.length,o=!1;for(;n--;){const a=r[n];(!t||Np(this,this[a],a,t,!0))&&(delete this[a],o=!0)}return o}normalize(t){const r=this,n={};return _e.forEach(this,(o,a)=>{const i=_e.findKey(n,a);if(i){r[i]=wc(o),delete r[a];return}const s=t?gR(a):String(a).trim();s!==a&&delete r[a],r[s]=wc(o),n[s]=!0}),this}concat(...t){return this.constructor.concat(this,...t)}toJSON(t){const r=Object.create(null);return _e.forEach(this,(n,o)=>{n!=null&&n!==!1&&(r[o]=t&&_e.isArray(n)?n.join(", "):n)}),r}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([t,r])=>t+": "+r).join(`
|
||||
`)}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(t){return t instanceof this?t:new this(t)}static concat(t,...r){const n=new this(t);return r.forEach(o=>n.set(o)),n}static accessor(t){const n=(this[fy]=this[fy]={accessors:{}}).accessors,o=this.prototype;function a(i){const s=Ql(i);n[s]||(mR(o,i),n[s]=!0)}return _e.isArray(t)?t.forEach(a):a(t),this}}Qd.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);_e.reduceDescriptors(Qd.prototype,({value:e},t)=>{let r=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(n){this[r]=n}}});_e.freezeMethods(Qd);const qn=Qd;function jp(e,t){const r=this||bg,n=t||r,o=qn.from(n.headers);let a=n.data;return _e.forEach(e,function(s){a=s.call(r,a,o.normalize(),t?t.status:void 0)}),o.normalize(),a}function w_(e){return!!(e&&e.__CANCEL__)}function wl(e,t,r){ct.call(this,e??"canceled",ct.ERR_CANCELED,t,r),this.name="CanceledError"}_e.inherits(wl,ct,{__CANCEL__:!0});function __(e,t,r){const n=r.config.validateStatus;!r.status||!n||n(r.status)?e(r):t(new ct("Request failed with status code "+r.status,[ct.ERR_BAD_REQUEST,ct.ERR_BAD_RESPONSE][Math.floor(r.status/100)-4],r.config,r.request,r))}function bR(e){const t=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return t&&t[1]||""}function vR(e,t){e=e||10;const r=new Array(e),n=new Array(e);let o=0,a=0,i;return t=t!==void 0?t:1e3,function(f){const g=Date.now(),b=n[a];i||(i=g),r[o]=f,n[o]=g;let S=a,O=0;for(;S!==o;)O+=r[S++],S=S%e;if(o=(o+1)%e,o===a&&(a=(a+1)%e),g-i<t)return;const T=b&&g-b;return T?Math.round(O*1e3/T):void 0}}function yR(e,t){let r=0,n=1e3/t,o,a;const i=(g,b=Date.now())=>{r=b,o=null,a&&(clearTimeout(a),a=null),e.apply(null,g)};return[(...g)=>{const b=Date.now(),S=b-r;S>=n?i(g,b):(o=g,a||(a=setTimeout(()=>{a=null,i(o)},n-S)))},()=>o&&i(o)]}const Yc=(e,t,r=3)=>{let n=0;const o=vR(50,250);return yR(a=>{const i=a.loaded,s=a.lengthComputable?a.total:void 0,f=i-n,g=o(f),b=i<=s;n=i;const S={loaded:i,total:s,progress:s?i/s:void 0,bytes:f,rate:g||void 0,estimated:g&&s&&b?(s-i)/g:void 0,event:a,lengthComputable:s!=null,[t?"download":"upload"]:!0};e(S)},r)},py=(e,t)=>{const r=e!=null;return[n=>t[0]({lengthComputable:r,total:e,loaded:n}),t[1]]},hy=e=>(...t)=>_e.asap(()=>e(...t)),xR=un.hasStandardBrowserEnv?function(){const t=un.navigator&&/(msie|trident)/i.test(un.navigator.userAgent),r=document.createElement("a");let n;function o(a){let i=a;return t&&(r.setAttribute("href",i),i=r.href),r.setAttribute("href",i),{href:r.href,protocol:r.protocol?r.protocol.replace(/:$/,""):"",host:r.host,search:r.search?r.search.replace(/^\?/,""):"",hash:r.hash?r.hash.replace(/^#/,""):"",hostname:r.hostname,port:r.port,pathname:r.pathname.charAt(0)==="/"?r.pathname:"/"+r.pathname}}return n=o(window.location.href),function(i){const s=_e.isString(i)?o(i):i;return s.protocol===n.protocol&&s.host===n.host}}():function(){return function(){return!0}}(),wR=un.hasStandardBrowserEnv?{write(e,t,r,n,o,a){const i=[e+"="+encodeURIComponent(t)];_e.isNumber(r)&&i.push("expires="+new Date(r).toGMTString()),_e.isString(n)&&i.push("path="+n),_e.isString(o)&&i.push("domain="+o),a===!0&&i.push("secure"),document.cookie=i.join("; ")},read(e){const t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove(e){this.write(e,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function _R(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}function SR(e,t){return t?e.replace(/\/?\/$/,"")+"/"+t.replace(/^\/+/,""):e}function S_(e,t){return e&&!_R(t)?SR(e,t):t}const gy=e=>e instanceof qn?{...e}:e;function Xa(e,t){t=t||{};const r={};function n(g,b,S){return _e.isPlainObject(g)&&_e.isPlainObject(b)?_e.merge.call({caseless:S},g,b):_e.isPlainObject(b)?_e.merge({},b):_e.isArray(b)?b.slice():b}function o(g,b,S){if(_e.isUndefined(b)){if(!_e.isUndefined(g))return n(void 0,g,S)}else return n(g,b,S)}function a(g,b){if(!_e.isUndefined(b))return n(void 0,b)}function i(g,b){if(_e.isUndefined(b)){if(!_e.isUndefined(g))return n(void 0,g)}else return n(void 0,b)}function s(g,b,S){if(S in t)return n(g,b);if(S in e)return n(void 0,g)}const f={url:a,method:a,data:a,baseURL:i,transformRequest:i,transformResponse:i,paramsSerializer:i,timeout:i,timeoutMessage:i,withCredentials:i,withXSRFToken:i,adapter:i,responseType:i,xsrfCookieName:i,xsrfHeaderName:i,onUploadProgress:i,onDownloadProgress:i,decompress:i,maxContentLength:i,maxBodyLength:i,beforeRedirect:i,transport:i,httpAgent:i,httpsAgent:i,cancelToken:i,socketPath:i,responseEncoding:i,validateStatus:s,headers:(g,b)=>o(gy(g),gy(b),!0)};return _e.forEach(Object.keys(Object.assign({},e,t)),function(b){const S=f[b]||o,O=S(e[b],t[b],b);_e.isUndefined(O)&&S!==s||(r[b]=O)}),r}const C_=e=>{const t=Xa({},e);let{data:r,withXSRFToken:n,xsrfHeaderName:o,xsrfCookieName:a,headers:i,auth:s}=t;t.headers=i=qn.from(i),t.url=v_(S_(t.baseURL,t.url),e.params,e.paramsSerializer),s&&i.set("Authorization","Basic "+btoa((s.username||"")+":"+(s.password?unescape(enco
Line truncated
|
||||
`+a.map(vy).join(`
|
||||
`):" "+vy(a[0]):"as no adapter specified";throw new ct("There is no suitable adapter to dispatch the request "+i,"ERR_NOT_SUPPORT")}return n},adapters:Ah};function Rp(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new wl(null,e)}function yy(e){return Rp(e),e.headers=qn.from(e.headers),e.data=jp.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),P_.getAdapter(e.adapter||bg.adapter)(e).then(function(n){return Rp(e),n.data=jp.call(e,e.transformResponse,n),n.headers=qn.from(n.headers),n},function(n){return w_(n)||(Rp(e),n&&n.response&&(n.response.data=jp.call(e,e.transformResponse,n.response),n.response.headers=qn.from(n.response.headers))),Promise.reject(n)})}const k_="1.7.5",vg={};["object","boolean","number","function","string","symbol"].forEach((e,t)=>{vg[e]=function(n){return typeof n===e||"a"+(t<1?"n ":" ")+e}});const xy={};vg.transitional=function(t,r,n){function o(a,i){return"[Axios v"+k_+"] Transitional option '"+a+"'"+i+(n?". "+n:"")}return(a,i,s)=>{if(t===!1)throw new ct(o(i," has been removed"+(r?" in "+r:"")),ct.ERR_DEPRECATED);return r&&!xy[i]&&(xy[i]=!0,console.warn(o(i," has been deprecated since v"+r+" and will be removed in the near future"))),t?t(a,i,s):!0}};function IR(e,t,r){if(typeof e!="object")throw new ct("options must be an object",ct.ERR_BAD_OPTION_VALUE);const n=Object.keys(e);let o=n.length;for(;o-- >0;){const a=n[o],i=t[a];if(i){const s=e[a],f=s===void 0||i(s,a,e);if(f!==!0)throw new ct("option "+a+" must be "+f,ct.ERR_BAD_OPTION_VALUE);continue}if(r!==!0)throw new ct("Unknown option "+a,ct.ERR_BAD_OPTION)}}const Mh={assertOptions:IR,validators:vg},Jo=Mh.validators;class Xc{constructor(t){this.defaults=t,this.interceptors={request:new dy,response:new dy}}async request(t,r){try{return await this._request(t,r)}catch(n){if(n instanceof Error){let o;Error.captureStackTrace?Error.captureStackTrace(o={}):o=new Error;const a=o.stack?o.stack.replace(/^.+\n/,""):"";try{n.stack?a&&!String(n.stack).endsWith(a.replace(/^.+\n.+\n/,""))&&(n.stack+=`
|
||||
`+a):n.stack=a}catch{}}throw n}}_request(t,r){typeof t=="string"?(r=r||{},r.url=t):r=t||{},r=Xa(this.defaults,r);const{transitional:n,paramsSerializer:o,headers:a}=r;n!==void 0&&Mh.assertOptions(n,{silentJSONParsing:Jo.transitional(Jo.boolean),forcedJSONParsing:Jo.transitional(Jo.boolean),clarifyTimeoutError:Jo.transitional(Jo.boolean)},!1),o!=null&&(_e.isFunction(o)?r.paramsSerializer={serialize:o}:Mh.assertOptions(o,{encode:Jo.function,serialize:Jo.function},!0)),r.method=(r.method||this.defaults.method||"get").toLowerCase();let i=a&&_e.merge(a.common,a[r.method]);a&&_e.forEach(["delete","get","head","post","put","patch","common"],C=>{delete a[C]}),r.headers=qn.concat(i,a);const s=[];let f=!0;this.interceptors.request.forEach(function(w){typeof w.runWhen=="function"&&w.runWhen(r)===!1||(f=f&&w.synchronous,s.unshift(w.fulfilled,w.rejected))});const g=[];this.interceptors.response.forEach(function(w){g.push(w.fulfilled,w.rejected)});let b,S=0,O;if(!f){const C=[yy.bind(this),void 0];for(C.unshift.apply(C,s),C.push.apply(C,g),O=C.length,b=Promise.resolve(r);S<O;)b=b.then(C[S++],C[S++]);return b}O=s.length;let T=r;for(S=0;S<O;){const C=s[S++],w=s[S++];try{T=C(T)}catch(m){w.call(this,m);break}}try{b=yy.call(this,T)}catch(C){return Promise.reject(C)}for(S=0,O=g.length;S<O;)b=b.then(g[S++],g[S++]);return b}getUri(t){t=Xa(this.defaults,t);const r=S_(t.baseURL,t.url);return v_(r,t.params,t.paramsSerializer)}}_e.forEach(["delete","get","head","options"],function(t){Xc.prototype[t]=function(r,n){return this.request(Xa(n||{},{method:t,url:r,data:(n||{}).data}))}});_e.forEach(["post","put","patch"],function(t){function r(n){return function(a,i,s){return this.request(Xa(s||{},{method:t,headers:n?{"Content-Type":"multipart/form-data"}:{},url:a,data:i}))}}Xc.prototype[t]=r(),Xc.prototype[t+"Form"]=r(!0)});const _c=Xc;class yg{constructor(t){if(typeof t!="function")throw new TypeError("executor must be a function.");let r;this.promise=new Promise(function(a){r=a});const n=this;this.promise.then(o=>{if(!n._listeners)return;let a=n._listeners.length;for(;a-- >0;)n._listeners[a](o);n._listeners=null}),this.promise.then=o=>{let a;const i=new Promise(s=>{n.subscribe(s),a=s}).then(o);return i.cancel=function(){n.unsubscribe(a)},i},t(function(a,i,s){n.reason||(n.reason=new wl(a,i,s),r(n.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;const r=this._listeners.indexOf(t);r!==-1&&this._listeners.splice(r,1)}static source(){let t;return{token:new yg(function(o){t=o}),cancel:t}}}const DR=yg;function LR(e){return function(r){return e.apply(null,r)}}function FR(e){return _e.isObject(e)&&e.isAxiosError===!0}const Ih={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(Ih).forEach(([e,t])=>{Ih[t]=e});const zR=Ih;function E_(e){const t=new _c(e),r=i_(_c.prototype.request,t);return _e.extend(r,_c.prototype,t,{allOwnKeys:!0}),_e.extend(r,t,null,{allOwnKeys:!0}),r.create=function(o){return E_(Xa(e,o))},r}const lr=E_(bg);lr.Axios=_c;lr.CanceledError=wl;lr.CancelToken=DR;lr.isCancel=w_;lr.VERSION=k_;lr.toFormData=Yd;lr.AxiosError=ct;lr.Cancel=lr.CanceledError;lr.all=function(t){return Promise.all(t)};lr.spread=LR;lr.isAxiosError=FR;lr.mergeConfig=Xa;lr.AxiosHeaders=qn;lr.formToJSON=e=>x_(_e.isHTMLForm(e)?new FormData(e):e);lr.getAdapter=P_.getAdapter;lr.HttpStatusCode=zR;lr.default=lr;const nt=lr;var Dh={},N_={exports:{}},hn={},j_={exports:{}},R_={};/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/(function(e){function t(W,z){var $=W.length;W.push(z);e:for(;0<$;){var q=$-1>>>1,Z=W[q];if(0<o(Z,z))W[q]=z,W[$]=Z,$=q;else break e}}function r(W){return W.length===0?null:W[0]}function n(W){if(W.length===0)return null;var z=W[0],$=W.pop();if($!==z){W[0]=$;e:for(var q=0,Z=W.length,ie=Z>>>1;q<ie;){var me=2*(q+1)-1,ue=W[me],te=me+1,Se=W[te];if(0>o(ue,$))te<Z&&0>o(Se,ue)?(W[q]=Se,W[te]=$,q=te):(W[q]=ue,W[me]=$,q=me);else if(te<Z&&0>o(Se,$))W[q]=Se,W[te]=$,q=te;else break e}}return z}function o(W,z){var $=W.sortIndex-z.sortIndex;return $!==0?$:W.id-z.id}if(typeof performance=="object"&&typeof performance.now=="function"){var a=performance;e.unstable_now=function(){return a.now()}}else{var i=Date,s=i.now();e.unstable_now=function(){return i.now()-s}}var f=[],g=[],b=1,S=null,O=3,T=!1,C=!1,w=!1,m=typeof setTimeout=="function"?setTimeout:null,d=typeof clearTimeout=="function"?clearTimeout:null,l=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function c(W){for(var z=r(g);z!==null;){if(z.callback===null)n(g);else if(z.startTime<=W)n(g),z.sortIndex=z.expirationTime,t(f,z);else break;z=r(g)}}function u(W){if(w=!1,c(W),!C)if(r(f)!==null)C=!0,H(h);else{var z=r(g);z!==null&&Q(u,z.startTime-W)}}function h(W,z){C=!1,w&&(w=!1,d(E),E=-1),T=!0;var $=O;try{for(c(z),S=r(f);S!==null&&(!(S.expirationTime>z)||W&&!R());){var q=S.callback;if(typeof q=="function"){S.callback=null,O=S.priorityLevel;var Z=q(S.expirationTime<=z);z=e.unstable_now(),typeof Z=="function"?S.callback=Z:S===r(f)&&n(f),c(z)}else n(f);S=r(f)}if(S!==null)var ie=!0;else{var me=r(g);me!==null&&Q(u,me.startTime-z),ie=!1}return ie}finally{S=null,O=$,T=!1}}var v=!1,y=null,E=-1,P=5,N=-1;function R(){return!(e.unstable_now()-N<P)}function M(){if(y!==null){var W=e.unstable_now();N=W;var z=!0;try{z=y(!0,W)}finally{z?L():(v=!1,y=null)}}else v=!1}var L;if(typeof l=="function")L=function(){l(M)};else if(typeof MessageChannel<"u"){var D=new MessageChannel,B=D.port2;D.port1.onmessage=M,L=function(){B.postMessage(null)}}else L=function(){m(M,0)};function H(W){y=W,v||(v=!0,L())}function Q(W,z){E=m(function(){W(e.unstable_now())},z)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(W){W.callback=null},e.unstable_continueExecution=function(){C||T||(C=!0,H(h))},e.unstable_forceFrameRate=function(W){0>W||125<W?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):P=0<W?Math.floor(1e3/W):5},e.unstable_getCurrentPriorityLevel=function(){return O},e.unstable_getFirstCallbackNode=function(){return r(f)},e.unstable_next=function(W){switch(O){case 1:case 2:case 3:var z=3;break;default:z=O}var $=O;O=z;try{return W()}finally{O=$}},e.unstable_pauseExecution=function(){},e.unstable_requestPaint=function(){},e.unstable_runWithPriority=function(W,z){switch(W){case 1:case 2:case 3:case 4:case 5:break;default:W=3}var $=O;O=W;try{return z()}finally{O=$}},e.unstable_scheduleCallback=function(W,z,$){var q=e.unstable_now();switch(typeof $=="object"&&$!==null?($=$.delay,$=typeof $=="number"&&0<$?q+$:q):$=q,W){case 1:var Z=-1;break;case 2:Z=250;break;case 5:Z=1073741823;break;case 4:Z=1e4;break;default:Z=5e3}return Z=$+Z,W={id:b++,callback:z,priorityLevel:W,startTime:$,expirationTime:Z,sortIndex:-1},$>q?(W.sortIndex=$,t(g,W),r(f)===null&&W===r(g)&&(w?(d(E),E=-1):w=!0,Q(u,$-q))):(W.sortIndex=Z,t(f,W),C||T||(C=!0,H(h))),W},e.unstable_shouldYield=R,e.unstable_wrapCallback=function(W){var z=O;return function(){var $=O;O=z;try{return W.apply(this,arguments)}finally{O=$}}}})(R_);j_.exports=R_;var BR=j_.exports;/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var A_=k,fn=BR;function Re(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,r=1;r<arguments.length;r++)t+="&args[]="+encodeURIComponent(arguments[r]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var M_=new Set,js={};function li(e,t){nl(e,t),nl(e+"Capture",t)}function nl(e,t){for(js[e]=t,e=0;e<t.length;e++)M_.add(t[e])}var Io=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Lh=Object.prototype.hasOwnProperty,VR=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,wy={},_y={};function UR(e){return Lh.call(_y,e)?!0:Lh.call(wy,e)?!1:VR.test(e)?_y[e]=!0:(wy[e]=!0,!1)}function WR(e,t,r,n){if(r!==null&&r.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return n?!1:r!==null?!r.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function $R(e,t,r,n){if(t===null||typeof t>"u"||WR(e,t,r,n))return!0;if(n)return!1;if(r!==null)switch(r.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function Ur(e,t,r,n,o,a,i){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=n,this.attributeNamespace=o,this.mustUseProperty=r,this.propertyName=e,this.type=t,this.sanitizeURL=a,this.removeEmptyString=i}var _r={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){_r[e]=new Ur(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];_r[t]=new Ur(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){_r[e]=new Ur(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){_r[e]=new Ur(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){_r[e]=new Ur(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){_r[e]=new Ur(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){_r[e]=new Ur(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){_r[e]=new Ur(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){_r[e]=new Ur(e,5,!1,e.toLowerCase(),null,!1,!1)});var xg=/[\-:]([a-z])/g;function wg(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(xg,wg);_r[t]=new Ur(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(xg,wg);_r[t]=new Ur(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(xg,wg);_r[t]=new Ur(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){_r[e]=new Ur(e,1,!1,e.toLowerCase(),null,!1,!1)});_r.xlinkHref=new Ur("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){_r[e]=new Ur(e,1,!1,e.toLowerCase(),null,!0,!0)});function _g(e,t,r,n){var o=_r.hasOwnProperty(t)?_r[t]:null;(o!==null?o.type!==0:n||!(2<t.length)||t[0]!=="o"&&t[0]!==
Line truncated
|
||||
`+Ap+e}var Mp=!1;function Ip(e,t){if(!e||Mp)return"";Mp=!0;var r=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(t)if(t=function(){throw Error()},Object.defineProperty(t.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(t,[])}catch(g){var n=g}Reflect.construct(e,[],t)}else{try{t.call()}catch(g){n=g}e.call(t.prototype)}else{try{throw Error()}catch(g){n=g}e()}}catch(g){if(g&&n&&typeof g.stack=="string"){for(var o=g.stack.split(`
|
||||
`),a=n.stack.split(`
|
||||
`),i=o.length-1,s=a.length-1;1<=i&&0<=s&&o[i]!==a[s];)s--;for(;1<=i&&0<=s;i--,s--)if(o[i]!==a[s]){if(i!==1||s!==1)do if(i--,s--,0>s||o[i]!==a[s]){var f=`
|
||||
`+o[i].replace(" at new "," at ");return e.displayName&&f.includes("<anonymous>")&&(f=f.replace("<anonymous>",e.displayName)),f}while(1<=i&&0<=s);break}}}finally{Mp=!1,Error.prepareStackTrace=r}return(e=e?e.displayName||e.name:"")?us(e):""}function HR(e){switch(e.tag){case 5:return us(e.type);case 16:return us("Lazy");case 13:return us("Suspense");case 19:return us("SuspenseList");case 0:case 2:case 15:return e=Ip(e.type,!1),e;case 11:return e=Ip(e.type.render,!1),e;case 1:return e=Ip(e.type,!0),e;default:return""}}function Vh(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Ai:return"Fragment";case Ri:return"Portal";case Fh:return"Profiler";case Sg:return"StrictMode";case zh:return"Suspense";case Bh:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case D_:return(e.displayName||"Context")+".Consumer";case I_:return(e._context.displayName||"Context")+".Provider";case Cg:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Tg:return t=e.displayName||null,t!==null?t:Vh(e.type)||"Memo";case aa:t=e._payload,e=e._init;try{return Vh(e(t))}catch{}}return null}function qR(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Vh(t);case 8:return t===Sg?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Ca(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function F_(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function GR(e){var t=F_(e)?"checked":"value",r=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),n=""+e[t];if(!e.hasOwnProperty(t)&&typeof r<"u"&&typeof r.get=="function"&&typeof r.set=="function"){var o=r.get,a=r.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(i){n=""+i,a.call(this,i)}}),Object.defineProperty(e,t,{enumerable:r.enumerable}),{getValue:function(){return n},setValue:function(i){n=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Hu(e){e._valueTracker||(e._valueTracker=GR(e))}function z_(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var r=t.getValue(),n="";return e&&(n=F_(e)?e.checked?"true":"false":e.value),e=n,e!==r?(t.setValue(e),!0):!1}function Zc(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Uh(e,t){var r=t.checked;return Zt({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:r??e._wrapperState.initialChecked})}function Cy(e,t){var r=t.defaultValue==null?"":t.defaultValue,n=t.checked!=null?t.checked:t.defaultChecked;r=Ca(t.value!=null?t.value:r),e._wrapperState={initialChecked:n,initialValue:r,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function B_(e,t){t=t.checked,t!=null&&_g(e,"checked",t,!1)}function Wh(e,t){B_(e,t);var r=Ca(t.value),n=t.type;if(r!=null)n==="number"?(r===0&&e.value===""||e.value!=r)&&(e.value=""+r):e.value!==""+r&&(e.value=""+r);else if(n==="submit"||n==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?$h(e,t.type,r):t.hasOwnProperty("defaultValue")&&$h(e,t.type,Ca(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Ty(e,t,r){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var n=t.type;if(!(n!=="submit"&&n!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,r||t===e.value||(e.value=t),e.defaultValue=t}r=e.name,r!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,r!==""&&(e.name=r)}function $h(e,t,r){(t!=="number"||Zc(e.ownerDocument)!==e)&&(r==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+r&&(e.defaultValue=""+r))}var cs=Array.isArray;function qi(e,t,r,n){if(e=e.options,t){t={};for(var o=0;o<r.length;o++)t["$"+r[o]]=!0;for(r=0;r<e.length;r++)o=t.hasOwnProperty("$"+e[r].value),e[r].selected!==o&&(e[r].selected=o),o&&n&&(e[r].defaultSelected=!0)}else{for(r=""+Ca(r),t=null,o=0;o<e.length;o++){if(e[o].value===r){e[o].selected=!0,n&&(e[o].defaultSelected=!0);return}t!==null||e[o].disabled||(t=e[o])}t!==null&&(t.selected=!0)}}function Hh(e,t){if(t.dan
Line truncated
|
||||
`).replace(nM,"")}function Ju(e,t,r){if(t=Ky(t),Ky(e)!==t&&r)throw Error(Re(425))}function id(){}var o0=null,a0=null;function i0(e,t){return e==="textarea"||e==="noscript"||typeof t.children=="string"||typeof t.children=="number"||typeof t.dangerouslySetInnerHTML=="object"&&t.dangerouslySetInnerHTML!==null&&t.dangerouslySetInnerHTML.__html!=null}var l0=typeof setTimeout=="function"?setTimeout:void 0,oM=typeof clearTimeout=="function"?clearTimeout:void 0,Yy=typeof Promise=="function"?Promise:void 0,aM=typeof queueMicrotask=="function"?queueMicrotask:typeof Yy<"u"?function(e){return Yy.resolve(null).then(e).catch(iM)}:l0;function iM(e){setTimeout(function(){throw e})}function Kp(e,t){var r=t,n=0;do{var o=r.nextSibling;if(e.removeChild(r),o&&o.nodeType===8)if(r=o.data,r==="/$"){if(n===0){e.removeChild(o),Ds(t);return}n--}else r!=="$"&&r!=="$?"&&r!=="$!"||n++;r=o}while(r);Ds(t)}function ma(e){for(;e!=null;e=e.nextSibling){var t=e.nodeType;if(t===1||t===3)break;if(t===8){if(t=e.data,t==="$"||t==="$!"||t==="$?")break;if(t==="/$")return null}}return e}function Qy(e){e=e.previousSibling;for(var t=0;e;){if(e.nodeType===8){var r=e.data;if(r==="$"||r==="$!"||r==="$?"){if(t===0)return e;t--}else r==="/$"&&t++}e=e.previousSibling}return null}var Sl=Math.random().toString(36).slice(2),co="__reactFiber$"+Sl,Vs="__reactProps$"+Sl,Do="__reactContainer$"+Sl,s0="__reactEvents$"+Sl,lM="__reactListeners$"+Sl,sM="__reactHandles$"+Sl;function Wa(e){var t=e[co];if(t)return t;for(var r=e.parentNode;r;){if(t=r[Do]||r[co]){if(r=t.alternate,t.child!==null||r!==null&&r.child!==null)for(e=Qy(e);e!==null;){if(r=e[co])return r;e=Qy(e)}return t}e=r,r=e.parentNode}return null}function uu(e){return e=e[co]||e[Do],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function Li(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(Re(33))}function rf(e){return e[Vs]||null}var u0=[],Fi=-1;function ka(e){return{current:e}}function Bt(e){0>Fi||(e.current=u0[Fi],u0[Fi]=null,Fi--)}function Lt(e,t){Fi++,u0[Fi]=e.current,e.current=t}var Ta={},Ar=ka(Ta),Zr=ka(!1),Za=Ta;function ol(e,t){var r=e.type.contextTypes;if(!r)return Ta;var n=e.stateNode;if(n&&n.__reactInternalMemoizedUnmaskedChildContext===t)return n.__reactInternalMemoizedMaskedChildContext;var o={},a;for(a in r)o[a]=t[a];return n&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function Jr(e){return e=e.childContextTypes,e!=null}function ld(){Bt(Zr),Bt(Ar)}function Xy(e,t,r){if(Ar.current!==Ta)throw Error(Re(168));Lt(Ar,t),Lt(Zr,r)}function k5(e,t,r){var n=e.stateNode;if(t=t.childContextTypes,typeof n.getChildContext!="function")return r;n=n.getChildContext();for(var o in n)if(!(o in t))throw Error(Re(108,qR(e)||"Unknown",o));return Zt({},r,n)}function sd(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Ta,Za=Ar.current,Lt(Ar,e),Lt(Zr,Zr.current),!0}function Zy(e,t,r){var n=e.stateNode;if(!n)throw Error(Re(169));r?(e=k5(e,t,Za),n.__reactInternalMemoizedMergedChildContext=e,Bt(Zr),Bt(Ar),Lt(Ar,e)):Bt(Zr),Lt(Zr,r)}var Eo=null,nf=!1,Yp=!1;function E5(e){Eo===null?Eo=[e]:Eo.push(e)}function uM(e){nf=!0,E5(e)}function Ea(){if(!Yp&&Eo!==null){Yp=!0;var e=0,t=kt;try{var r=Eo;for(kt=1;e<r.length;e++){var n=r[e];do n=n(!0);while(n!==null)}Eo=null,nf=!1}catch(o){throw Eo!==null&&(Eo=Eo.slice(e+1)),e5(Pg,Ea),o}finally{kt=t,Yp=!1}}return null}var zi=[],Bi=0,ud=null,cd=0,Tn=[],On=0,Ja=null,No=1,jo="";function za(e,t){zi[Bi++]=cd,zi[Bi++]=ud,ud=e,cd=t}function N5(e,t,r){Tn[On++]=No,Tn[On++]=jo,Tn[On++]=Ja,Ja=e;var n=No;e=jo;var o=32-Gn(n)-1;n&=~(1<<o),r+=1;var a=32-Gn(t)+o;if(30<a){var i=o-o%5;a=(n&(1<<i)-1).toString(32),n>>=i,o-=i,No=1<<32-Gn(t)+o|r<<o|n,jo=a+e}else No=1<<a|r<<o|n,jo=e}function Dg(e){e.return!==null&&(za(e,1),N5(e,1,0))}function Lg(e){for(;e===ud;)ud=zi[--Bi],zi[Bi]=null,cd=zi[--Bi],zi[Bi]=null;for(;e===Ja;)Ja=Tn[--On],Tn[On]=null,jo=Tn[--On],Tn[On]=null,No=Tn[--On],Tn[On]=null}var cn=null,an=null,Ht=!1,Wn=null;function j5(e,t){var r=kn(5,null,null,0);r.elementType="DELETED",r.stateNode=t,r.return=e,t=e.deletions,t===null?(e.deletions=[r],e.flags|=16):t.push(r)}function Jy(e,t){switch(e.tag){case 5:var r=e.type;return t=t.nodeType!==1||r.toLowerCase()!==t.nodeName.toLowerCase()?null:t,t!==null?(e.stateNode=t,cn=e,an=ma(t.firstChild),!0):!1;case 6:return t=e.pendingProps===""||t.nodeType!==3?null:t,t!==null?(e.stateNode=t,cn=e,an=null,!0):!1;case 13:return t=t.nodeType!==8?null:t,t!==null?(r=Ja!==null?{id:No,overflow:jo}:null,e.memoizedState={dehydrated:t,treeContext:r,retryLane:1073741824},r=kn(18,null,null,0),r.stateNode=t,r.return=e,e.child=r,cn=e,an=null,!0):!1;default:return!1}}function c0(e){return(e.mode&1)!==0&&(e.flags&128)===0}function d0(e){if(Ht){var t=an;if(t){var r=t;if(!Jy(e,t)){if(c0(e))throw Error(Re(418));t=ma(r.nextSibling);var n=cn;t&&Jy(e,t)?j5(n,r):(e.flags=e.flags&-4097|2,Ht=!1,cn=e)}}else{if(c0(e))throw Error(Re(418));e.flags=e.flags&-4097|2,Ht=!1,cn=e}}}function ex(e){
Line truncated
|
||||
Error generating stack: `+a.message+`
|
||||
`+a.stack}return{value:e,source:t,stack:o,digest:null}}function eh(e,t,r){return{value:e,source:null,stack:r??null,digest:t??null}}function g0(e,t){try{console.error(t.value)}catch(r){setTimeout(function(){throw r})}}var vM=typeof WeakMap=="function"?WeakMap:Map;function a1(e,t,r){r=Ro(-1,r),r.tag=3,r.payload={element:null};var n=t.value;return r.callback=function(){vd||(vd=!0,T0=n),g0(e,t)},r}function i1(e,t,r){r=Ro(-1,r),r.tag=3;var n=e.type.getDerivedStateFromError;if(typeof n=="function"){var o=t.value;r.payload=function(){return n(o)},r.callback=function(){g0(e,t)}}var a=e.stateNode;return a!==null&&typeof a.componentDidCatch=="function"&&(r.callback=function(){g0(e,t),typeof n!="function"&&(va===null?va=new Set([this]):va.add(this));var i=t.stack;this.componentDidCatch(t.value,{componentStack:i!==null?i:""})}),r}function sx(e,t,r){var n=e.pingCache;if(n===null){n=e.pingCache=new vM;var o=new Set;n.set(t,o)}else o=n.get(t),o===void 0&&(o=new Set,n.set(t,o));o.has(r)||(o.add(r),e=RM.bind(null,e,t,r),t.then(e,e))}function ux(e){do{var t;if((t=e.tag===13)&&(t=e.memoizedState,t=t!==null?t.dehydrated!==null:!0),t)return e;e=e.return}while(e!==null);return null}function cx(e,t,r,n,o){return e.mode&1?(e.flags|=65536,e.lanes=o,e):(e===t?e.flags|=65536:(e.flags|=128,r.flags|=131072,r.flags&=-52805,r.tag===1&&(r.alternate===null?r.tag=17:(t=Ro(-1,1),t.tag=2,ba(r,t,1))),r.lanes|=1),e)}var yM=Vo.ReactCurrentOwner,Qr=!1;function zr(e,t,r,n){t.child=e===null?F5(t,null,r,n):il(t,e.child,r,n)}function dx(e,t,r,n,o){r=r.render;var a=t.ref;return Qi(t,o),n=Kg(e,t,r,n,a,o),r=Yg(),e!==null&&!Qr?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,Fo(e,t,o)):(Ht&&r&&Dg(t),t.flags|=1,zr(e,t,n,o),t.child)}function fx(e,t,r,n,o){if(e===null){var a=r.type;return typeof a=="function"&&!am(a)&&a.defaultProps===void 0&&r.compare===null&&r.defaultProps===void 0?(t.tag=15,t.type=a,l1(e,t,a,n,o)):(e=Rc(r.type,null,n,t,t.mode,o),e.ref=t.ref,e.return=t,t.child=e)}if(a=e.child,!(e.lanes&o)){var i=a.memoizedProps;if(r=r.compare,r=r!==null?r:Fs,r(i,n)&&e.ref===t.ref)return Fo(e,t,o)}return t.flags|=1,e=xa(a,n),e.ref=t.ref,e.return=t,t.child=e}function l1(e,t,r,n,o){if(e!==null){var a=e.memoizedProps;if(Fs(a,n)&&e.ref===t.ref)if(Qr=!1,t.pendingProps=n=a,(e.lanes&o)!==0)e.flags&131072&&(Qr=!0);else return t.lanes=e.lanes,Fo(e,t,o)}return m0(e,t,r,n,o)}function s1(e,t,r){var n=t.pendingProps,o=n.children,a=e!==null?e.memoizedState:null;if(n.mode==="hidden")if(!(t.mode&1))t.memoizedState={baseLanes:0,cachePool:null,transitions:null},Lt(Wi,on),on|=r;else{if(!(r&1073741824))return e=a!==null?a.baseLanes|r:r,t.lanes=t.childLanes=1073741824,t.memoizedState={baseLanes:e,cachePool:null,transitions:null},t.updateQueue=null,Lt(Wi,on),on|=e,null;t.memoizedState={baseLanes:0,cachePool:null,transitions:null},n=a!==null?a.baseLanes:r,Lt(Wi,on),on|=n}else a!==null?(n=a.baseLanes|r,t.memoizedState=null):n=r,Lt(Wi,on),on|=n;return zr(e,t,o,r),t.child}function u1(e,t){var r=t.ref;(e===null&&r!==null||e!==null&&e.ref!==r)&&(t.flags|=512,t.flags|=2097152)}function m0(e,t,r,n,o){var a=Jr(r)?Za:Ar.current;return a=ol(t,a),Qi(t,o),r=Kg(e,t,r,n,a,o),n=Yg(),e!==null&&!Qr?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,Fo(e,t,o)):(Ht&&n&&Dg(t),t.flags|=1,zr(e,t,r,o),t.child)}function px(e,t,r,n,o){if(Jr(r)){var a=!0;sd(t)}else a=!1;if(Qi(t,o),t.stateNode===null)Ec(e,t),D5(t,r,n),h0(t,r,n,o),n=!0;else if(e===null){var i=t.stateNode,s=t.memoizedProps;i.props=s;var f=i.context,g=r.contextType;typeof g=="object"&&g!==null?g=Rn(g):(g=Jr(r)?Za:Ar.current,g=ol(t,g));var b=r.getDerivedStateFromProps,S=typeof b=="function"||typeof i.getSnapshotBeforeUpdate=="function";S||typeof i.UNSAFE_componentWillReceiveProps!="function"&&typeof i.componentWillReceiveProps!="function"||(s!==n||f!==g)&&ox(t,i,n,g),ia=!1;var O=t.memoizedState;i.state=O,pd(t,n,i,o),f=t.memoizedState,s!==n||O!==f||Zr.current||ia?(typeof b=="function"&&(p0(t,r,b,n),f=t.memoizedState),(s=ia||nx(t,r,s,n,O,f,g))?(S||typeof i.UNSAFE_componentWillMount!="function"&&typeof i.componentWillMount!="function"||(typeof i.componentWillMount=="function"&&i.componentWillMount(),typeof i.UNSAFE_componentWillMount=="function"&&i.UNSAFE_componentWillMount()),typeof i.componentDidMount=="function"&&(t.flags|=4194308)):(typeof i.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=n,t.memoizedState=f),i.props=n,i.state=f,i.context=g,n=s):(typeof i.componentDidMount=="function"&&(t.flags|=4194308),n=!1)}else{i=t.stateNode,M5(e,t),s=t.memoizedProps,g=t.type===t.elementType?s:Bn(t.type,s),i.props=g,S=t.pendingProps,O=i.context,f=r.contextType,typeof f=="object"&&f!==null?f=Rn(f):(f=Jr(r)?Za:Ar.current,f=ol(t,f));var T=r.getDerivedStateFromProps;(b=typeof T=="function"||typeof i.getSnapshotBeforeUpdate=="function")||typeof i.UNSAFE_componentWillReceiveProps!="function"&&typeof i.componentWillReceiveProps!="function"||(s!==S||O!==f)&&ox(t,i,n,f),ia=!1,O=t.memoizedState,i.state=O,pd(t,n,i,o);var C=t.memoizedS
Line truncated
|
||||
* @remix-run/router v1.10.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/function Ks(){return Ks=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},Ks.apply(this,arguments)}var fa;(function(e){e.Pop="POP",e.Push="PUSH",e.Replace="REPLACE"})(fa||(fa={}));const Px="popstate";function WM(e){e===void 0&&(e={});function t(n,o){let{pathname:a,search:i,hash:s}=n.location;return E0("",{pathname:a,search:i,hash:s},o.state&&o.state.usr||null,o.state&&o.state.key||"default")}function r(n,o){return typeof o=="string"?o:_d(o)}return HM(t,r,null,e)}function Xt(e,t){if(e===!1||e===null||typeof e>"u")throw new Error(t)}function cm(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function $M(){return Math.random().toString(36).substr(2,8)}function kx(e,t){return{usr:e.state,key:e.key,idx:t}}function E0(e,t,r,n){return r===void 0&&(r=null),Ks({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?Cl(t):t,{state:r,key:t&&t.key||n||$M()})}function _d(e){let{pathname:t="/",search:r="",hash:n=""}=e;return r&&r!=="?"&&(t+=r.charAt(0)==="?"?r:"?"+r),n&&n!=="#"&&(t+=n.charAt(0)==="#"?n:"#"+n),t}function Cl(e){let t={};if(e){let r=e.indexOf("#");r>=0&&(t.hash=e.substr(r),e=e.substr(0,r));let n=e.indexOf("?");n>=0&&(t.search=e.substr(n),e=e.substr(0,n)),e&&(t.pathname=e)}return t}function HM(e,t,r,n){n===void 0&&(n={});let{window:o=document.defaultView,v5Compat:a=!1}=n,i=o.history,s=fa.Pop,f=null,g=b();g==null&&(g=0,i.replaceState(Ks({},i.state,{idx:g}),""));function b(){return(i.state||{idx:null}).idx}function S(){s=fa.Pop;let m=b(),d=m==null?null:m-g;g=m,f&&f({action:s,location:w.location,delta:d})}function O(m,d){s=fa.Push;let l=E0(w.location,m,d);r&&r(l,m),g=b()+1;let c=kx(l,g),u=w.createHref(l);try{i.pushState(c,"",u)}catch(h){if(h instanceof DOMException&&h.name==="DataCloneError")throw h;o.location.assign(u)}a&&f&&f({action:s,location:w.location,delta:1})}function T(m,d){s=fa.Replace;let l=E0(w.location,m,d);r&&r(l,m),g=b();let c=kx(l,g),u=w.createHref(l);i.replaceState(c,"",u),a&&f&&f({action:s,location:w.location,delta:0})}function C(m){let d=o.location.origin!=="null"?o.location.origin:o.location.href,l=typeof m=="string"?m:_d(m);return Xt(d,"No window.location.(origin|href) available to create URL for href: "+l),new URL(l,d)}let w={get action(){return s},get location(){return e(o,i)},listen(m){if(f)throw new Error("A history only accepts one active listener");return o.addEventListener(Px,S),f=m,()=>{o.removeEventListener(Px,S),f=null}},createHref(m){return t(o,m)},createURL:C,encodeLocation(m){let d=C(m);return{pathname:d.pathname,search:d.search,hash:d.hash}},push:O,replace:T,go(m){return i.go(m)}};return w}var Ex;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(Ex||(Ex={}));function qM(e,t,r){r===void 0&&(r="/");let n=typeof t=="string"?Cl(t):t,o=Ys(n.pathname||"/",r);if(o==null)return null;let a=A1(e);GM(a);let i=null;for(let s=0;i==null&&s<a.length;++s)i=rI(a[s],oI(o));return i}function A1(e,t,r,n){t===void 0&&(t=[]),r===void 0&&(r=[]),n===void 0&&(n="");let o=(a,i,s)=>{let f={relativePath:s===void 0?a.path||"":s,caseSensitive:a.caseSensitive===!0,childrenIndex:i,route:a};f.relativePath.startsWith("/")&&(Xt(f.relativePath.startsWith(n),'Absolute route path "'+f.relativePath+'" nested under path '+('"'+n+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),f.relativePath=f.relativePath.slice(n.length));let g=wa([n,f.relativePath]),b=r.concat(f);a.children&&a.children.length>0&&(Xt(a.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+g+'".')),A1(a.children,t,b,g)),!(a.path==null&&!a.index)&&t.push({path:g,score:eI(g,a.index),routesMeta:b})};return e.forEach((a,i)=>{var s;if(a.path===""||!((s=a.path)!=null&&s.includes("?")))o(a,i);else for(let f of M1(a.path))o(a,i,f)}),t}function M1(e){let t=e.split("/");if(t.length===0)return[];let[r,...n]=t,o=r.endsWith("?"),a=r.replace(/\?$/,"");if(n.length===0)return o?[a,""]:[a];let i=M1(n.join("/")),s=[];return s.push(...i.map(f=>f===""?a:[a,f].join("/"))),o&&s.push(...i),s.map(f=>e.startsWith("/")&&f===""?"/":f)}function GM(e){e.sort((t,r)=>t.score!==r.score?r.score-t.score:tI(t.routesMeta.map(n=>n.childrenIndex),r.routesMeta.map(n=>n.childrenIndex)))}const KM=/^:\w+$/,YM=3,QM=2,XM=1,ZM=10,JM=-2,Nx=e=>e==="*";function eI(e,t){let r=e.split("/"),n=r.length;return r.some(Nx)&&(n+=JM),t&&(n+=QM),r.filter(o=>!Nx(o)).reduce((o,a)=>o+(KM.test(a)?YM:a===""?XM:ZM),n)}function tI(e,t){return e.length===t.length&&e.slice(0,-1).every((n,o)=>n===t[o])?e[e.length-1]-t[t.length-1]:0}function rI(e,t){let{routesMeta:r}=e,n={},o="/",a=[];for(let i=0;i<r.length;++i){let s=r[i],f=i===r.length-1,g=o==="/"?t:t.slice(o.length)||"/",b=N0({path:s.relativePath,caseSensitive:s.caseSensitive,end:f},g);if(!b)return null;Object.assign(n,b.paLine truncated
|
||||
* React Router v6.17.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/function Sd(){return Sd=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},Sd.apply(this,arguments)}const hf=k.createContext(null),D1=k.createContext(null),ui=k.createContext(null),gf=k.createContext(null),Uo=k.createContext({outlet:null,matches:[],isDataRoute:!1}),L1=k.createContext(null);function pI(e,t){let{relative:r}=t===void 0?{}:t;Tl()||Xt(!1);let{basename:n,navigator:o}=k.useContext(ui),{hash:a,pathname:i,search:s}=mf(e,{relative:r}),f=i;return n!=="/"&&(f=i==="/"?n:wa([n,i])),o.createHref({pathname:f,search:s,hash:a})}function Tl(){return k.useContext(gf)!=null}function Na(){return Tl()||Xt(!1),k.useContext(gf).location}function F1(e){k.useContext(ui).static||k.useLayoutEffect(e)}function yo(){let{isDataRoute:e}=k.useContext(Uo);return e?OI():hI()}function hI(){Tl()||Xt(!1);let e=k.useContext(hf),{basename:t,navigator:r}=k.useContext(ui),{matches:n}=k.useContext(Uo),{pathname:o}=Na(),a=JSON.stringify(dm(n).map(f=>f.pathnameBase)),i=k.useRef(!1);return F1(()=>{i.current=!0}),k.useCallback(function(f,g){if(g===void 0&&(g={}),!i.current)return;if(typeof f=="number"){r.go(f);return}let b=fm(f,JSON.parse(a),o,g.relative==="path");e==null&&t!=="/"&&(b.pathname=b.pathname==="/"?t:wa([t,b.pathname])),(g.replace?r.replace:r.push)(b,g.state,g)},[t,r,a,o,e])}function z1(){let{matches:e}=k.useContext(Uo),t=e[e.length-1];return t?t.params:{}}function mf(e,t){let{relative:r}=t===void 0?{}:t,{matches:n}=k.useContext(Uo),{pathname:o}=Na(),a=JSON.stringify(dm(n).map(i=>i.pathnameBase));return k.useMemo(()=>fm(e,JSON.parse(a),o,r==="path"),[e,a,o,r])}function gI(e,t){return mI(e,t)}function mI(e,t,r){Tl()||Xt(!1);let{navigator:n}=k.useContext(ui),{matches:o}=k.useContext(Uo),a=o[o.length-1],i=a?a.params:{};a&&a.pathname;let s=a?a.pathnameBase:"/";a&&a.route;let f=Na(),g;if(t){var b;let w=typeof t=="string"?Cl(t):t;s==="/"||(b=w.pathname)!=null&&b.startsWith(s)||Xt(!1),g=w}else g=f;let S=g.pathname||"/",O=s==="/"?S:S.slice(s.length)||"/",T=qM(e,{pathname:O}),C=wI(T&&T.map(w=>Object.assign({},w,{params:Object.assign({},i,w.params),pathname:wa([s,n.encodeLocation?n.encodeLocation(w.pathname).pathname:w.pathname]),pathnameBase:w.pathnameBase==="/"?s:wa([s,n.encodeLocation?n.encodeLocation(w.pathnameBase).pathname:w.pathnameBase])})),o,r);return t&&C?k.createElement(gf.Provider,{value:{location:Sd({pathname:"/",search:"",hash:"",state:null,key:"default"},g),navigationType:fa.Pop}},C):C}function bI(){let e=TI(),t=dI(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),r=e instanceof Error?e.stack:null,o={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"},a=null;return k.createElement(k.Fragment,null,k.createElement("h2",null,"Unexpected Application Error!"),k.createElement("h3",{style:{fontStyle:"italic"}},t),r?k.createElement("pre",{style:o},r):null,a)}const vI=k.createElement(bI,null);class yI extends k.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,r){return r.location!==t.location||r.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error||r.error,location:r.location,revalidation:t.revalidation||r.revalidation}}componentDidCatch(t,r){console.error("React Router caught the following error during render",t,r)}render(){return this.state.error?k.createElement(Uo.Provider,{value:this.props.routeContext},k.createElement(L1.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function xI(e){let{routeContext:t,match:r,children:n}=e,o=k.useContext(hf);return o&&o.static&&o.staticContext&&(r.route.errorElement||r.route.ErrorBoundary)&&(o.staticContext._deepestRenderedBoundaryId=r.route.id),k.createElement(Uo.Provider,{value:t},n)}function wI(e,t,r){var n;if(t===void 0&&(t=[]),r===void 0&&(r=null),e==null){var o;if((o=r)!=null&&o.errors)e=r.matches;else return null}let a=e,i=(n=r)==null?void 0:n.errors;if(i!=null){let s=a.findIndex(f=>f.route.id&&(i==null?void 0:i[f.route.id]));s>=0||Xt(!1),a=a.slice(0,Math.min(a.length,s+1))}return a.reduceRight((s,f,g)=>{let b=f.route.id?i==null?void 0:i[f.route.id]:null,S=null;r&&(S=f.route.errorElement||vI);let O=t.concat(a.slice(0,g+1)),T=()=>{let C;return b?C=S:f.route.Component?C=k.createElement(f.route.Component,null):f.route.element?C=f.route.element:C=s,k.createElement(xI,{match:f,routeContext:{outlet:s,matches:O,isDataRoute:r!=null},children:C})};return r&&(f.route.ErrorBoundary||f.route.errorElement||g===0)?k.createElement(yI,{location:r.location,revalidation:r.revalidation,component:S,error:b,children:T(),routeContext:{outlet:null,matches:O,isDataRoute:!0}}):T()},null)}var B1=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable=Line truncated
|
||||
* React Router DOM v6.17.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/function Td(){return Td=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},Td.apply(this,arguments)}function U1(e,t){if(e==null)return{};var r={},n=Object.keys(e),o,a;for(a=0;a<n.length;a++)o=n[a],!(t.indexOf(o)>=0)&&(r[o]=e[o]);return r}function EI(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function NI(e,t){return e.button===0&&(!t||t==="_self")&&!EI(e)}const jI=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","unstable_viewTransition"],RI=["aria-current","caseSensitive","className","end","style","to","unstable_viewTransition","children"],AI=k.createContext({isTransitioning:!1}),MI="startTransition",jx=fg[MI];function II(e){let{basename:t,children:r,future:n,window:o}=e,a=k.useRef();a.current==null&&(a.current=WM({window:o,v5Compat:!0}));let i=a.current,[s,f]=k.useState({action:i.action,location:i.location}),{v7_startTransition:g}=n||{},b=k.useCallback(S=>{g&&jx?jx(()=>f(S)):f(S)},[f,g]);return k.useLayoutEffect(()=>i.listen(b),[i,b]),k.createElement(kI,{basename:t,children:r,location:s.location,navigationType:s.action,navigator:i})}const DI=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",LI=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,W1=k.forwardRef(function(t,r){let{onClick:n,relative:o,reloadDocument:a,replace:i,state:s,target:f,to:g,preventScrollReset:b,unstable_viewTransition:S}=t,O=U1(t,jI),{basename:T}=k.useContext(ui),C,w=!1;if(typeof g=="string"&&LI.test(g)&&(C=g,DI))try{let c=new URL(window.location.href),u=g.startsWith("//")?new URL(c.protocol+g):new URL(g),h=Ys(u.pathname,T);u.origin===c.origin&&h!=null?g=h+u.search+u.hash:w=!0}catch{}let m=pI(g,{relative:o}),d=BI(g,{replace:i,state:s,target:f,preventScrollReset:b,relative:o,unstable_viewTransition:S});function l(c){n&&n(c),c.defaultPrevented||d(c)}return k.createElement("a",Td({},O,{href:C||m,onClick:w||a?n:l,ref:r,target:f}))}),FI=k.forwardRef(function(t,r){let{"aria-current":n="page",caseSensitive:o=!1,className:a="",end:i=!1,style:s,to:f,unstable_viewTransition:g,children:b}=t,S=U1(t,RI),O=mf(f,{relative:S.relative}),T=Na(),C=k.useContext(D1),{navigator:w}=k.useContext(ui),m=C!=null&&VI(O)&&g===!0,d=w.encodeLocation?w.encodeLocation(O).pathname:O.pathname,l=T.pathname,c=C&&C.navigation&&C.navigation.location?C.navigation.location.pathname:null;o||(l=l.toLowerCase(),c=c?c.toLowerCase():null,d=d.toLowerCase());let u=l===d||!i&&l.startsWith(d)&&l.charAt(d.length)==="/",h=c!=null&&(c===d||!i&&c.startsWith(d)&&c.charAt(d.length)==="/"),v={isActive:u,isPending:h,isTransitioning:m},y=u?n:void 0,E;typeof a=="function"?E=a(v):E=[a,u?"active":null,h?"pending":null,m?"transitioning":null].filter(Boolean).join(" ");let P=typeof s=="function"?s(v):s;return k.createElement(W1,Td({},S,{"aria-current":y,className:E,ref:r,style:P,to:f,unstable_viewTransition:g}),typeof b=="function"?b(v):b)});var R0;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(R0||(R0={}));var Rx;(function(e){e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(Rx||(Rx={}));function zI(e){let t=k.useContext(hf);return t||Xt(!1),t}function BI(e,t){let{target:r,replace:n,state:o,preventScrollReset:a,relative:i,unstable_viewTransition:s}=t===void 0?{}:t,f=yo(),g=Na(),b=mf(e,{relative:i});return k.useCallback(S=>{if(NI(S,r)){S.preventDefault();let O=n!==void 0?n:_d(g)===_d(b);f(e,{replace:O,state:o,preventScrollReset:a,relative:i,unstable_viewTransition:s})}},[g,f,b,n,o,r,e,a,i,s])}function VI(e,t){t===void 0&&(t={});let r=k.useContext(AI);r==null&&Xt(!1);let{basename:n}=zI(R0.useViewTransitionState),o=mf(e,{relative:t.relative});if(!r.isTransitioning)return!1;let a=Ys(r.currentLocation.pathname,n)||r.currentLocation.pathname,i=Ys(r.nextLocation.pathname,n)||r.nextLocation.pathname;return N0(o.pathname,i)!=null||N0(o.pathname,a)!=null}function UI({title:e,titleId:t,...r},n){return k.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",ref:n,"aria-labelledby":t},r),e?k.createElement("title",{id:t},e):null,k.createElement("path",{fillRule:"evenodd",d:"M7.5 3.75A1.5 1.5 0 006 5.25v13.5a1.5 1.5 0 001.5 1.5h6a1.5 1.5 0 001.5-1.5V15a.75.75 0 011.5 0v3.75a3 3 0 01-3 3h-6a3 3 0 01-3-3V5.25a3 3 0 013-3h6a3 3 0 013 3V9A.75.75 0 0115 9V5.25a1.5 1.5 0 00-1.5-1.5h-6zm5.03 4.72a.75.75 0 010 1.06l-1.72 1.72h10.94a.75.75 0 010 1.5H10.81l1.72 1.72a.75.75 0 11-1.06 1.06l-3-3a.75.75 0 010-1.06l3-3a.75.75 0 011.06 0z",clipRule:"evenodd"}))}const WI=k.forwardRef(UI),$I=WI;function HI({title:e,titleId:t,...r},n){return k.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",rLine truncated
|
||||
Copyright (c) 2018 Jed Watson.
|
||||
Licensed under the MIT License (MIT), see
|
||||
http://jedwatson.github.io/classnames
|
||||
*/(function(e){(function(){var t={}.hasOwnProperty;function r(){for(var n=[],o=0;o<arguments.length;o++){var a=arguments[o];if(a){var i=typeof a;if(i==="string"||i==="number")n.push(a);else if(Array.isArray(a)){if(a.length){var s=r.apply(null,a);s&&n.push(s)}}else if(i==="object"){if(a.toString!==Object.prototype.toString&&!a.toString.toString().includes("[native code]")){n.push(a.toString());continue}for(var f in a)t.call(a,f)&&a[f]&&n.push(f)}}}return n.join(" ")}e.exports?(r.default=r,e.exports=r):window.classNames=r})()})(q1);var at=q1.exports,G1={exports:{}},zn={};function hm(){for(var e,t,r=0,n="";r<arguments.length;)(e=arguments[r++])&&(t=K1(e))&&(n&&(n+=" "),n+=t);return n}function K1(e){if(typeof e=="string")return e;for(var t,r="",n=0;n<e.length;n++)e[n]&&(t=K1(e[n]))&&(r&&(r+=" "),r+=t);return r}function A0(){return A0=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},A0.apply(this,arguments)}function x4(e){var t=function(r){var n=r.theme,o=r.prefix,a={nextPart:new Map,validators:[]};return function(i,s){return s?i.map(function(f){return[f[0],f[1].map(function(g){return typeof g=="string"?s+g:typeof g=="object"?Object.fromEntries(Object.entries(g).map(function(b){return[s+b[0],b[1]]})):g})]}):i}(Object.entries(r.classGroups),o).forEach(function(i){M0(i[1],a,i[0],n)}),a}(e);return{getClassGroupId:function(r){var n=r.split("-");return n[0]===""&&n.length!==1&&n.shift(),Y1(n,t)||function(o){if(Ax.test(o)){var a=Ax.exec(o)[1],i=a==null?void 0:a.substring(0,a.indexOf(":"));if(i)return"arbitrary.."+i}}(r)},getConflictingClassGroupIds:function(r){return e.conflictingClassGroups[r]||[]}}}function Y1(e,t){var r;if(e.length===0)return t.classGroupId;var n=t.nextPart.get(e[0]),o=n?Y1(e.slice(1),n):void 0;if(o)return o;if(t.validators.length!==0){var a=e.join("-");return(r=t.validators.find(function(i){return(0,i.validator)(a)}))==null?void 0:r.classGroupId}}Object.defineProperty(zn,"__esModule",{value:!0});var Ax=/^\[(.+)\]$/;function M0(e,t,r,n){e.forEach(function(o){if(typeof o!="string"){if(typeof o=="function")return o.isThemeGetter?void M0(o(n),t,r,n):void t.validators.push({validator:o,classGroupId:r});Object.entries(o).forEach(function(a){M0(a[1],Mx(t,a[0]),r,n)})}else(o===""?t:Mx(t,o)).classGroupId=r})}function Mx(e,t){var r=e;return t.split("-").forEach(function(n){r.nextPart.has(n)||r.nextPart.set(n,{nextPart:new Map,validators:[]}),r=r.nextPart.get(n)}),r}function w4(e){if(e<1)return{get:function(){},set:function(){}};var t=0,r=new Map,n=new Map;function o(a,i){r.set(a,i),++t>e&&(t=0,n=r,r=new Map)}return{get:function(a){var i=r.get(a);return i!==void 0?i:(i=n.get(a))!==void 0?(o(a,i),i):void 0},set:function(a,i){r.has(a)?r.set(a,i):o(a,i)}}}function _4(e){var t=e.separator||":";return function(r){for(var n=0,o=[],a=0,i=0;i<r.length;i++){var s=r[i];n===0&&s===t[0]&&(t.length!==1&&r.slice(i,i+t.length)!==t||(o.push(r.slice(a,i)),a=i+t.length)),s==="["?n++:s==="]"&&n--}var f=o.length===0?r:r.substring(a),g=f.startsWith("!");return{modifiers:o,hasImportantModifier:g,baseClassName:g?f.substring(1):f}}}function S4(e){return A0({cache:w4(e.cacheSize),splitModifiers:_4(e)},x4(e))}var C4=/\s+/;function T4(e,t){var r=t.splitModifiers,n=t.getClassGroupId,o=t.getConflictingClassGroupIds,a=new Set;return e.trim().split(C4).map(function(i){var s=r(i),f=s.modifiers,g=s.hasImportantModifier,b=n(s.baseClassName);if(!b)return{isTailwindClass:!1,originalClassName:i};var S=function(O){if(O.length<=1)return O;var T=[],C=[];return O.forEach(function(w){w[0]==="["?(T.push.apply(T,C.sort().concat([w])),C=[]):C.push(w)}),T.push.apply(T,C.sort()),T}(f).join(":");return{isTailwindClass:!0,modifierId:g?S+"!":S,classGroupId:b,originalClassName:i}}).reverse().filter(function(i){if(!i.isTailwindClass)return!0;var s=i.modifierId,f=i.classGroupId,g=s+f;return!a.has(g)&&(a.add(g),o(f).forEach(function(b){return a.add(s+b)}),!0)}).reverse().map(function(i){return i.originalClassName}).join(" ")}function I0(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];var n,o,a,i=s;function s(g){var b=t[0],S=t.slice(1).reduce(function(O,T){return T(O)},b());return n=S4(S),o=n.cache.get,a=n.cache.set,i=f,f(g)}function f(g){var b=o(g);if(b)return b;var S=T4(g,n);return a(g,S),S}return function(){return i(hm.apply(null,arguments))}}function zt(e){var t=function(r){return r[e]||[]};return t.isThemeGetter=!0,t}var ja=/^\[(.+)\]$/,O4=/^\d+\/\d+$/,P4=new Set(["px","full","screen"]),k4=/^(\d+)?(xs|sm|md|lg|xl)$/,E4=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh)/,N4=/^-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/;function Sn(e){return!Number.isNaN(Number(e))||P4.has(e)||O4.test(e)||la(e)}function la(e){var t,r=(t=ja.exec(e))==null?void 0:t[1];return!!r&&(r.startsWith("length:")||E4.test(r))}function Q1(e){var t,r=(t=ja.exec(e))==null?void 0:t[1];return!!r&&r.startsWLine truncated
|
||||
* tabbable 6.2.0
|
||||
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE
|
||||
*/var xF=["input:not([inert])","select:not([inert])","textarea:not([inert])","a[href]:not([inert])","button:not([inert])","[tabindex]:not(slot):not([inert])","audio[controls]:not([inert])","video[controls]:not([inert])",'[contenteditable]:not([contenteditable="false"]):not([inert])',"details>summary:first-of-type:not([inert])","details:not([inert])"],Q0=xF.join(","),nP=typeof Element>"u",tu=nP?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,Id=!nP&&Element.prototype.getRootNode?function(e){var t;return e==null||(t=e.getRootNode)===null||t===void 0?void 0:t.call(e)}:function(e){return e==null?void 0:e.ownerDocument},Dd=function e(t,r){var n;r===void 0&&(r=!0);var o=t==null||(n=t.getAttribute)===null||n===void 0?void 0:n.call(t,"inert"),a=o===""||o==="true",i=a||r&&t&&e(t.parentNode);return i},wF=function(t){var r,n=t==null||(r=t.getAttribute)===null||r===void 0?void 0:r.call(t,"contenteditable");return n===""||n==="true"},_F=function(t,r,n){if(Dd(t))return[];var o=Array.prototype.slice.apply(t.querySelectorAll(Q0));return r&&tu.call(t,Q0)&&o.unshift(t),o=o.filter(n),o},SF=function e(t,r,n){for(var o=[],a=Array.from(t);a.length;){var i=a.shift();if(!Dd(i,!1))if(i.tagName==="SLOT"){var s=i.assignedElements(),f=s.length?s:i.children,g=e(f,!0,n);n.flatten?o.push.apply(o,g):o.push({scopeParent:i,candidates:g})}else{var b=tu.call(i,Q0);b&&n.filter(i)&&(r||!t.includes(i))&&o.push(i);var S=i.shadowRoot||typeof n.getShadowRoot=="function"&&n.getShadowRoot(i),O=!Dd(S,!1)&&(!n.shadowRootFilter||n.shadowRootFilter(i));if(S&&O){var T=e(S===!0?i.children:S.children,!0,n);n.flatten?o.push.apply(o,T):o.push({scopeParent:i,candidates:T})}else a.unshift.apply(a,i.children)}}return o},oP=function(t){return!isNaN(parseInt(t.getAttribute("tabindex"),10))},aP=function(t){if(!t)throw new Error("No node provided");return t.tabIndex<0&&(/^(AUDIO|VIDEO|DETAILS)$/.test(t.tagName)||wF(t))&&!oP(t)?0:t.tabIndex},CF=function(t,r){var n=aP(t);return n<0&&r&&!oP(t)?0:n},TF=function(t,r){return t.tabIndex===r.tabIndex?t.documentOrder-r.documentOrder:t.tabIndex-r.tabIndex},iP=function(t){return t.tagName==="INPUT"},OF=function(t){return iP(t)&&t.type==="hidden"},PF=function(t){var r=t.tagName==="DETAILS"&&Array.prototype.slice.apply(t.children).some(function(n){return n.tagName==="SUMMARY"});return r},kF=function(t,r){for(var n=0;n<t.length;n++)if(t[n].checked&&t[n].form===r)return t[n]},EF=function(t){if(!t.name)return!0;var r=t.form||Id(t),n=function(s){return r.querySelectorAll('input[type="radio"][name="'+s+'"]')},o;if(typeof window<"u"&&typeof window.CSS<"u"&&typeof window.CSS.escape=="function")o=n(window.CSS.escape(t.name));else try{o=n(t.name)}catch(i){return console.error("Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s",i.message),!1}var a=kF(o,t.form);return!a||a===t},NF=function(t){return iP(t)&&t.type==="radio"},jF=function(t){return NF(t)&&!EF(t)},RF=function(t){var r,n=t&&Id(t),o=(r=n)===null||r===void 0?void 0:r.host,a=!1;if(n&&n!==t){var i,s,f;for(a=!!((i=o)!==null&&i!==void 0&&(s=i.ownerDocument)!==null&&s!==void 0&&s.contains(o)||t!=null&&(f=t.ownerDocument)!==null&&f!==void 0&&f.contains(t));!a&&o;){var g,b,S;n=Id(o),o=(g=n)===null||g===void 0?void 0:g.host,a=!!((b=o)!==null&&b!==void 0&&(S=b.ownerDocument)!==null&&S!==void 0&&S.contains(o))}}return a},sw=function(t){var r=t.getBoundingClientRect(),n=r.width,o=r.height;return n===0&&o===0},AF=function(t,r){var n=r.displayCheck,o=r.getShadowRoot;if(getComputedStyle(t).visibility==="hidden")return!0;var a=tu.call(t,"details>summary:first-of-type"),i=a?t.parentElement:t;if(tu.call(i,"details:not([open]) *"))return!0;if(!n||n==="full"||n==="legacy-full"){if(typeof o=="function"){for(var s=t;t;){var f=t.parentElement,g=Id(t);if(f&&!f.shadowRoot&&o(f)===!0)return sw(t);t.assignedSlot?t=t.assignedSlot:!f&&g!==t.ownerDocument?t=g.host:t=f}t=s}if(RF(t))return!t.getClientRects().length;if(n!=="legacy-full")return!0}else if(n==="non-zero-area")return sw(t);return!1},MF=function(t){if(/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(t.tagName))for(var r=t.parentElement;r;){if(r.tagName==="FIELDSET"&&r.disabled){for(var n=0;n<r.children.length;n++){var o=r.children.item(n);if(o.tagName==="LEGEND")return tu.call(r,"fieldset[disabled] *")?!0:!o.contains(t)}return!0}r=r.parentElement}return!1},IF=function(t,r){return!(r.disabled||Dd(r)||OF(r)||AF(r,t)||PF(r)||MF(r))},uw=function(t,r){return!(jF(r)||aP(r)<0||!IF(t,r))},DF=function(t){var r=parseInt(t.getAttribute("tabindex"),10);return!!(isNaN(r)||r>=0)},LF=function e(t){var r=[],n=[];return t.forEach(function(o,a){var i=!!o.scopeParent,s=i?o.scopeParent:o,f=CF(s,i),g=i?e(o.candidates):s;f===0?i?r.push.apply(r,g):r.push(s):n.push({documentOrder:a,tabIndex:f,item:o,isScope:i,content:g})}),n.sort(TF).reduce(function(o,a){return a.isScope?o.push.apply(o,a.content):o.push(a.content),o},[]).concatLine truncated
|
||||
|
||||
More details: https://www.material-tailwind.com/docs/react/collapse
|
||||
`);var P=r.default.useRef(null),N=(0,g.useTheme)().navbar,R=N.styles,M=R.base.mobileNav;u=u??{},h=h??"";var L=(0,s.twMerge)((0,i.default)((0,f.default)(M)),h),D={unmount:{height:0,opacity:0,transition:{duration:.3,times:"[0.4, 0, 0.2, 1]"}},mount:{opacity:1,height:"".concat((E=P.current)===null||E===void 0?void 0:E.scrollHeight,"px"),transition:{duration:.3,times:"[0.4, 0, 0.2, 1]"}}},B=(0,a.default)(D,u),H=n.AnimatePresence,Q=(0,o.useMergeRefs)([l,P]);return r.default.createElement(n.LazyMotion,{features:n.domAnimation},r.default.createElement(H,null,r.default.createElement(n.m.div,S({},y,{ref:Q,className:L,initial:"unmount",exit:"unmount",animate:c?"mount":"unmount",variants:B}),v)))});w.displayName="MaterialTailwind.MobileNav",w.propTypes={open:b.propTypesOpen,animate:b.propTypesAnimate,className:b.propTypesClassName,children:b.propTypesChildren};var m=w})(YP);(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(l,c){for(var u in c)Object.defineProperty(l,u,{enumerable:!0,get:c[u]})}t(e,{Navbar:function(){return m},MobileNav:function(){return g.MobileNav},default:function(){return d}});var r=T(k),n=T(Ze),o=T(at),a=Xe,i=T(qt),s=T(Ye),f=Ke,g=YP,b=bf;function S(l,c,u){return c in l?Object.defineProperty(l,c,{value:u,enumerable:!0,configurable:!0,writable:!0}):l[c]=u,l}function O(){return O=Object.assign||function(l){for(var c=1;c<arguments.length;c++){var u=arguments[c];for(var h in u)Object.prototype.hasOwnProperty.call(u,h)&&(l[h]=u[h])}return l},O.apply(this,arguments)}function T(l){return l&&l.__esModule?l:{default:l}}function C(l,c){if(l==null)return{};var u=w(l,c),h,v;if(Object.getOwnPropertySymbols){var y=Object.getOwnPropertySymbols(l);for(v=0;v<y.length;v++)h=y[v],!(c.indexOf(h)>=0)&&Object.prototype.propertyIsEnumerable.call(l,h)&&(u[h]=l[h])}return u}function w(l,c){if(l==null)return{};var u={},h=Object.keys(l),v,y;for(y=0;y<h.length;y++)v=h[y],!(c.indexOf(v)>=0)&&(u[v]=l[v]);return u}var m=r.default.forwardRef(function(l,c){var u=l.variant,h=l.color,v=l.shadow,y=l.blurred,E=l.fullWidth,P=l.className,N=l.children,R=C(l,["variant","color","shadow","blurred","fullWidth","className","children"]),M=(0,f.useTheme)().navbar,L=M.defaultProps,D=M.valid,B=M.styles,H=B.base,Q=B.variants;u=u??L.variant,h=h??L.color,v=v??L.shadow,y=y??L.blurred,E=E??L.fullWidth,P=P??L.className;var W,z=(0,o.default)((0,s.default)(H.navbar.initial),(W={},S(W,(0,s.default)(H.navbar.shadow),v),S(W,(0,s.default)(H.navbar.blurred),y&&h==="white"),S(W,(0,s.default)(H.navbar.fullWidth),E),W)),$=(0,o.default)((0,s.default)(Q[(0,i.default)(D.variants,u,"filled")][(0,i.default)(D.colors,h,"white")])),q=(0,a.twMerge)((0,o.default)(z,$),P);return r.default.createElement("nav",O({},R,{ref:c,className:q}),N)});m.propTypes={variant:n.default.oneOf(b.propTypesVariant),color:n.default.oneOf(b.propTypesColor),shadow:b.propTypesShadow,blurred:b.propTypesBlurred,fullWidth:b.propTypesFullWidth,className:b.propTypesClassName,children:b.propTypesChildren},m.displayName="MaterialTailwind.Navbar";var d=Object.assign(m,{MobileNav:g.MobileNav})})(KP);var QP={},Ff={},Al={};(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(m,d){for(var l in d)Object.defineProperty(m,l,{enumerable:!0,get:d[l]})}t(e,{propTypesOpen:function(){return a},propTypesHandler:function(){return i},propTypesPlacement:function(){return s},propTypesOffset:function(){return f},propTypesDismiss:function(){return g},propTypesAnimate:function(){return b},propTypesContent:function(){return S},propTypesInteractive:function(){return O},propTypesClassName:function(){return T},propTypesChildren:function(){return C},propTypesContextValue:function(){return w}});var r=o(Ze),n=Vt;function o(m){return m&&m.__esModule?m:{default:m}}var a=r.default.bool,i=r.default.func,s=n.propTypesPlacements,f=n.propTypesOffsetType,g=n.propTypesDismissType,b=n.propTypesAnimation,S=r.default.node,O=r.default.bool,T=r.default.string,C=r.default.node.isRequired,w=r.default.shape({open:r.default.bool.isRequired,strategy:r.default.oneOf(["fixed","absolute"]).isRequired,x:r.default.number,y:r.default.number,context:r.default.instanceOf(Object).isRequired,reference:r.default.func.isRequired,floating:r.default.func.isRequired,getReferenceProps:r.default.func.isRequired,getFloatingProps:r.default.func.isRequired,appliedAnimation:b.isRequired,labelId:r.default.string.isRequired,descriptionId:r.default.string.isRequired}).isRequired})(Al);(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(f,g){for(var b in g)Object.defineProperty(f,b,{enumerable:!0,get:g[b]})}t(e,{PopoverContext:function(){return a},usePopover:function(){return i},PopoverContextProvider:function(){return s}});var r=o(k),n=Al;function o(f){return f&&f.__esModule?f:{default:f}}var a=r.default.createContext(null);a.displayName="MaterialTailwind.PopoverContext";function i(){var f=r.default.useContext(a);if(!f)throw new Error("usePopover() must be used within a Popover. It happens when you us
Line truncated
|
||||
${se?"border-purple-500 ring-1 ring-purple-500/40":"border-transparent"}
|
||||
${!ft||pe?"opacity-75 bg-stormGrey-200/20":""}`,children:[x.jsxs("div",{className:"flex items-center space-x-4",id:"left",children:[P&&x.jsx("input",{type:"checkbox",checked:se,onChange:()=>je(ee.Id,!pt),className:`h-5 w-5 text-purple-500 focus:ring-purple-400 border-gray-600 bg-gray-900 rounded ${pt?"":"opacity-40 cursor-not-allowed"}`,disabled:!pt}),x.jsx("div",{className:`w-12 h-12 flex items-center justify-center rounded-full
|
||||
${ee.updateStatus==="✅"?"bg-mossGreen-600":ee.updateStatus==="⚠️"?"bg-warmAmberGlow-400":"bg-sunsetCoral-500"}`}),x.jsxs("div",{children:[x.jsx("p",{className:"text-lg antialiased font-sans font-semibold leading-normal text-inherit",children:ee.Name}),x.jsxs("p",{className:"text-sm antialiased font-sans font-light leading-normal text-inherit",children:["ID: ",ee.Id]}),ee.duplicateName&&x.jsx("p",{className:"text-xs text-amber-700",children:"⚠️ Doppelter Name erkannt"})]})]}),x.jsx("div",{className:"ml-auto flex flex-col items-end gap-1 text-sm",id:"right",children:xe?x.jsxs(x.Fragment,{children:[x.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-warmAmberGlow-600",children:"Redeploy"}),x.jsx("span",{className:"text-warmAmberGlow-500",children:"läuft…"})]}):Oe?x.jsxs(x.Fragment,{children:[x.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-warmAmberGlow-600",children:"Redeploy"}),x.jsx("span",{className:"text-warmAmberGlow-500",children:"in Warteliste…"})]}):tt?x.jsxs(x.Fragment,{children:[x.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-stormGrey-500",children:"System"}),x.jsx("span",{className:"text-gray-300",children:"Redeploy deaktiviert"})]}):Me?x.jsxs(x.Fragment,{children:[x.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-stormGrey-500",children:"Status"}),x.jsx("span",{className:"text-mossGreen-600",children:"Aktuell"})]}):E?x.jsx(G.Button,{onClick:()=>st(ee.Id),disabled:pe,className:"disabled:opacity-50 disabled:cursor-not-allowed",children:"Redeploy"}):null})]})},ee.Id)})}),x.jsx(Wf,{disabled:r||!!o})]})}const ZB={idle:"Bereit",running:"Läuft",success:"Erfolgreich",error:"Fehlgeschlagen"},ss={idle:"bg-stormGrey-700 text-gray-200",running:"bg-arcticBlue-600 text-white",success:"bg-mossGreen-600 text-white",error:"bg-sunsetCoral-600 text-white"},JB={initializing:"Vorbereitung","activating-maintenance":"Wartungsmodus aktivieren","executing-script":"Skript wird ausgeführt",waiting:"Warte auf Portainer",completed:"Abgeschlossen",failed:"Fehlgeschlagen"},eV={info:"text-arcticBlue-700",success:"text-mossGreen-700",warning:"text-warmAmberGlow-700",error:"text-sunsetCoral-700",stdout:"text-stormGrey-700",stderr:"text-warmAmberGlow-700",debug:"text-lavenderSmoke-700"},tV=e=>{if(!e)return"-";const t=new Date(e);return Number.isNaN(t.getTime())?e:t.toLocaleString("de-DE",{hour:"2-digit",minute:"2-digit",second:"2-digit"})},Fa=e=>{if(!e&&e!==0)return"-";const r=(n=>{if(n instanceof Date)return n;if(typeof n=="number"){const o=n>1e12?n:n*1e3;return new Date(o)}if(typeof n=="string"){const o=Number(n);if(!Number.isNaN(o)){const a=o>1e12?o:o*1e3;return new Date(a)}return new Date(n)}return null})(e);return!r||Number.isNaN(r.getTime())?String(e):r.toLocaleString("de-DE",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"})},$w=e=>e===1?"Git":e===2?"Compose":e??"-",Oh=()=>({host:"",port:"22",username:"",password:"",extraSshArgs:""});function rV(){var _i,Si,Nu;const{hasPermission:e,user:t}=mn(),r=k.useCallback(()=>{},[]),{maintenance:n,update:o,script:a,ssh:i,loading:s,error:f,setMaintenanceMode:g,triggerUpdate:b,saveScript:S,resetScript:O,saveSshConfig:T,deleteSshConfig:C,testSshConnection:w,fetchSuperuserStatus:m,fetchSetupStatus:d,deleteSetupEndpoint:l,deleteSetupServer:c,updateSetupApiKey:u,removeSuperuserAccount:h}=mi(),v=!!(t!=null&&t.isSuperuser),y=!!(v||e("maintenance-access","read")),E=!!(v||e("maintenance-access","full")),P=!!(v||e("maintenance-server-manage","read")),N=!!(v||e("maintenance-server-manage","full")),R=!!(v||e("maintenance-server-delete","full")),M=!!(v||e("maintenance-portainer","read")),L=!!(v||e("maintenance-update","full")),D=!!(v||e("maintenance-ssh-update","read")),B=!!(v||e("maintenance-ssh-update","full")),H=!!(v||e("maintenance-duplicates","read")),Q=!!(v||e("maintenance-duplicates","full")),W=!!(n!=null&&n.active),z=n==null?void 0:n.message,$=(_i=n==null?void 0:n.extra)==null?void 0:_i.type,q=!!(o!=null&&o.running),[Z,ie]=k.useState(""),[me,ue]=k.useState(!1),[te,Se]=k.useState(!1),[ve,he]=k.useState(""),[re,ge]=k.useState(!1),[Ce,Ne]=k.useState(()=>Oh()),[Pe,Te]=k.useState(!1),[je,ze]=k.useState(!1),[qe,it]=k.useState(!1),[et,st]=k.useState(!1),[Ge,we]=k.useState(!1),[Ee,ke]=k.useState(null);k.useEffect(()=>{if(!a)return;const be=a.custom??a.default??a.effective??"";ie(be)},[a]),k.useEffect(()=>{if(!i){Ne(Oh()),Te(!1),ze(!1),ke(null);return}Ne({host:i.host??"",port:String(i.port??"22"),username:i.username??"",password:"",extraSshArgs:Array.isArray(i.extraSshArgs)?i.extraSshArgs.join(`
|
||||
`):""}),Te(!!i.passwordStored),ze(!1),ke(null)},[i]);const De=k.useMemo(()=>a?a.source==="custom"&&typeof a.custom=="string"?a.custom:a.default??a.effective??"":"",[a]),de=a?Z!==De:!1,Ae=(a==null?void 0:a.source)==="custom"?"Benutzerdefiniert":"Standard",[fe,ee]=k.useState(!0),[ae,Oe]=k.useState(!1),[xe,pe]=k.useState(null),[se,Me]=k.useState(""),[tt,ft]=k.useState(!1),[pt,bt]=k.useState(null),[ht,Jt]=k.useState(!0),[er,Ut]=k.useState(""),[Mr,Wt]=k.useState({}),[Cr,Gt]=k.useState(null),[tn,Tr]=k.useState(null),[Wr,Mn]=k.useState(null),[$r,bn]=k.useState([]),[pr,ro]=k.useState(!0),[J,Ie]=k.useState(""),[Be,Ot]=k.useState(!1),[Mt,mt]=k.useState(null),[Pt,sr]=k.useState(null),xt=k.useRef(null),[_t,Hr]=k.useState(null),[or,vn]=k.useState(!0),[wo,$o]=k.useState(""),[_o,Ho]=k.useState(!1),[Ra,So]=k.useState(null),In=k.useRef(null),rn=k.useCallback(async({silent:be=!1}={})=>{var Fe,Ve;ee(!0),Me("");try{const We=await m(),rt=!!(We!=null&&We.exists);Oe(rt),pe(rt?We.user??null:null)}catch(We){const rt=((Ve=(Fe=We.response)==null?void 0:Fe.data)==null?void 0:Ve.error)||We.message||"Superuser-Status konnte nicht geladen werden";Oe(!1),pe(null),Me(rt),be||r({variant:"error",title:"Statusabfrage fehlgeschlagen",description:rt})}finally{ee(!1)}},[m,r]),Ir=k.useCallback(async({silent:be=!1}={})=>{var Fe,Ve;be||Jt(!0),Ut("");try{const We=await d();bt(We),Wt({})}catch(We){const rt=((Ve=(Fe=We.response)==null?void 0:Fe.data)==null?void 0:Ve.error)||We.message||"Setup-Daten konnten nicht geladen werden";Ut(rt),be||r({variant:"error",title:"Setup-Daten",description:rt})}finally{Jt(!1)}},[d,r]),Aa=k.useCallback(async({silent:be=!1}={})=>{if(In.current)return In.current;const Fe=(async()=>{var Ve,We;be?Ho(!0):vn(!0),$o("");try{const Rt=(await nt.get("/api/maintenance/portainer-status")).data??{};Hr(Rt),So(new Date)}catch(rt){const Rt=((We=(Ve=rt.response)==null?void 0:Ve.data)==null?void 0:We.error)||rt.message||"Fehler beim Prüfen des Portainer-Status";$o(Rt),r({variant:"error",title:"Statusaktualisierung fehlgeschlagen",description:Rt})}finally{be?Ho(!1):vn(!1),In.current=null}})();return In.current=Fe,Fe},[r]),Co=k.useCallback(async({silent:be=!1}={})=>{if(W||q){bn([]),Ie("Wartungsmodus aktiv – Duplikat-Verwaltung ist vorübergehend deaktiviert."),ro(!1),Ot(!1);return}if(xt.current)return xt.current;const Fe=(async()=>{var Ve,We,rt;be?Ot(!0):ro(!0),Ie("");try{const Nt=(await nt.get("/api/maintenance/duplicates")).data,Dt=Array.isArray(Nt)?Nt:(Nt==null?void 0:Nt.items)??[];bn(Dt),sr(new Date)}catch(Rt){if(((Ve=Rt.response)==null?void 0:Ve.status)===423){const Nt="Wartungsmodus aktiv – Duplikat-Verwaltung ist vorübergehend deaktiviert.";bn([]),Ie(Nt),r({variant:"warning",title:"Duplikat-Verwaltung gesperrt",description:Nt})}else{const Nt=((rt=(We=Rt.response)==null?void 0:We.data)==null?void 0:rt.error)||Rt.message||"Fehler beim Laden der Wartungsdaten";Ie(Nt),r({variant:"error",title:"Duplikate konnten nicht geladen werden",description:Nt})}}finally{be?Ot(!1):ro(!1),xt.current=null}})();return xt.current=Fe,Fe},[W,q,r]);k.useEffect(()=>{rn({silent:!0})},[rn]),k.useEffect(()=>{Ir()},[Ir]),k.useEffect(()=>{Aa()},[Aa]),k.useEffect(()=>{Co()},[Co]);const Dn=k.useMemo(()=>{var be;return((be=pt==null?void 0:pt.servers)==null?void 0:be.items)??[]},[pt]),Dr=k.useMemo(()=>{var be;return((be=pt==null?void 0:pt.endpoints)==null?void 0:be.items)??[]},[pt]),It=k.useMemo(()=>{var Fe;const be=((Fe=pt==null?void 0:pt.apiKeys)==null?void 0:Fe.items)??[];return new Map(be.map(Ve=>[Ve.serverId,Ve]))},[pt]),tr=k.useMemo(()=>!!(pt!=null&&pt.setupComplete),[pt]),Et=k.useMemo(()=>{const be=Array.isArray($r)?$r.length:0,Fe=Array.isArray($r)?$r.reduce((Ve,We)=>{var rt;return Ve+(((rt=We==null?void 0:We.duplicates)==null?void 0:rt.length)||0)},0):0;return{groups:be,duplicateCount:Fe}},[$r]),Or=k.useCallback((be,Fe)=>{B&&(Ne(Ve=>({...Ve,[be]:Fe})),be==="password"&&Te(!1))},[B]),yn=k.useMemo(()=>{const be={host:Ce.host.trim(),port:Number.parseInt(Ce.port,10)||22,username:Ce.username.trim(),extraSshArgs:(Ce.extraSshArgs||"").split(/\r?\n/).map(Ve=>Ve.trim()).filter(Boolean)},Fe=Ce.password??"";return Fe?be.password=Fe:Pe||(be.password=""),be},[Ce,Pe]),no=k.useCallback(async()=>{var be,Fe;if(B&&a)try{ue(!0),await S(Z),r({variant:"success",title:"Skript gespeichert",description:"Das benutzerdefinierte Portainer-Update-Skript wurde aktualisiert."})}catch(Ve){const We=((Fe=(be=Ve.response)==null?void 0:be.data)==null?void 0:Fe.error)||Ve.message||"Skript konnte nicht gespeichert werden";r({variant:"error",title:"Speichern fehlgeschlagen",description:We})}finally{ue(!1)}},[B,S,Z,a,r]),Ma=k.useCallback(async()=>{var be,Fe;if(B)try{ue(!0),await O(),r({variant:"info",title:"Standardskript wiederhergestellt",description:"Es wird wieder das Standard-Update-Skript verwendet."})}catch(Ve){const We=((Fe=(be=Ve.response)==null?void 0:be.data)==null?void 0:Fe.error)||Ve.message||"Standardskript konnte nicht wiederhergestellt werden";r({variant:"error",title:"Zurü
Line truncated
|
||||
Dies setzt die Superuser-Einrichtung zurück.`)))try{ft(!0);const We=await h(),rt=Number((We==null?void 0:We.usersRemoved)??0);r({variant:"success",title:"Superuser gelöscht",description:rt>1?`${rt} Konten aus der Superuser-Gruppe wurden entfernt.`:"Superuser-Konto wurde entfernt."}),await rn({silent:!0})}catch(We){if(((be=We.response)==null?void 0:be.status)===404)await rn({silent:!0}),r({variant:"info",title:"Kein Superuser vorhanden",description:"Es ist kein Superuser mehr vorhanden."});else{const Rt=((Ve=(Fe=We.response)==null?void 0:Fe.data)==null?void 0:Ve.error)||We.message||"Superuser konnte nicht gelöscht werden";r({variant:"error",title:"Löschen fehlgeschlagen",description:Rt})}}finally{ft(!1)}},[tt,fe,ae,h,rn,r]),Hf=k.useCallback(async be=>{var rt,Rt;if(!R||Wr===be)return;const Fe=Dr.find(Nt=>Nt.id===be);if(!Fe)return;const Ve=Fe.name||`Endpoint ${Fe.external_id}`,We=`Endpoint "${Ve}" wirklich löschen?${Fe.is_default?`
|
||||
Dieser Endpoint ist aktuell als Standard markiert.`:""}`;if(!(typeof window<"u"&&!window.confirm(We))){Mn(be);try{await l(be),r({variant:"success",title:"Endpoint gelöscht",description:`Endpoint "${Ve}" wurde entfernt.`}),await Ir({silent:!0})}catch(Nt){const Dt=((Rt=(rt=Nt.response)==null?void 0:rt.data)==null?void 0:Rt.error)||Nt.message||"Endpoint konnte nicht gelöscht werden.";r({variant:"error",title:"Endpoint löschen fehlgeschlagen",description:Dt})}finally{Mn(null)}}},[R,l,Wr,Ir,Dr,r]),qf=k.useCallback(async be=>{var Rt,Nt;if(!R||tn===be)return;const Fe=Dn.find(Dt=>Dt.id===be);if(!Fe)return;const Ve=Dr.filter(Dt=>Dt.server_id===be),We=Fe.name||Fe.url||`Server ${be}`,rt=[`Server "${We}" wirklich löschen?`,Ve.length?`Dabei werden ${Ve.length} zugeordnete Endpoint(s) entfernt.`:null,"Das System benötigt anschließend erneut einen gültigen Server/Endpoint im Setup."].filter(Boolean).join(`
|
||||
`);if(!(typeof window<"u"&&!window.confirm(rt))){Tr(be);try{await c(be),r({variant:"success",title:"Server gelöscht",description:`Server "${We}" wurde entfernt.`}),await Ir({silent:!0})}catch(Dt){const yr=((Nt=(Rt=Dt.response)==null?void 0:Rt.data)==null?void 0:Nt.error)||Dt.message||"Server konnte nicht gelöscht werden.";r({variant:"error",title:"Server löschen fehlgeschlagen",description:yr})}finally{Tr(null)}}},[R,c,Ir,tn,Dr,Dn,r]),Gf=k.useCallback((be,Fe)=>{N&&Wt(Ve=>({...Ve,[be]:Fe}))},[N]),Kf=k.useCallback(async be=>{var Ve,We;if(!N)return;const Fe=(Mr[be]??"").trim();if(!Fe){r({variant:"warning",title:"API-Key fehlt",description:"Bitte gib einen API-Key ein."});return}if(Cr!==be){Gt(be);try{await u(be,Fe),r({variant:"success",title:"API-Key aktualisiert",description:"Der API-Key wurde gespeichert."}),Wt(rt=>({...rt,[be]:""})),await Ir({silent:!0})}catch(rt){const Rt=((We=(Ve=rt.response)==null?void 0:Ve.data)==null?void 0:We.error)||rt.message||"API-Key konnte nicht aktualisiert werden.";r({variant:"error",title:"Aktualisierung fehlgeschlagen",description:Rt})}finally{Gt(null)}}},[Mr,Cr,N,Ir,r,u]),Ou=k.useCallback(async be=>{var Fe,Ve;if(E&&!(s||re)&&be!==W){if(q){r({variant:"warning",title:"Aktion nicht möglich",description:"Während eines laufenden Updates kann der Wartungsmodus nicht geändert werden."});return}ge(!0);try{const We=be&&!!$,rt={active:be};We&&z?rt.message=z:rt.message=null,await g(rt),r({variant:be?"info":"success",title:be?"Wartungsmodus aktiviert":"Wartungsmodus deaktiviert",description:be?"Benutzer sehen jetzt die Wartungsseite.":"StackPulse steht wieder zur Verfügung."})}catch(We){const rt=((Ve=(Fe=We.response)==null?void 0:Fe.data)==null?void 0:Ve.error)||We.message||"Wartungsmodus konnte nicht geändert werden";r({variant:"error",title:"Änderung fehlgeschlagen",description:rt})}finally{ge(!1)}}},[E,s,re,W,q,g,z,$,r]),To=k.useCallback(async()=>{var Fe,Ve;if(!L)return;if(W||q){r({variant:"warning",title:"Update nicht möglich",description:"Während eines aktiven Wartungsmodus kann kein weiteres Update gestartet werden."});return}const be=(_t==null?void 0:_t.latestVersion)??(_t==null?void 0:_t.currentVersion)??"unbekannt";if(!(typeof window<"u"&&!window.confirm(`Portainer-Update starten?
|
||||
Zielversion: ${be}.
|
||||
Während des Updates befindet sich StackPulse im Wartungsmodus.
|
||||
Fortfahren?`)))try{he(""),Se(!0),await b(),r({variant:"info",title:"Update gestartet",description:"Das Portainer-Update wurde gestartet."})}catch(We){const rt=((Ve=(Fe=We.response)==null?void 0:Fe.data)==null?void 0:Ve.error)||We.message||"Portainer-Update konnte nicht gestartet werden";he(rt),r({variant:"error",title:"Update fehlgeschlagen",description:rt})}finally{Se(!1)}},[L,W,q,_t,b,r]),vi=k.useCallback(async be=>{var rt,Rt,Nt,Dt;if(!Q||!be)return;if(W||q){r({variant:"warning",title:"Aktion nicht möglich",description:"Während des Wartungsmodus sind Bereinigungen deaktiviert."});return}const Fe=(rt=be.canonical)==null?void 0:rt.Id;if(!Fe)return;const Ve=(be.duplicates||[]).map(yr=>yr.Id).filter(Boolean);if(!Ve.length)return;const We=((Rt=be.canonical)==null?void 0:Rt.Name)||be.name||`Stack ${Fe}`;if(!(typeof window<"u"&&!window.confirm(`Bereinigung für "${We}" starten?
|
||||
Es werden ${Ve.length} Duplikate entfernt: ${Ve.join(", ")}`))){mt(String(Fe));try{const qo=(await nt.post("/api/maintenance/duplicates/cleanup",{canonicalId:Fe,duplicateIds:Ve})).data??{};if(qo.success===!1)throw new Error(qo.error||"Bereinigung fehlgeschlagen");const Xf=Array.isArray(qo.results)?qo.results.filter(Ci=>Ci.status==="deleted").map(Ci=>Ci.id):Ve;r({variant:"success",title:"Bereinigung abgeschlossen",description:`${We} – entfernte IDs: ${Xf.join(", ")}`}),await Co({silent:!0})}catch(yr){const qo=((Dt=(Nt=yr.response)==null?void 0:Nt.data)==null?void 0:Dt.error)||yr.message||"Bereinigung fehlgeschlagen";r({variant:"error",title:"Bereinigung fehlgeschlagen",description:qo})}finally{mt(null)}}},[Q,Co,W,r,q]);n!=null&&n.activatedAt&&Fa(n.activatedAt),n!=null&&n.updatedAt&&Fa(n.updatedAt);const oo=W||q,yi=(o==null?void 0:o.status)??(q?"running":"idle"),Pu=ZB[yi]??yi,ku=ss[yi]??ss.idle,xi=o!=null&&o.stage?JB[o.stage]??o.stage:"–",zl=(o==null?void 0:o.logs)??[],Yf=(o==null?void 0:o.targetVersion)??"-",Eu=(o==null?void 0:o.resultVersion)??"-",wi=o!=null&&o.startedAt?Fa(o.startedAt):"-",Qf=o!=null&&o.finishedAt?Fa(o.finishedAt):"-",Bl=(o==null?void 0:o.message)??(q?"Update läuft…":""),Vl=q||me||s;return y?x.jsxs("div",{className:"mt-12 mb-8 flex flex-col gap-12",children:[(W||q)&&x.jsx("div",{className:"rounded-lg border border-cyan-500/60 bg-cyan-900/30 px-4 py-3 text-sm text-bluegray-100",children:x.jsxs("div",{className:"flex flex-col gap-1",children:[x.jsxs("span",{children:["Wartungsmodus aktiv",z?` – ${z}`:q?" – Portainer-Update läuft":"","."]}),q&&x.jsxs("span",{className:"text-xs text-indigo-900",children:["Phase: ",xi]})]})}),y&&x.jsxs(G.Card,{children:[x.jsx(G.CardHeader,{variant:"gradient",color:"gray",className:"mb-5 p-4",children:x.jsx(G.Typography,{variant:"h6",color:"white",className:"flex items-center justify-between",children:x.jsx("span",{children:"Wartungsmodus"})})}),x.jsxs(G.CardBody,{className:"flex flex-col gap-4 p-4",children:[x.jsxs("div",{className:"flex items-start justify-between gap-4",children:[x.jsx("div",{className:"space-y-1",children:x.jsx("p",{className:"text-sm antialiased font-sans text-sm font-light leading-normal text-inherit",children:"Schaltet StackPulse für Benutzer in den Wartungsmodus."})}),x.jsx(G.Switch,{checked:W,disabled:!E||re||s||q,onChange:be=>Ou(be.target.checked),ripple:!1,color:"amber"})]}),re&&x.jsx("p",{className:"text-xs text-stormGrey-500",children:"Wartungsmodus wird aktualisiert…"})]})]}),P&&x.jsxs(G.Card,{children:[x.jsx(G.CardHeader,{variant:"gradient",color:"gray",className:"mb-5 p-4",children:x.jsx(G.Typography,{variant:"h6",color:"white",className:"flex items-center justify-between",children:x.jsx("span",{children:"Server & Endpoints"})})}),x.jsxs(G.CardBody,{className:"flex flex-col gap-4 p-4",children:[ht&&x.jsx("p",{className:"text-sm text-stormGrey-500",children:"Daten werden geladen…"}),!ht&&er&&x.jsx(Alert,{color:"red",className:"border border-red-200 bg-red-50 text-red-700",children:er}),!ht&&!er&&x.jsxs("div",{className:"flex flex-col gap-6",children:[x.jsxs("div",{className:"flex flex-col gap-3",children:[x.jsxs("div",{className:"flex items-center justify-between",children:[x.jsx(G.Typography,{variant:"small",color:"blue-gray",className:"font-semibold uppercase",children:"Server"}),x.jsxs(G.Typography,{variant:"small",color:"blue-gray",children:[Dn.length," vorhanden"]})]}),Dn.length===0?x.jsx("p",{className:"text-sm text-stormGrey-500",children:"Es sind derzeit keine Server hinterlegt."}):x.jsx("div",{className:"flex flex-col gap-3",children:Dn.map(be=>{const Fe=It.get(be.id)||null,Ve=!!(Fe!=null&&Fe.hasKey),We=Fe!=null&&Fe.updatedAt?new Date(Fe.updatedAt).toLocaleString("de-DE",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"}):null;return x.jsxs("div",{className:"flex flex-col gap-3 rounded-md border border-blue-gray-100 p-3 md:flex-row md:items-start md:justify-between",children:[x.jsxs("div",{className:"flex flex-col gap-1",children:[x.jsx("p",{className:"text-sm font-medium text-stormGrey-900",children:be.name||"Ohne Namen"}),x.jsx("p",{className:"text-xs text-stormGrey-500 break-all",children:be.url}),x.jsxs("p",{className:"text-xs text-stormGrey-500",children:["API-Key: ",Ve?"vorhanden":"nicht hinterlegt",We?` (aktualisiert am ${We})`:""]})]}),x.jsxs("div",{className:"flex w-full flex-col gap-2 md:max-w-xs",children:[x.jsx(G.Input,{type:"password",label:"Neuer API-Key",value:Mr[be.id]??"",onChange:rt=>Gf(be.id,rt.target.value),disabled:!N||ht||Cr===be.id}),x.jsxs("div",{className:"flex items-center gap-2 justify-end",children:[x.jsx(G.Button,{variant:"text",color:"red",size:"sm",onClick:()=>qf(be.id),disabled:!R||ht||tn===be.id||Wr!==null||Cr===be.id,children:tn===be.id?"Lösche…":"Löschen"}),x.jsx(G.Button,{color:"blue",size:"sm",onClick:()=>Kf(be.id),disabled:!N||ht||Cr===be.id,children:Cr===be.id?"Speichere…":"API-Key speichern"})]})]})]},be.id)})})]}),x.jsxs("div",{cl
Line truncated
|
||||
`)},xV=(e=[])=>{if(!Array.isArray(e))return[];const t=[];for(let r=0;r<e.length;r+=4)t.push(e.slice(r,r+4));return t};function wV(){const e=yo(),{user:t,setSession:r,refreshSession:n}=mn(),[o,a]=k.useState(!0),[i,s]=k.useState(""),[f,g]=k.useState([]),[b,S]=k.useState(!1),[O,T]=k.useState(!1),C=!!(t!=null&&t.requiresSecurityPhraseDownload),w=k.useMemo(()=>xV(f),[f]),m=k.useCallback(u=>{if(!u)return;const h=new Blob([u],{type:"text/plain;charset=utf-8"}),v=URL.createObjectURL(h),y=document.createElement("a");y.href=v;const E=t!=null&&t.username?`${t.username}-sicherheitsschluessel`:"security-phrase";y.download=`${E}.txt`,document.body.appendChild(y),y.click(),document.body.removeChild(y),URL.revokeObjectURL(v)},[t==null?void 0:t.username]),d=k.useCallback(async()=>{var u,h,v,y,E,P,N;a(!0),s("");try{const R=await nt.get("/api/users/me/security-phrase"),M=Array.isArray((h=(u=R.data)==null?void 0:u.item)==null?void 0:h.words)?R.data.item.words:[];if(!M.length)throw new Error("EMPTY_SECURITY_PHRASE");g(M),T(!1)}catch(R){if(((v=R==null?void 0:R.response)==null?void 0:v.status)===409||((E=(y=R==null?void 0:R.response)==null?void 0:y.data)==null?void 0:E.error)==="SECURITY_PHRASE_ALREADY_DOWNLOADED"){await n(),e("/dashboard/stacks",{replace:!0});return}const M=((N=(P=R==null?void 0:R.response)==null?void 0:P.data)==null?void 0:N.error)||(R==null?void 0:R.message)||"Sicherheitsschlüssel konnte nicht geladen werden.";s(M)}finally{a(!1)}},[e,n]);k.useEffect(()=>{if(!t){e("/auth/sign-in",{replace:!0});return}if(!C){e("/dashboard/stacks",{replace:!0});return}d()},[t,C,d,e]);const l=k.useCallback(()=>{d()},[d]),c=k.useCallback(async()=>{var u,h,v,y,E;if(!(b||!Array.isArray(f)||f.length===0)){S(!0),s("");try{const P=yV(f);m(P);const N=await nt.post("/api/users/me/security-phrase/downloaded"),R=((h=(u=N.data)==null?void 0:u.item)==null?void 0:h.downloadedAt)||null,M=(v=N.data)==null?void 0:v.session;M!=null&&M.user?r(M):await n(),T(!!R),e("/dashboard/stacks",{replace:!0})}catch(P){const N=((E=(y=P==null?void 0:P.response)==null?void 0:y.data)==null?void 0:E.error)||(P==null?void 0:P.message)||"Download konnte nicht bestätigt werden.";s(N)}finally{S(!1)}}},[f,b,m,n,e,r]);return C?x.jsxs("div",{className:"flex min-h-screen flex-col items-center justify-center bg-blue-gray-50/50 px-4 py-10",children:[x.jsxs("div",{className:"mb-6 text-center",children:[x.jsx(G.Typography,{variant:"h2",className:"font-semibold text-blue-gray-800",children:"Sicherheitsschlüssel sichern"}),x.jsx(G.Typography,{color:"blue-gray",className:"mt-2 max-w-3xl text-base",children:"Bitte lade den einmaligen Sicherheitsschlüssel herunter und bewahre ihn sicher auf. Er wird benötigt, um dein Passwort zurückzusetzen. Ohne den Download kannst du die Anwendung nicht weiter nutzen."})]}),x.jsx(G.Card,{className:"w-full max-w-3xl shadow-lg",children:x.jsxs(G.CardBody,{className:"space-y-6",children:[o&&x.jsx("div",{className:"flex items-center justify-center py-10",children:x.jsx(G.Spinner,{className:"h-10 w-10 text-blue-gray-500"})}),!o&&i&&x.jsx(G.Alert,{color:"red",className:"border border-red-200 bg-red-50 text-red-700",icon:x.jsx(Dz,{className:"h-5 w-5"}),children:x.jsxs("div",{className:"flex items-start justify-between gap-4",children:[x.jsxs("div",{children:[x.jsx(G.Typography,{variant:"h6",color:"red",children:"Fehler"}),x.jsx(G.Typography,{color:"red",className:"text-sm",children:i})]}),x.jsx(G.Button,{size:"sm",color:"red",variant:"text",onClick:l,className:"normal-case",children:"Erneut versuchen"})]})}),!o&&!i&&x.jsxs(x.Fragment,{children:[x.jsxs("div",{className:"rounded-lg border border-blue-gray-100 bg-blue-gray-50/60 p-6",children:[x.jsx(G.Typography,{variant:"h5",className:"mb-4 text-blue-gray-800",children:"Deine Sicherheitsphrase"}),x.jsx("div",{className:"space-y-3",children:w.map((u,h)=>x.jsx("div",{className:"flex flex-wrap items-center justify-center gap-4 text-lg font-mono tracking-wide text-blue-gray-900 md:text-xl",children:u.map((v,y)=>x.jsx("span",{className:"uppercase",children:v},`phrase-${h}-${y}`))},`phrase-row-${h}`))})]}),x.jsxs("div",{className:"flex flex-col items-center justify-between gap-4 md:flex-row",children:[x.jsx("div",{className:"text-sm text-blue-gray-600",children:"Der Download startet im TXT-Format. Bewahre die Datei sicher auf – sie kann später nicht erneut angezeigt werden."}),x.jsxs(G.Button,{color:"blue",size:"md",className:"flex items-center gap-2 normal-case",onClick:c,disabled:b||O||f.length===0,children:[x.jsx(Az,{className:"h-5 w-5"}),"Sicherheitsschlüssel herunterladen"]})]})]})]})})]}):null}const _V="/assets/pattern-68ea7609.png";function SV(){const e=yo(),{setSession:t,refreshSession:r}=mn(),[n,o]=k.useState(""),[a,i]=k.useState(""),[s,f]=k.useState(!1),[g,b]=k.useState(null),[S,O]=k.useState(!1);k.useEffect(()=>{let C=!0;const w=new AbortController;return(async()=>{try{const d=await fetch("/api/setup/status",{credentials:"include",signal:w.signal});if(d.ok&&!(await d.json()).setupComplete&
Line truncated
|
||||
@@ -34,8 +34,8 @@
|
||||
<script defer data-site="YOUR_DOMAIN_HERE" src="https://api.nepcha.com/js/nepcha-analytics.js"></script>
|
||||
|
||||
|
||||
<script type="module" crossorigin src="/assets/index-8d2b4f50.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-11780ccd.css">
|
||||
<script type="module" crossorigin src="/assets/index-f91011a0.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-f6cd1c87.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
+385
-6
@@ -1,5 +1,12 @@
|
||||
import { db } from '../db/index.js';
|
||||
import { logEvent } from '../logging/eventLogs.js';
|
||||
import {
|
||||
generateSecurityPhraseWords,
|
||||
encryptSecurityPhrase,
|
||||
decryptSecurityPhrase,
|
||||
canonicalizeWords,
|
||||
canonicalizePhraseInput
|
||||
} from './securityPhrase.js';
|
||||
import {
|
||||
hashPassword,
|
||||
normalizeAvatarColor,
|
||||
@@ -18,6 +25,7 @@ const selectUsersWithGroups = db.prepare(`
|
||||
u.last_login,
|
||||
u.created_at,
|
||||
u.updated_at,
|
||||
u.security_phrase_downloaded_at,
|
||||
GROUP_CONCAT(g.id || '::' || g.name, '|||') AS group_pairs
|
||||
FROM users u
|
||||
LEFT JOIN user_group_memberships m ON m.user_id = u.id
|
||||
@@ -36,6 +44,10 @@ const selectUserWithGroupsById = db.prepare(`
|
||||
u.last_login,
|
||||
u.created_at,
|
||||
u.updated_at,
|
||||
u.security_phrase_content,
|
||||
u.security_phrase_iv,
|
||||
u.security_phrase_tag,
|
||||
u.security_phrase_downloaded_at,
|
||||
GROUP_CONCAT(g.id || '::' || g.name, '|||') AS group_pairs
|
||||
FROM users u
|
||||
LEFT JOIN user_group_memberships m ON m.user_id = u.id
|
||||
@@ -63,6 +75,20 @@ const insertMembershipForUser = db.prepare(`
|
||||
VALUES (?, ?)
|
||||
`);
|
||||
|
||||
const selectSecurityPhraseByUsername = db.prepare(`
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
is_active,
|
||||
security_phrase_content AS content,
|
||||
security_phrase_iv AS iv,
|
||||
security_phrase_tag AS tag,
|
||||
security_phrase_downloaded_at AS downloaded_at
|
||||
FROM users
|
||||
WHERE lower(username) = lower(?)
|
||||
LIMIT 1
|
||||
`);
|
||||
|
||||
const selectGroupById = db.prepare('SELECT id, name FROM user_groups WHERE id = ?');
|
||||
const selectGroupIdByName = db.prepare('SELECT id FROM user_groups WHERE lower(name) = lower(?) LIMIT 1');
|
||||
|
||||
@@ -75,8 +101,21 @@ const isUserInGroupStatement = db.prepare(`
|
||||
`);
|
||||
|
||||
const insertUserStatement = db.prepare(`
|
||||
INSERT INTO users (username, email, password_hash, password_salt, avatar_color, is_active, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
INSERT INTO users (
|
||||
username,
|
||||
email,
|
||||
password_hash,
|
||||
password_salt,
|
||||
avatar_color,
|
||||
is_active,
|
||||
security_phrase_content,
|
||||
security_phrase_iv,
|
||||
security_phrase_tag,
|
||||
security_phrase_downloaded_at,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, 1, ?, ?, ?, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||
`);
|
||||
|
||||
const updateUserCoreStatement = db.prepare(`
|
||||
@@ -97,6 +136,14 @@ const updateUserActiveStatement = db.prepare(`
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
const updateUserPasswordStatement = db.prepare(`
|
||||
UPDATE users
|
||||
SET password_hash = ?,
|
||||
password_salt = ?,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
const parseGroupPairs = (rawPairs) => {
|
||||
if (!rawPairs) {
|
||||
return [];
|
||||
@@ -127,7 +174,8 @@ const sanitizeUserRecord = (row) => ({
|
||||
lastLogin: row.last_login || null,
|
||||
createdAt: row.created_at || null,
|
||||
updatedAt: row.updated_at || null,
|
||||
groups: parseGroupPairs(row.group_pairs)
|
||||
groups: parseGroupPairs(row.group_pairs),
|
||||
securityPhraseDownloadedAt: row.security_phrase_downloaded_at || null
|
||||
});
|
||||
|
||||
export function listUsers() {
|
||||
@@ -147,13 +195,76 @@ const applyUserGroupAssignments = db.transaction((userId, groupIds) => {
|
||||
});
|
||||
});
|
||||
|
||||
const insertUserWithGroups = db.transaction(({ username, email, passwordHash, passwordSalt, avatarColor, groupId }) => {
|
||||
const result = insertUserStatement.run(username, email, passwordHash, passwordSalt, avatarColor);
|
||||
const insertUserWithGroups = db.transaction(({
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
passwordSalt,
|
||||
avatarColor,
|
||||
groupId,
|
||||
securityPhrase
|
||||
}) => {
|
||||
const phraseToPersist = securityPhrase && typeof securityPhrase === 'object'
|
||||
? {
|
||||
content: securityPhrase.content ?? null,
|
||||
iv: securityPhrase.iv ?? null,
|
||||
tag: securityPhrase.tag ?? null
|
||||
}
|
||||
: { content: null, iv: null, tag: null };
|
||||
|
||||
const result = insertUserStatement.run(
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
passwordSalt,
|
||||
avatarColor,
|
||||
phraseToPersist.content,
|
||||
phraseToPersist.iv,
|
||||
phraseToPersist.tag
|
||||
);
|
||||
const userId = Number(result.lastInsertRowid);
|
||||
applyUserGroupAssignments(userId, [groupId]);
|
||||
return userId;
|
||||
});
|
||||
|
||||
const selectSecurityPhraseByUserId = db.prepare(`
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
security_phrase_content AS content,
|
||||
security_phrase_iv AS iv,
|
||||
security_phrase_tag AS tag,
|
||||
security_phrase_downloaded_at AS downloaded_at
|
||||
FROM users
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
const updateSecurityPhraseStatement = db.prepare(`
|
||||
UPDATE users
|
||||
SET
|
||||
security_phrase_content = ?,
|
||||
security_phrase_iv = ?,
|
||||
security_phrase_tag = ?,
|
||||
security_phrase_downloaded_at = NULL,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
const markSecurityPhraseDownloadedStatement = db.prepare(`
|
||||
UPDATE users
|
||||
SET security_phrase_downloaded_at = COALESCE(security_phrase_downloaded_at, CURRENT_TIMESTAMP),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`);
|
||||
|
||||
const selectUsersMissingSecurityPhraseStatement = db.prepare(`
|
||||
SELECT id
|
||||
FROM users
|
||||
WHERE security_phrase_content IS NULL
|
||||
OR security_phrase_iv IS NULL
|
||||
OR security_phrase_tag IS NULL
|
||||
`);
|
||||
|
||||
const resolveActorFields = (actor) => {
|
||||
if (!actor || actor.id === undefined || actor.id === null) {
|
||||
return {};
|
||||
@@ -175,6 +286,265 @@ const normalizeEmail = (value) => {
|
||||
return trimmed.toLowerCase();
|
||||
};
|
||||
|
||||
export function getUserSecurityPhrase(userId, options = {}) {
|
||||
const numericUserId = Number(userId);
|
||||
if (!Number.isFinite(numericUserId) || numericUserId <= 0) {
|
||||
const error = new Error('INVALID_USER_ID');
|
||||
error.code = 'INVALID_USER_ID';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const { actor = null, allowAutoGenerate = true } = options;
|
||||
|
||||
const record = selectSecurityPhraseByUserId.get(numericUserId);
|
||||
if (!record) {
|
||||
const error = new Error('USER_NOT_FOUND');
|
||||
error.code = 'USER_NOT_FOUND';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const words = decryptSecurityPhrase(record);
|
||||
|
||||
if (allowAutoGenerate && (!Array.isArray(words) || words.length === 0)) {
|
||||
return renewUserSecurityPhrase(numericUserId, { actor, suppressLog: true });
|
||||
}
|
||||
|
||||
return {
|
||||
userId: numericUserId,
|
||||
username: record.username,
|
||||
words,
|
||||
downloadedAt: record.downloaded_at || null
|
||||
};
|
||||
}
|
||||
|
||||
export function renewUserSecurityPhrase(userId, options = {}) {
|
||||
const actor = options.actor || null;
|
||||
const suppressLog = Boolean(options.suppressLog);
|
||||
const numericUserId = Number(userId);
|
||||
if (!Number.isFinite(numericUserId) || numericUserId <= 0) {
|
||||
const error = new Error('INVALID_USER_ID');
|
||||
error.code = 'INVALID_USER_ID';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const existingUser = selectUserCredentialsById.get(numericUserId);
|
||||
if (!existingUser) {
|
||||
const error = new Error('USER_NOT_FOUND');
|
||||
error.code = 'USER_NOT_FOUND';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const previousPhraseRecord = selectSecurityPhraseByUserId.get(numericUserId);
|
||||
|
||||
const words = generateSecurityPhraseWords(8);
|
||||
const { encrypted: encryptedPhrase } = encryptSecurityPhrase(words);
|
||||
|
||||
updateSecurityPhraseStatement.run(
|
||||
encryptedPhrase.content,
|
||||
encryptedPhrase.iv,
|
||||
encryptedPhrase.tag,
|
||||
numericUserId
|
||||
);
|
||||
|
||||
if (!suppressLog) {
|
||||
logEvent({
|
||||
category: 'benutzer',
|
||||
eventType: 'benutzer-sicherheitsschluessel-erneuert',
|
||||
action: 'sicherheitsschluessel-erneuern',
|
||||
status: 'erfolgreich',
|
||||
entityType: 'benutzer',
|
||||
entityId: String(numericUserId),
|
||||
entityName: existingUser.username ?? `ID ${numericUserId}`,
|
||||
message: `Sicherheitsschlüssel für Benutzer "${existingUser.username ?? numericUserId}" erneuert`,
|
||||
metadata: {
|
||||
previousDownloadedAt: previousPhraseRecord?.downloaded_at ?? null
|
||||
},
|
||||
...resolveActorFields(actor)
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
userId: numericUserId,
|
||||
username: existingUser.username,
|
||||
words,
|
||||
downloadedAt: null
|
||||
};
|
||||
}
|
||||
|
||||
export function markSecurityPhraseDownloaded(userId, options = {}) {
|
||||
const actor = options.actor || null;
|
||||
const numericUserId = Number(userId);
|
||||
if (!Number.isFinite(numericUserId) || numericUserId <= 0) {
|
||||
const error = new Error('INVALID_USER_ID');
|
||||
error.code = 'INVALID_USER_ID';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const record = selectSecurityPhraseByUserId.get(numericUserId);
|
||||
if (!record) {
|
||||
const error = new Error('USER_NOT_FOUND');
|
||||
error.code = 'USER_NOT_FOUND';
|
||||
throw error;
|
||||
}
|
||||
|
||||
markSecurityPhraseDownloadedStatement.run(numericUserId);
|
||||
|
||||
const updatedRecord = selectSecurityPhraseByUserId.get(numericUserId) || record;
|
||||
const downloadedAt = updatedRecord?.downloaded_at || null;
|
||||
|
||||
logEvent({
|
||||
category: 'benutzer',
|
||||
eventType: 'benutzer-sicherheitsschluessel-heruntergeladen',
|
||||
action: 'sicherheitsschluessel-herunterladen',
|
||||
status: 'erfolgreich',
|
||||
entityType: 'benutzer',
|
||||
entityId: String(numericUserId),
|
||||
entityName: record.username ?? `ID ${numericUserId}`,
|
||||
message: `Sicherheitsschlüssel für Benutzer "${record.username ?? numericUserId}" heruntergeladen`,
|
||||
metadata: {
|
||||
downloadedAt
|
||||
},
|
||||
...resolveActorFields(actor)
|
||||
});
|
||||
|
||||
return {
|
||||
userId: numericUserId,
|
||||
username: record.username,
|
||||
downloadedAt
|
||||
};
|
||||
}
|
||||
|
||||
export function verifySecurityPhraseForUsername(username, phraseInput) {
|
||||
const normalizedUsername = typeof username === 'string' ? username.trim() : '';
|
||||
if (!normalizedUsername) {
|
||||
const error = new Error('USERNAME_REQUIRED');
|
||||
error.code = 'USERNAME_REQUIRED';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const record = selectSecurityPhraseByUsername.get(normalizedUsername);
|
||||
if (!record) {
|
||||
const error = new Error('USER_NOT_FOUND');
|
||||
error.code = 'USER_NOT_FOUND';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const candidateCanonical = canonicalizePhraseInput(phraseInput);
|
||||
if (!candidateCanonical) {
|
||||
const error = new Error('PHRASE_REQUIRED');
|
||||
error.code = 'PHRASE_REQUIRED';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const words = decryptSecurityPhrase(record);
|
||||
if (!Array.isArray(words) || words.length === 0) {
|
||||
const error = new Error('PHRASE_NOT_INITIALIZED');
|
||||
error.code = 'PHRASE_NOT_INITIALIZED';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const storedCanonical = canonicalizeWords(words);
|
||||
if (!storedCanonical) {
|
||||
const error = new Error('PHRASE_NOT_INITIALIZED');
|
||||
error.code = 'PHRASE_NOT_INITIALIZED';
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (storedCanonical !== candidateCanonical) {
|
||||
const error = new Error('PHRASE_MISMATCH');
|
||||
error.code = 'PHRASE_MISMATCH';
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
userId: record.id,
|
||||
username: record.username,
|
||||
isActive: Boolean(record.is_active),
|
||||
words
|
||||
};
|
||||
}
|
||||
|
||||
export function setUserPassword(userId, newPassword, options = {}) {
|
||||
const actor = options.actor || null;
|
||||
const resetMethod = options.resetMethod || 'security-phrase';
|
||||
const numericUserId = Number(userId);
|
||||
if (!Number.isFinite(numericUserId) || numericUserId <= 0) {
|
||||
const error = new Error('INVALID_USER_ID');
|
||||
error.code = 'INVALID_USER_ID';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const existingUser = selectUserCredentialsById.get(numericUserId);
|
||||
if (!existingUser) {
|
||||
const error = new Error('USER_NOT_FOUND');
|
||||
error.code = 'USER_NOT_FOUND';
|
||||
throw error;
|
||||
}
|
||||
|
||||
let passwordHash;
|
||||
let passwordSalt;
|
||||
try {
|
||||
const hashed = hashPassword(newPassword);
|
||||
passwordHash = hashed.hash;
|
||||
passwordSalt = hashed.salt;
|
||||
} catch (err) {
|
||||
if (err && err.code) {
|
||||
throw err;
|
||||
}
|
||||
const error = new Error('INVALID_PASSWORD');
|
||||
error.code = 'INVALID_PASSWORD';
|
||||
throw error;
|
||||
}
|
||||
|
||||
updateUserPasswordStatement.run(passwordHash, passwordSalt, numericUserId);
|
||||
const updated = getUserById(numericUserId);
|
||||
|
||||
logEvent({
|
||||
category: 'benutzer',
|
||||
eventType: 'benutzer-passwort-zurueckgesetzt',
|
||||
action: 'passwort-zuruecksetzen',
|
||||
status: 'erfolgreich',
|
||||
entityType: 'benutzer',
|
||||
entityId: String(numericUserId),
|
||||
entityName: updated?.username ?? existingUser.username ?? `ID ${numericUserId}`,
|
||||
message: `Passwort für Benutzer "${updated?.username ?? existingUser.username ?? numericUserId}" zurückgesetzt`,
|
||||
metadata: {
|
||||
resetMethod
|
||||
},
|
||||
...resolveActorFields(actor)
|
||||
});
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
export function ensureSecurityPhrasesForExistingUsers() {
|
||||
const rows = selectUsersMissingSecurityPhraseStatement.all();
|
||||
if (!Array.isArray(rows) || rows.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let processed = 0;
|
||||
const initialize = db.transaction((users) => {
|
||||
users.forEach((entry) => {
|
||||
if (!entry || entry.id === undefined || entry.id === null) {
|
||||
return;
|
||||
}
|
||||
const words = generateSecurityPhraseWords(8);
|
||||
const { encrypted: encryptedPhrase } = encryptSecurityPhrase(words);
|
||||
updateSecurityPhraseStatement.run(
|
||||
encryptedPhrase.content,
|
||||
encryptedPhrase.iv,
|
||||
encryptedPhrase.tag,
|
||||
entry.id
|
||||
);
|
||||
processed += 1;
|
||||
});
|
||||
});
|
||||
|
||||
initialize(rows);
|
||||
return processed;
|
||||
}
|
||||
|
||||
export function createUser({ username, email, password, groupId, avatarColor }, options = {}) {
|
||||
const actor = options.actor || null;
|
||||
const normalizedUsername = typeof username === 'string' ? username.trim() : '';
|
||||
@@ -245,13 +615,22 @@ export function createUser({ username, email, password, groupId, avatarColor },
|
||||
const normalizedAvatarColor = normalizeAvatarColor(avatarColor);
|
||||
const avatarColorToPersist = normalizedAvatarColor || pickRandomAvatarColor() || DEFAULT_AVATAR_COLOR;
|
||||
|
||||
const securityPhraseWords = generateSecurityPhraseWords(8);
|
||||
const { encrypted: encryptedPhrase } = encryptSecurityPhrase(securityPhraseWords);
|
||||
const securityPhrasePayload = {
|
||||
content: encryptedPhrase.content,
|
||||
iv: encryptedPhrase.iv,
|
||||
tag: encryptedPhrase.tag
|
||||
};
|
||||
|
||||
const userId = insertUserWithGroups({
|
||||
username: normalizedUsername,
|
||||
email: normalizedEmail,
|
||||
passwordHash,
|
||||
passwordSalt,
|
||||
avatarColor: avatarColorToPersist,
|
||||
groupId: numericGroupId
|
||||
groupId: numericGroupId,
|
||||
securityPhrase: securityPhrasePayload
|
||||
});
|
||||
|
||||
const userRecord = getUserById(userId);
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import crypto from 'crypto';
|
||||
import { wordlist as englishWordlist } from '@scure/bip39/wordlists/english.js';
|
||||
|
||||
const FALLBACK_SECRET = 'stackpulse-security-phrase-secret';
|
||||
|
||||
const WORD_LIST = englishWordlist;
|
||||
|
||||
|
||||
const SECURITY_PHRASE_KEY = crypto
|
||||
.createHash('sha256')
|
||||
.update(String(process.env.SECURITY_PHRASE_SECRET || FALLBACK_SECRET))
|
||||
.digest();
|
||||
|
||||
const encryptValue = (value) => {
|
||||
const iv = crypto.randomBytes(12);
|
||||
const cipher = crypto.createCipheriv('aes-256-gcm', SECURITY_PHRASE_KEY, iv);
|
||||
const encrypted = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()]);
|
||||
const tag = cipher.getAuthTag();
|
||||
return {
|
||||
iv: iv.toString('base64'),
|
||||
content: encrypted.toString('base64'),
|
||||
tag: tag.toString('base64')
|
||||
};
|
||||
};
|
||||
|
||||
const decryptValue = ({ content, iv, tag }) => {
|
||||
if (!content || !iv || !tag) {
|
||||
return '';
|
||||
}
|
||||
const decipher = crypto.createDecipheriv('aes-256-gcm', SECURITY_PHRASE_KEY, Buffer.from(iv, 'base64'));
|
||||
decipher.setAuthTag(Buffer.from(tag, 'base64'));
|
||||
const decrypted = Buffer.concat([
|
||||
decipher.update(Buffer.from(content, 'base64')),
|
||||
decipher.final()
|
||||
]);
|
||||
return decrypted.toString('utf8');
|
||||
};
|
||||
|
||||
export const generateSecurityPhraseWords = (count = 8) => {
|
||||
const words = [];
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
const index = crypto.randomInt(0, WORD_LIST.length);
|
||||
words.push(WORD_LIST[index]);
|
||||
}
|
||||
return words;
|
||||
};
|
||||
|
||||
export const encodeWords = (words = []) => {
|
||||
return words
|
||||
.map((word) => {
|
||||
const index = WORD_LIST.indexOf(word);
|
||||
if (index === -1) {
|
||||
throw new Error(`SECURITY_PHRASE_WORD_UNKNOWN:${word}`);
|
||||
}
|
||||
return String(index);
|
||||
})
|
||||
.join('-');
|
||||
};
|
||||
|
||||
export const decodeWords = (encoded) => {
|
||||
if (!encoded) {
|
||||
return [];
|
||||
}
|
||||
return encoded.split('-').map((entry) => {
|
||||
const index = Number(entry);
|
||||
if (!Number.isFinite(index) || index < 0 || index >= WORD_LIST.length) {
|
||||
throw new Error('SECURITY_PHRASE_DECODE_FAILED');
|
||||
}
|
||||
return WORD_LIST[index];
|
||||
});
|
||||
};
|
||||
|
||||
export const encryptSecurityPhrase = (words) => {
|
||||
const encoded = encodeWords(words);
|
||||
const encrypted = encryptValue(encoded);
|
||||
return {
|
||||
encrypted,
|
||||
encoded
|
||||
};
|
||||
};
|
||||
|
||||
export const decryptSecurityPhrase = (record) => {
|
||||
if (!record) {
|
||||
return [];
|
||||
}
|
||||
const encoded = decryptValue(record);
|
||||
if (!encoded) {
|
||||
return [];
|
||||
}
|
||||
return decodeWords(encoded);
|
||||
};
|
||||
|
||||
export const formatPhraseForDisplay = (words) => {
|
||||
const rows = [];
|
||||
for (let i = 0; i < words.length; i += 4) {
|
||||
rows.push(words.slice(i, i + 4));
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
const normalizeWord = (value) => {
|
||||
if (!value && value !== 0) {
|
||||
return '';
|
||||
}
|
||||
return String(value).trim().toLowerCase();
|
||||
};
|
||||
|
||||
export const canonicalizeWords = (words = []) => {
|
||||
return words
|
||||
.map(normalizeWord)
|
||||
.filter((word) => word.length > 0)
|
||||
.join('');
|
||||
};
|
||||
|
||||
export const extractWordsFromString = (input) => {
|
||||
if (!input && input !== 0) {
|
||||
return [];
|
||||
}
|
||||
const normalized = String(input)
|
||||
.replace(/[\r\n\t]+/g, ' ')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (!normalized) {
|
||||
return [];
|
||||
}
|
||||
return normalized.split(/\s+/).filter(Boolean);
|
||||
};
|
||||
|
||||
export const canonicalizePhraseInput = (input) => {
|
||||
if (Array.isArray(input)) {
|
||||
return canonicalizeWords(input);
|
||||
}
|
||||
return canonicalizeWords(extractWordsFromString(input));
|
||||
};
|
||||
|
||||
export default WORD_LIST;
|
||||
@@ -0,0 +1,172 @@
|
||||
import React, { createContext, useCallback, useContext, useMemo, useState } from "react";
|
||||
|
||||
const LEVEL_PRIORITY = {
|
||||
none: 0,
|
||||
read: 1,
|
||||
full: 2,
|
||||
};
|
||||
|
||||
const AuthContext = createContext(null);
|
||||
AuthContext.displayName = "AuthContext";
|
||||
|
||||
const normalizePermissions = (raw) => {
|
||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
||||
return {};
|
||||
}
|
||||
return Object.entries(raw).reduce((acc, [key, value]) => {
|
||||
if (typeof key !== "string" || !key) {
|
||||
return acc;
|
||||
}
|
||||
const normalizedValue = typeof value === "string" ? value : String(value ?? "").trim();
|
||||
acc[key] = normalizedValue || "none";
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export function AuthProvider({ children }) {
|
||||
const [state, setState] = useState({
|
||||
user: null,
|
||||
permissions: {},
|
||||
loading: false,
|
||||
error: null,
|
||||
lastErrorCode: null,
|
||||
initialized: false,
|
||||
});
|
||||
|
||||
const setSession = useCallback((payload) => {
|
||||
const user = payload?.user ?? null;
|
||||
const permissions = normalizePermissions(payload?.permissions);
|
||||
setState({
|
||||
user,
|
||||
permissions,
|
||||
loading: false,
|
||||
error: null,
|
||||
lastErrorCode: null,
|
||||
initialized: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const clearSession = useCallback(() => {
|
||||
setState((prev) => ({
|
||||
user: null,
|
||||
permissions: {},
|
||||
loading: false,
|
||||
error: null,
|
||||
lastErrorCode: null,
|
||||
initialized: prev.initialized || true,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const refreshSession = useCallback(async () => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
loading: true,
|
||||
error: null,
|
||||
lastErrorCode: null,
|
||||
}));
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/session", {
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
|
||||
if (!response.ok) {
|
||||
setState({
|
||||
user: null,
|
||||
permissions: {},
|
||||
loading: false,
|
||||
error: payload?.error ?? null,
|
||||
lastErrorCode: payload?.error ?? null,
|
||||
initialized: true,
|
||||
});
|
||||
return { ok: false, status: response.status, error: payload?.error ?? null };
|
||||
}
|
||||
|
||||
setSession(payload);
|
||||
return { ok: true, data: payload };
|
||||
} catch (err) {
|
||||
const message = err?.message || "NETWORK_ERROR";
|
||||
setState({
|
||||
user: null,
|
||||
permissions: {},
|
||||
loading: false,
|
||||
error: message,
|
||||
lastErrorCode: "NETWORK_ERROR",
|
||||
initialized: true,
|
||||
});
|
||||
return { ok: false, error: message };
|
||||
}
|
||||
}, [setSession]);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
let capturedError = null;
|
||||
try {
|
||||
const response = await fetch("/api/auth/logout", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
if (!response.ok) {
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
const logoutError = new Error(payload?.error || "LOGOUT_FAILED");
|
||||
logoutError.code = payload?.error || `STATUS_${response.status}`;
|
||||
throw logoutError;
|
||||
}
|
||||
} catch (err) {
|
||||
capturedError = err;
|
||||
} finally {
|
||||
clearSession();
|
||||
}
|
||||
if (capturedError) {
|
||||
throw capturedError;
|
||||
}
|
||||
}, [clearSession]);
|
||||
|
||||
const hasPermission = useCallback(
|
||||
(permissionKey, requiredLevel = "full") => {
|
||||
if (!permissionKey) {
|
||||
return true;
|
||||
}
|
||||
if (state.user?.isSuperuser) {
|
||||
return true;
|
||||
}
|
||||
const current = state.permissions?.[permissionKey] ?? "none";
|
||||
const currentPriority = LEVEL_PRIORITY[current] ?? 0;
|
||||
const requiredPriority = LEVEL_PRIORITY[requiredLevel] ?? LEVEL_PRIORITY.full;
|
||||
return currentPriority >= requiredPriority;
|
||||
},
|
||||
[state.permissions, state.user]
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
user: state.user,
|
||||
permissions: state.permissions,
|
||||
loading: state.loading,
|
||||
error: state.error,
|
||||
lastErrorCode: state.lastErrorCode,
|
||||
initialized: state.initialized,
|
||||
isAuthenticated: Boolean(state.user),
|
||||
refreshSession,
|
||||
setSession,
|
||||
clearSession,
|
||||
logout,
|
||||
hasPermission,
|
||||
}),
|
||||
[state, refreshSession, setSession, clearSession, logout, hasPermission]
|
||||
);
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error("useAuth must be used within an AuthProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export const AuthContextDisplayName = "/src/components/AuthProvider.jsx";
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Typography, Card, CardBody } from "@material-tailwind/react";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
|
||||
export function PermissionGate({
|
||||
permission,
|
||||
requiredLevel = "full",
|
||||
loadingFallback = null,
|
||||
children,
|
||||
}) {
|
||||
const { initialized, loading, hasPermission } = useAuth();
|
||||
|
||||
if (!permission) {
|
||||
return children;
|
||||
}
|
||||
|
||||
if (!initialized || loading) {
|
||||
return (
|
||||
loadingFallback ?? (
|
||||
<div className="flex min-h-[200px] items-center justify-center">
|
||||
<Typography variant="small" color="blue-gray">
|
||||
Berechtigungen werden geladen …
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (hasPermission(permission, requiredLevel)) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-12 flex items-center justify-center px-4">
|
||||
<Card className="max-w-xl border border-blue-gray-100 shadow-sm">
|
||||
<CardBody className="space-y-3 text-center">
|
||||
<Typography variant="h5" color="blue-gray">
|
||||
Kein Zugriff auf diesen Bereich
|
||||
</Typography>
|
||||
<Typography variant="small" className="text-blue-gray-500">
|
||||
Für diese Funktion wird die Berechtigung <code>{permission}</code> benötigt.
|
||||
</Typography>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PermissionGate.propTypes = {
|
||||
permission: PropTypes.string,
|
||||
requiredLevel: PropTypes.string,
|
||||
loadingFallback: PropTypes.node,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default PermissionGate;
|
||||
|
||||
@@ -11,7 +11,10 @@ import {
|
||||
import routes from "@/routes";
|
||||
import { UserDetails } from "@/pages/dashboard/userDetails.jsx";
|
||||
import { UserGroupDetail } from "@/pages/dashboard/userGroupDetail.jsx";
|
||||
import { SecurityPhrase } from "@/pages/dashboard/securityPhrase.jsx";
|
||||
import { useMaterialTailwindController, setOpenConfigurator } from "@/components";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
import PermissionGate from "@/components/PermissionGate.jsx";
|
||||
|
||||
export function Dashboard() {
|
||||
const [controller, dispatch] = useMaterialTailwindController();
|
||||
@@ -20,8 +23,13 @@ export function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
const [setupChecked, setSetupChecked] = useState(false);
|
||||
const [setupIncomplete, setSetupIncomplete] = useState(true);
|
||||
const [authChecked, setAuthChecked] = useState(false);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const {
|
||||
initialized: authInitialized,
|
||||
loading: authLoading,
|
||||
isAuthenticated,
|
||||
refreshSession,
|
||||
user
|
||||
} = useAuth();
|
||||
|
||||
const checkSetupStatus = useCallback(async () => {
|
||||
setSetupChecked(false);
|
||||
@@ -40,37 +48,15 @@ export function Dashboard() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const checkSession = useCallback(async () => {
|
||||
setAuthChecked(false);
|
||||
try {
|
||||
const response = await fetch("/api/auth/session", { credentials: "include" });
|
||||
if (response.status === 403) {
|
||||
setSetupIncomplete(true);
|
||||
setIsAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
if (!response.ok) {
|
||||
setIsAuthenticated(false);
|
||||
return;
|
||||
}
|
||||
const data = await response.json();
|
||||
setIsAuthenticated(Boolean(data?.user));
|
||||
} catch (error) {
|
||||
console.error("⚠️ [Auth] Sessionprüfung fehlgeschlagen:", error);
|
||||
setIsAuthenticated(false);
|
||||
} finally {
|
||||
setAuthChecked(true);
|
||||
}
|
||||
}, [setSetupIncomplete, setIsAuthenticated, setAuthChecked]);
|
||||
|
||||
useEffect(() => {
|
||||
checkSetupStatus();
|
||||
}, [checkSetupStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!setupChecked || setupIncomplete) return;
|
||||
checkSession();
|
||||
}, [setupChecked, setupIncomplete, checkSession]);
|
||||
if (authInitialized || authLoading) return;
|
||||
refreshSession();
|
||||
}, [setupChecked, setupIncomplete, authInitialized, authLoading, refreshSession]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!setupChecked) return;
|
||||
@@ -82,7 +68,7 @@ export function Dashboard() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!authChecked) return;
|
||||
if (!authInitialized) return;
|
||||
|
||||
if (!isAuthenticated) {
|
||||
if (location.pathname !== "/auth/sign-in") {
|
||||
@@ -91,10 +77,53 @@ export function Dashboard() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (location.pathname === "/setup" || location.pathname.startsWith("/auth/")) {
|
||||
const requiresSecurityPhrase = Boolean(user?.requiresSecurityPhraseDownload);
|
||||
const isSecurityPhrasePath = location.pathname === "/dashboard/security-phrase";
|
||||
|
||||
if (requiresSecurityPhrase && !isSecurityPhrasePath) {
|
||||
navigate("/dashboard/security-phrase", { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!requiresSecurityPhrase && isSecurityPhrasePath) {
|
||||
navigate("/dashboard/stacks", { replace: true });
|
||||
}
|
||||
}, [setupChecked, setupIncomplete, authChecked, isAuthenticated, location.pathname, navigate]);
|
||||
}, [
|
||||
setupChecked,
|
||||
setupIncomplete,
|
||||
authInitialized,
|
||||
isAuthenticated,
|
||||
location.pathname,
|
||||
navigate,
|
||||
user?.requiresSecurityPhraseDownload
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!setupChecked || setupIncomplete) return;
|
||||
if (!authInitialized || authLoading) return;
|
||||
|
||||
if (!isAuthenticated) {
|
||||
if (location.pathname !== "/auth/sign-in") {
|
||||
navigate("/auth/sign-in", { replace: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const isAuthPath = location.pathname.startsWith("/auth/");
|
||||
const isLogoutPath = location.pathname === "/auth/logout";
|
||||
|
||||
if (location.pathname === "/setup" || (isAuthPath && !isLogoutPath)) {
|
||||
navigate("/dashboard/stacks", { replace: true });
|
||||
}
|
||||
}, [
|
||||
setupChecked,
|
||||
setupIncomplete,
|
||||
authInitialized,
|
||||
authLoading,
|
||||
isAuthenticated,
|
||||
location.pathname,
|
||||
navigate
|
||||
]);
|
||||
|
||||
if (!setupChecked) {
|
||||
return (
|
||||
@@ -112,7 +141,7 @@ export function Dashboard() {
|
||||
);
|
||||
}
|
||||
|
||||
if (!authChecked) {
|
||||
if (!authInitialized || authLoading) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-blue-gray-50/50">
|
||||
<span className="text-blue-gray-500">Pruefe Anmeldung ...</span>
|
||||
@@ -128,26 +157,34 @@ export function Dashboard() {
|
||||
);
|
||||
}
|
||||
|
||||
const isSecurityPhraseRoute = location.pathname === "/dashboard/security-phrase";
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-blue-gray-50/50">
|
||||
<Sidenav
|
||||
routes={routes}
|
||||
brandImg={
|
||||
sidenavType === "dark" ? "/img/logo-ct.png" : "/img/logo-ct-dark.png"
|
||||
}
|
||||
/>
|
||||
<div className="p-4 xl:ml-80">
|
||||
<DashboardNavbar />
|
||||
<Configurator />
|
||||
<IconButton
|
||||
size="lg"
|
||||
color="white"
|
||||
className="fixed bottom-8 right-8 z-40 rounded-full shadow-blue-gray-900/10 xl:hidden"
|
||||
ripple={false}
|
||||
onClick={() => setOpenConfigurator(dispatch, true)}
|
||||
>
|
||||
<Cog6ToothIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
{!isSecurityPhraseRoute && (
|
||||
<Sidenav
|
||||
routes={routes}
|
||||
brandImg={
|
||||
sidenavType === "dark" ? "/img/logo-ct.png" : "/img/logo-ct-dark.png"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<div className={isSecurityPhraseRoute ? "" : "p-4 xl:ml-80"}>
|
||||
{!isSecurityPhraseRoute && (
|
||||
<>
|
||||
<DashboardNavbar />
|
||||
<Configurator />
|
||||
<IconButton
|
||||
size="lg"
|
||||
color="white"
|
||||
className="fixed bottom-8 right-8 z-40 rounded-full shadow-blue-gray-900/10 xl:hidden"
|
||||
ripple={false}
|
||||
onClick={() => setOpenConfigurator(dispatch, true)}
|
||||
>
|
||||
<Cog6ToothIcon className="h-5 w-5" />
|
||||
</IconButton>
|
||||
</>
|
||||
)}
|
||||
<Routes>
|
||||
{routes.map(
|
||||
({ layout, pages }) =>
|
||||
@@ -156,12 +193,29 @@ export function Dashboard() {
|
||||
<Route exact path={path} element={element} />
|
||||
))
|
||||
)}
|
||||
<Route path="users/:userId" element={<UserDetails />} />
|
||||
<Route path="usergroups/:groupId" element={<UserGroupDetail />} />
|
||||
<Route path="security-phrase" element={<SecurityPhrase />} />
|
||||
<Route
|
||||
path="users/:userId"
|
||||
element={
|
||||
<PermissionGate permission="users-access" requiredLevel="read">
|
||||
<UserDetails />
|
||||
</PermissionGate>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="usergroups/:groupId"
|
||||
element={
|
||||
<PermissionGate permission="user-groups-access" requiredLevel="read">
|
||||
<UserGroupDetail />
|
||||
</PermissionGate>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
<div className="text-blue-gray-600">
|
||||
<Footer />
|
||||
</div>
|
||||
{!isSecurityPhraseRoute && (
|
||||
<div className="text-blue-gray-600">
|
||||
<Footer />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
+14
-11
@@ -20,23 +20,26 @@ import "./tailwind.css";
|
||||
import ToastProvider from "@/components/ToastProvider.jsx";
|
||||
import MaintenanceProvider from "@/components/MaintenanceProvider.jsx";
|
||||
import PageProvider from "@/components/PageProvider.jsx";
|
||||
import { AuthProvider } from "@/components/AuthProvider.jsx";
|
||||
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<MaintenanceProvider>
|
||||
<ToastProvider>
|
||||
<PageProvider>
|
||||
<ThemeProvider>
|
||||
<MaterialTailwindControllerProvider>
|
||||
<App />
|
||||
</MaterialTailwindControllerProvider>
|
||||
</ThemeProvider>
|
||||
</PageProvider>
|
||||
</ToastProvider>
|
||||
</MaintenanceProvider>
|
||||
<AuthProvider>
|
||||
<MaintenanceProvider>
|
||||
<ToastProvider>
|
||||
<PageProvider>
|
||||
<ThemeProvider>
|
||||
<MaterialTailwindControllerProvider>
|
||||
<App />
|
||||
</MaterialTailwindControllerProvider>
|
||||
</ThemeProvider>
|
||||
</PageProvider>
|
||||
</ToastProvider>
|
||||
</MaintenanceProvider>
|
||||
</AuthProvider>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,400 @@
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import axios from "axios";
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
Typography,
|
||||
Input,
|
||||
Button,
|
||||
Spinner,
|
||||
Alert
|
||||
} from "@material-tailwind/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useToast } from "@/components/ToastProvider.jsx";
|
||||
|
||||
const PHRASE_WORD_COUNT = 8;
|
||||
|
||||
const normalizeWord = (value) => String(value || "").trim().toLowerCase();
|
||||
|
||||
const extractWordsFromText = (text = "") => {
|
||||
const normalized = String(text || "").replace(/[\r\n\t]+/g, " ").trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return [];
|
||||
}
|
||||
return normalized.split(/\s+/).filter(Boolean);
|
||||
};
|
||||
|
||||
const buildPhrasePayload = (words = []) => {
|
||||
const normalized = words.map(normalizeWord).filter(Boolean);
|
||||
return {
|
||||
words: normalized,
|
||||
canonical: normalized.join("")
|
||||
};
|
||||
};
|
||||
|
||||
export function ForgotPassword() {
|
||||
const navigate = useNavigate();
|
||||
const { showToast } = useToast();
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
const [phraseInputs, setPhraseInputs] = useState(Array(PHRASE_WORD_COUNT).fill(""));
|
||||
const [fileName, setFileName] = useState("");
|
||||
const [fileContent, setFileContent] = useState("");
|
||||
const [verificationError, setVerificationError] = useState("");
|
||||
const [verifying, setVerifying] = useState(false);
|
||||
const [verificationToken, setVerificationToken] = useState("");
|
||||
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [resetting, setResetting] = useState(false);
|
||||
const [resetError, setResetError] = useState("");
|
||||
|
||||
const phraseWords = useMemo(() => {
|
||||
if (fileContent.trim()) {
|
||||
return extractWordsFromText(fileContent);
|
||||
}
|
||||
return phraseInputs.map(normalizeWord).filter(Boolean);
|
||||
}, [fileContent, phraseInputs]);
|
||||
|
||||
const handlePhraseInputChange = useCallback((index, value) => {
|
||||
setPhraseInputs((prev) => {
|
||||
const next = [...prev];
|
||||
next[index] = value;
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleFileUpload = useCallback((event) => {
|
||||
const [file] = event.target.files || [];
|
||||
if (!file) {
|
||||
setFileName("");
|
||||
setFileContent("");
|
||||
return;
|
||||
}
|
||||
setFileName(file.name);
|
||||
const reader = new FileReader();
|
||||
reader.onload = (loadEvent) => {
|
||||
const text = String(loadEvent.target?.result || "");
|
||||
setFileContent(text);
|
||||
setPhraseInputs(Array(PHRASE_WORD_COUNT).fill(""));
|
||||
};
|
||||
reader.onerror = () => {
|
||||
setFileName("");
|
||||
setFileContent("");
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: "Datei konnte nicht gelesen werden",
|
||||
description: "Bitte lade die Datei erneut hoch."
|
||||
});
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}, [showToast]);
|
||||
|
||||
const handleVerifyPhrase = useCallback(async () => {
|
||||
setVerificationError("");
|
||||
setVerificationToken("");
|
||||
|
||||
const trimmedUsername = username.trim();
|
||||
if (!trimmedUsername) {
|
||||
setVerificationError("Bitte gib deinen Benutzernamen ein.");
|
||||
return;
|
||||
}
|
||||
|
||||
const words = phraseWords;
|
||||
const hasFile = Boolean(fileContent.trim());
|
||||
|
||||
if (!hasFile && words.length !== PHRASE_WORD_COUNT) {
|
||||
setVerificationError("Bitte gib alle acht Wörter des Sicherheitsschlüssels ein.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasFile && words.length !== PHRASE_WORD_COUNT) {
|
||||
setVerificationError("Die hochgeladene Datei enthält nicht genau acht Wörter.");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = buildPhrasePayload(words);
|
||||
if (!payload.canonical) {
|
||||
setVerificationError("Der Sicherheitsschlüssel ist ungültig oder leer.");
|
||||
return;
|
||||
}
|
||||
|
||||
setVerifying(true);
|
||||
try {
|
||||
const response = await axios.post("/api/auth/recover/verify", {
|
||||
username: trimmedUsername,
|
||||
phrase: payload.canonical,
|
||||
words: payload.words
|
||||
});
|
||||
|
||||
const responseToken = response.data?.token;
|
||||
if (!responseToken) {
|
||||
throw new Error("TOKEN_MISSING");
|
||||
}
|
||||
setVerificationToken(responseToken);
|
||||
showToast({
|
||||
variant: "success",
|
||||
title: "Sicherheitsschlüssel bestätigt",
|
||||
description: "Du kannst jetzt ein neues Passwort festlegen."
|
||||
});
|
||||
} catch (error) {
|
||||
const serverError = error.response?.data?.error;
|
||||
if (serverError === "USER_NOT_FOUND") {
|
||||
setVerificationError("Der angegebene Benutzer wurde nicht gefunden.");
|
||||
} else if (serverError === "PHRASE_MISMATCH") {
|
||||
setVerificationError("Der Sicherheitsschlüssel passt nicht zum Benutzer.");
|
||||
} else if (serverError === "PHRASE_NOT_INITIALIZED") {
|
||||
setVerificationError("Für diesen Benutzer ist aktuell kein Sicherheitsschlüssel hinterlegt.");
|
||||
} else if (serverError === "PHRASE_REQUIRED") {
|
||||
setVerificationError("Bitte gib den Sicherheitsschlüssel ein.");
|
||||
} else if (serverError === "USERNAME_REQUIRED") {
|
||||
setVerificationError("Der Benutzername darf nicht leer sein.");
|
||||
} else if (serverError === "INVALID_PASSWORD") {
|
||||
setVerificationError("Der Sicherheitsschlüssel ist ungültig.");
|
||||
} else {
|
||||
setVerificationError("Die Sicherheitsprüfung ist fehlgeschlagen. Bitte versuche es erneut.");
|
||||
}
|
||||
} finally {
|
||||
setVerifying(false);
|
||||
}
|
||||
}, [fileContent, phraseWords, showToast, username]);
|
||||
|
||||
const handleResetPassword = useCallback(async () => {
|
||||
setResetError("");
|
||||
|
||||
if (!verificationToken) {
|
||||
setResetError("Bitte bestätige zuerst deinen Sicherheitsschlüssel.");
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedPassword = password.trim();
|
||||
const trimmedConfirm = confirmPassword.trim();
|
||||
|
||||
if (!trimmedPassword || !trimmedConfirm) {
|
||||
setResetError("Bitte gib das neue Passwort zweimal ein.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (trimmedPassword !== trimmedConfirm) {
|
||||
setResetError("Die Passwörter stimmen nicht überein.");
|
||||
return;
|
||||
}
|
||||
|
||||
setResetting(true);
|
||||
try {
|
||||
await axios.post("/api/auth/recover/reset", {
|
||||
token: verificationToken,
|
||||
password: trimmedPassword,
|
||||
confirmPassword: trimmedConfirm
|
||||
});
|
||||
showToast({
|
||||
variant: "success",
|
||||
title: "Passwort aktualisiert",
|
||||
description: "Du kannst dich jetzt mit deinem neuen Passwort anmelden."
|
||||
});
|
||||
navigate("/auth/sign-in", { replace: true });
|
||||
} catch (error) {
|
||||
const serverError = error.response?.data?.error;
|
||||
if (serverError === "TOKEN_INVALID_OR_EXPIRED") {
|
||||
setResetError("Die Wiederherstellungssitzung ist abgelaufen. Bitte starte den Vorgang erneut.");
|
||||
setVerificationToken("");
|
||||
} else if (serverError === "PASSWORD_TOO_SHORT") {
|
||||
setResetError("Das Passwort muss mindestens 8 Zeichen enthalten.");
|
||||
} else if (serverError === "PASSWORD_REQUIRED") {
|
||||
setResetError("Bitte gib ein neues Passwort ein.");
|
||||
} else if (serverError === "PASSWORD_MISMATCH") {
|
||||
setResetError("Die Passwörter stimmen nicht überein.");
|
||||
} else {
|
||||
setResetError("Das Passwort konnte nicht zurückgesetzt werden. Bitte versuche es erneut.");
|
||||
}
|
||||
} finally {
|
||||
setResetting(false);
|
||||
}
|
||||
}, [confirmPassword, navigate, password, showToast, verificationToken]);
|
||||
|
||||
const verificationCompleted = Boolean(verificationToken);
|
||||
|
||||
return (
|
||||
<section className="m-8 flex justify-center">
|
||||
<Card className="w-full max-w-3xl">
|
||||
<CardHeader floated={false} shadow={false} className="rounded-none border-b border-blue-gray-50 bg-white px-6 py-5">
|
||||
<Typography variant="h4" color="blue-gray" className="font-semibold">
|
||||
Passwort zurücksetzen
|
||||
</Typography>
|
||||
<Typography color="blue-gray" className="mt-1 text-sm">
|
||||
Gib deinen Benutzernamen und den Sicherheitsschlüssel (8 Wörter) ein oder lade die gespeicherte Schlüsseldatei.
|
||||
</Typography>
|
||||
</CardHeader>
|
||||
<CardBody className="space-y-8">
|
||||
<div className="space-y-4">
|
||||
<Typography variant="h6" color="blue-gray">
|
||||
Schritt 1: Sicherheitsschlüssel prüfen
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography className="mb-2 block text-xs font-semibold uppercase text-blue-gray-500">
|
||||
Benutzername
|
||||
</Typography>
|
||||
<Input
|
||||
size="lg"
|
||||
placeholder="Benutzername"
|
||||
value={username}
|
||||
onChange={(event) => setUsername(event.target.value)}
|
||||
disabled={verificationCompleted || verifying}
|
||||
className=" !border-t-blue-gray-200 focus:!border-t-gray-900"
|
||||
labelProps={{ className: "before:content-none after:content-none" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography className="mb-2 block text-xs font-semibold uppercase text-blue-gray-500">
|
||||
Sicherheitsschlüssel eingeben
|
||||
</Typography>
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-4 min-w-[20px] [&>div]:!min-w-0">
|
||||
{phraseInputs.map((value, index) => (
|
||||
<Input
|
||||
key={`phrase-input-${index}`}
|
||||
placeholder={`Wort ${index + 1}`}
|
||||
value={value}
|
||||
onChange={(event) => handlePhraseInputChange(index, event.target.value)}
|
||||
disabled={verificationCompleted || verifying || Boolean(fileContent.trim())}
|
||||
className=" !border-t-blue-gray-200 focus:!border-t-gray-900"
|
||||
labelProps={{ className: "before:content-none after:content-none" }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography className="mb-2 block text-xs font-semibold uppercase text-blue-gray-500">
|
||||
Alternativ: Schlüsseldatei (.txt) hochladen
|
||||
</Typography>
|
||||
<div className="flex flex-col gap-3 md:flex-row md:items-center">
|
||||
<input
|
||||
type="file"
|
||||
accept=".txt"
|
||||
onChange={handleFileUpload}
|
||||
disabled={verificationCompleted || verifying}
|
||||
className="block w-full max-w-xs text-sm text-blue-gray-500 file:mr-4 file:rounded-md file:border-0 file:bg-blue-gray-50 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-blue-gray-700 hover:file:bg-blue-gray-100"
|
||||
/>
|
||||
{fileName && (
|
||||
<Typography className="text-xs text-blue-gray-500">
|
||||
Ausgewählte Datei: <span className="font-medium text-blue-gray-700">{fileName}</span>
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{verificationError && (
|
||||
<Alert color="red" className="border border-red-200 bg-red-50 text-red-700">
|
||||
{verificationError}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
<Button
|
||||
color="blue"
|
||||
className="normal-case"
|
||||
onClick={handleVerifyPhrase}
|
||||
disabled={verifying || verificationCompleted}
|
||||
>
|
||||
{verifying ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<Spinner className="h-4 w-4" /> Prüfe Sicherheitsschlüssel ...
|
||||
</span>
|
||||
) : (
|
||||
"Sicherheitsschlüssel prüfen"
|
||||
)}
|
||||
</Button>
|
||||
{verificationCompleted && (
|
||||
<Typography className="text-sm text-blue-gray-500">
|
||||
Die Wiederherstellung bleibt für kurze Zeit aktiv. Bitte setze dein Passwort zeitnah zurück.
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Schritt 2 */}
|
||||
<div className="space-y-4">
|
||||
<Typography variant="h6" color={verificationCompleted ? "blue-gray" : "gray"}>
|
||||
Schritt 2: Neues Passwort festlegen
|
||||
</Typography>
|
||||
|
||||
{!verificationCompleted && (
|
||||
<Typography className="text-sm text-blue-gray-500">
|
||||
Nach erfolgreicher Prüfung des Sicherheitsschlüssels kannst du hier ein neues Passwort vergeben.
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{verificationCompleted && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Typography className="mb-2 block text-xs font-semibold uppercase text-blue-gray-500">
|
||||
Neues Passwort
|
||||
</Typography>
|
||||
<Input
|
||||
type="password"
|
||||
size="lg"
|
||||
placeholder="Neues Passwort"
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
disabled={resetting}
|
||||
className=" !border-t-blue-gray-200 focus:!border-t-gray-900"
|
||||
labelProps={{ className: "before:content-none after:content-none" }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography className="mb-2 block text-xs font-semibold uppercase text-blue-gray-500">
|
||||
Passwort bestätigen
|
||||
</Typography>
|
||||
<Input
|
||||
type="password"
|
||||
size="lg"
|
||||
placeholder="Passwort bestätigen"
|
||||
value={confirmPassword}
|
||||
onChange={(event) => setConfirmPassword(event.target.value)}
|
||||
disabled={resetting}
|
||||
className=" !border-t-blue-gray-200 focus:!border-t-gray-900"
|
||||
labelProps={{ className: "before:content-none after:content-none" }}
|
||||
/>
|
||||
</div>
|
||||
{resetError && (
|
||||
<Alert color="red" className="border border-red-200 bg-red-50 text-red-700">
|
||||
{resetError}
|
||||
</Alert>
|
||||
)}
|
||||
<Button
|
||||
color="green"
|
||||
className="normal-case"
|
||||
onClick={handleResetPassword}
|
||||
disabled={resetting}
|
||||
>
|
||||
{resetting ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<Spinner className="h-4 w-4" /> Speichere Passwort ...
|
||||
</span>
|
||||
) : (
|
||||
"Neues Passwort speichern"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-blue-gray-50 pt-4">
|
||||
<Typography
|
||||
as="button"
|
||||
type="button"
|
||||
onClick={() => navigate("/auth/sign-in")}
|
||||
className="text-sm font-medium text-blue-600 transition hover:text-blue-800"
|
||||
>
|
||||
Zurück zur Anmeldung
|
||||
</Typography>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default ForgotPassword;
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from "@/pages/auth/sign-in";
|
||||
export * from "@/pages/auth/sign-up";
|
||||
export * from "@/pages/auth/sign-out";
|
||||
export * from "@/pages/auth/forgot-password";
|
||||
@@ -7,9 +7,11 @@ import {
|
||||
} from "@material-tailwind/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import pattern from "@/assets/images/pattern.png";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
|
||||
export function SignIn() {
|
||||
const navigate = useNavigate();
|
||||
const { setSession, refreshSession } = useAuth();
|
||||
const [identifier, setIdentifier] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -42,8 +44,12 @@ export function SignIn() {
|
||||
|
||||
if (sessionResponse.ok) {
|
||||
const sessionData = await sessionResponse.json();
|
||||
if (sessionData?.user) {
|
||||
setSession(sessionData);
|
||||
}
|
||||
if (sessionData?.user && isActive) {
|
||||
navigate("/dashboard/stacks", { replace: true });
|
||||
const requiresSecurityPhrase = Boolean(sessionData.user.requiresSecurityPhraseDownload);
|
||||
navigate(requiresSecurityPhrase ? "/dashboard/security-phrase" : "/dashboard/stacks", { replace: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -64,7 +70,7 @@ export function SignIn() {
|
||||
isActive = false;
|
||||
controller.abort();
|
||||
};
|
||||
}, [navigate]);
|
||||
}, [navigate, setSession]);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
async (event) => {
|
||||
@@ -108,7 +114,20 @@ export function SignIn() {
|
||||
return;
|
||||
}
|
||||
|
||||
navigate("/dashboard/stacks", { replace: true });
|
||||
let sessionPayload = null;
|
||||
|
||||
if (payload?.user) {
|
||||
setSession(payload);
|
||||
sessionPayload = payload;
|
||||
} else {
|
||||
const refreshed = await refreshSession();
|
||||
if (refreshed?.ok && refreshed?.data?.user) {
|
||||
sessionPayload = refreshed.data;
|
||||
}
|
||||
}
|
||||
|
||||
const requiresSecurityPhrase = Boolean(sessionPayload?.user?.requiresSecurityPhraseDownload);
|
||||
navigate(requiresSecurityPhrase ? "/dashboard/security-phrase" : "/dashboard/stacks", { replace: true });
|
||||
} catch (err) {
|
||||
console.error("⚠️ [Auth] Anmeldung fehlgeschlagen:", err);
|
||||
setError("Netzwerkfehler – bitte erneut versuchen.");
|
||||
@@ -116,7 +135,7 @@ export function SignIn() {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[identifier, password, loading, navigate]
|
||||
[identifier, password, loading, navigate, refreshSession, setSession]
|
||||
);
|
||||
|
||||
if (!statusChecked) {
|
||||
@@ -190,6 +209,14 @@ export function SignIn() {
|
||||
<Button type="submit" className="mt-6" fullWidth disabled={loading}>
|
||||
{loading ? "Anmeldung läuft ..." : "Anmelden"}
|
||||
</Button>
|
||||
<Typography
|
||||
as="button"
|
||||
type="button"
|
||||
onClick={() => navigate("/auth/forgot-password")}
|
||||
className="mt-4 text-sm font-medium text-blue-600 transition hover:text-blue-800"
|
||||
>
|
||||
Passwort vergessen?
|
||||
</Typography>
|
||||
</form>
|
||||
</div>
|
||||
<div className="w-2/5 h-full hidden lg:block">
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Card, CardBody, Typography, Spinner } from "@material-tailwind/react";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
|
||||
export function SignOut() {
|
||||
const navigate = useNavigate();
|
||||
const { logout } = useAuth();
|
||||
const [status, setStatus] = useState("abmelden...");
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const performLogout = async () => {
|
||||
try {
|
||||
setStatus("Abmeldung läuft …");
|
||||
setError(null);
|
||||
await logout();
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
console.error("⚠️ [Auth] Logout fehlgeschlagen:", err);
|
||||
setError("Abmeldung fehlgeschlagen. Bitte versuche es erneut.");
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
navigate("/auth/sign-in", { replace: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
performLogout();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [logout, navigate]);
|
||||
|
||||
return (
|
||||
<section className="m-8 flex min-h-[60vh] items-center justify-center">
|
||||
<Card className="w-full max-w-md border border-blue-gray-100 shadow-sm">
|
||||
<CardBody className="space-y-4 text-center">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<Spinner className="h-5 w-5 text-blue-gray-500" />
|
||||
<Typography color="blue-gray" className="text-sm font-medium">
|
||||
{status}
|
||||
</Typography>
|
||||
</div>
|
||||
{error && (
|
||||
<Typography color="red" className="text-sm">
|
||||
{error}
|
||||
</Typography>
|
||||
)}
|
||||
<Typography className="text-xs text-blue-gray-400">
|
||||
Du wirst automatisch weitergeleitet …
|
||||
</Typography>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
SignOut.displayName = "/src/pages/auth/sign-out.jsx";
|
||||
@@ -5,3 +5,4 @@ export * from "@/pages/dashboard/users";
|
||||
export * from "@/pages/dashboard/usergroups";
|
||||
export * from "@/pages/dashboard/userDetails";
|
||||
export * from "@/pages/dashboard/userGroupDetail";
|
||||
export * from "@/pages/dashboard/securityPhrase";
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "@material-tailwind/react";
|
||||
import { PaginationControls, usePage } from "@/components/PageProvider.jsx";
|
||||
import { useMaintenance } from "@/components/MaintenanceProvider.jsx";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
|
||||
const UPDATE_STAGE_LABELS = {
|
||||
initializing: "Vorbereitung",
|
||||
@@ -176,6 +177,9 @@ export function Logs() {
|
||||
const updateRunning = Boolean(updateState?.running);
|
||||
const maintenanceLocked = maintenanceActive || updateRunning;
|
||||
const updateStageLabel = updateState?.stage ? (UPDATE_STAGE_LABELS[updateState.stage] ?? updateState.stage) : "–";
|
||||
const { hasPermission } = useAuth();
|
||||
const canDeleteLogs = hasPermission("logs-delete", "full");
|
||||
const canExportLogs = hasPermission("logs-export", "full");
|
||||
|
||||
|
||||
const {
|
||||
@@ -607,6 +611,10 @@ export function Logs() {
|
||||
]);
|
||||
|
||||
const handleDeleteLog = async (id) => {
|
||||
if (!canDeleteLogs) {
|
||||
setError("Dir fehlt die Berechtigung, einzelne Logeinträge zu löschen.");
|
||||
return;
|
||||
}
|
||||
if (!window.confirm("Diesen Log-Eintrag dauerhaft löschen?")) return;
|
||||
setActionLoading(true);
|
||||
setError("");
|
||||
@@ -622,6 +630,10 @@ export function Logs() {
|
||||
};
|
||||
|
||||
const handleDeleteFiltered = async () => {
|
||||
if (!canDeleteLogs) {
|
||||
setError("Dir fehlt die Berechtigung, Logs zu löschen.");
|
||||
return;
|
||||
}
|
||||
if (!logs.length) return;
|
||||
if (!window.confirm("Alle angezeigten Logs (entsprechend Filter) löschen?")) return;
|
||||
setActionLoading(true);
|
||||
@@ -642,6 +654,10 @@ export function Logs() {
|
||||
};
|
||||
|
||||
const handleExport = async (format) => {
|
||||
if (!canExportLogs) {
|
||||
setError("Dir fehlt die Berechtigung, Logs zu exportieren.");
|
||||
return;
|
||||
}
|
||||
setActionLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
@@ -716,23 +732,31 @@ export function Logs() {
|
||||
{filtersOpen && (
|
||||
<div className="mb-8">
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
onClick={() => handleExport('txt')}
|
||||
disabled={actionLoading || loading}
|
||||
className="w-full md:flex-1">
|
||||
Export TXT</Button>
|
||||
<Button
|
||||
onClick={() => handleExport('sql')}
|
||||
disabled={actionLoading || loading}
|
||||
className="w-full md:flex-1">
|
||||
Export SQL</Button>
|
||||
<Button
|
||||
onClick={handleDeleteFiltered}
|
||||
disabled={actionLoading || loading || logs.length === 0}
|
||||
className="w-full md:flex-1 bg-sunsetCoral-500 hover:bg-sunsetCoral-600">
|
||||
Angezeigte löschen</Button>
|
||||
</div>
|
||||
{(canExportLogs || canDeleteLogs) && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{canExportLogs && (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => handleExport('txt')}
|
||||
disabled={actionLoading || loading}
|
||||
className="w-full md:flex-1">
|
||||
Export TXT</Button>
|
||||
<Button
|
||||
onClick={() => handleExport('sql')}
|
||||
disabled={actionLoading || loading}
|
||||
className="w-full md:flex-1">
|
||||
Export SQL</Button>
|
||||
</>
|
||||
)}
|
||||
{canDeleteLogs && (
|
||||
<Button
|
||||
onClick={handleDeleteFiltered}
|
||||
disabled={actionLoading || loading || logs.length === 0}
|
||||
className="w-full md:flex-1 bg-sunsetCoral-500 hover:bg-sunsetCoral-600">
|
||||
Angezeigte löschen</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-6 mt-10">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
@@ -1270,15 +1294,17 @@ export function Logs() {
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 align-top">
|
||||
<Typography variant="small">
|
||||
<button
|
||||
onClick={() => handleDeleteLog(log.id)}
|
||||
disabled={actionLoading}
|
||||
className="rounded-md border border-sunsetCoral-600 px-3 py-1 text-xs text-sunsetCoral-800 transition hover:bg-sunsetCoral-600/20 disabled:opacity-60"
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</Typography>
|
||||
{canDeleteLogs && (
|
||||
<Typography variant="small">
|
||||
<button
|
||||
onClick={() => handleDeleteLog(log.id)}
|
||||
disabled={actionLoading}
|
||||
className="rounded-md border border-sunsetCoral-600 px-3 py-1 text-xs text-sunsetCoral-800 transition hover:bg-sunsetCoral-600/20 disabled:opacity-60"
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</Typography>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,240 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import axios from "axios";
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
Typography,
|
||||
Button,
|
||||
Spinner,
|
||||
Alert
|
||||
} from "@material-tailwind/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ArrowDownTrayIcon, ArrowPathIcon } from "@heroicons/react/24/outline";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
|
||||
const formatWordsAsText = (words = []) => {
|
||||
if (!Array.isArray(words) || words.length === 0) {
|
||||
return "";
|
||||
}
|
||||
const rows = [];
|
||||
for (let index = 0; index < words.length; index += 4) {
|
||||
rows.push(words.slice(index, index + 4).join(" "));
|
||||
}
|
||||
return rows.join("\n");
|
||||
};
|
||||
|
||||
const groupWords = (words = []) => {
|
||||
if (!Array.isArray(words)) {
|
||||
return [];
|
||||
}
|
||||
const rows = [];
|
||||
for (let index = 0; index < words.length; index += 4) {
|
||||
rows.push(words.slice(index, index + 4));
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
export function SecurityPhrase() {
|
||||
const navigate = useNavigate();
|
||||
const { user, setSession, refreshSession } = useAuth();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const [words, setWords] = useState([]);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [downloaded, setDownloaded] = useState(false);
|
||||
|
||||
const requiresDownload = Boolean(user?.requiresSecurityPhraseDownload);
|
||||
|
||||
const groupedWords = useMemo(() => groupWords(words), [words]);
|
||||
|
||||
const triggerDownload = useCallback((content) => {
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
|
||||
const baseName = user?.username ? `${user.username}-sicherheitsschluessel` : "security-phrase";
|
||||
link.download = `${baseName}.txt`;
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}, [user?.username]);
|
||||
|
||||
const fetchPhrase = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
const response = await axios.get("/api/users/me/security-phrase");
|
||||
const phraseWords = Array.isArray(response.data?.item?.words) ? response.data.item.words : [];
|
||||
if (!phraseWords.length) {
|
||||
throw new Error("EMPTY_SECURITY_PHRASE");
|
||||
}
|
||||
setWords(phraseWords);
|
||||
setDownloaded(false);
|
||||
} catch (err) {
|
||||
if (err?.response?.status === 409 || err?.response?.data?.error === "SECURITY_PHRASE_ALREADY_DOWNLOADED") {
|
||||
await refreshSession();
|
||||
navigate("/dashboard/stacks", { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const message = err?.response?.data?.error || err?.message || "Sicherheitsschlüssel konnte nicht geladen werden.";
|
||||
setError(message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [navigate, refreshSession]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
navigate("/auth/sign-in", { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!requiresDownload) {
|
||||
navigate("/dashboard/stacks", { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
fetchPhrase();
|
||||
}, [user, requiresDownload, fetchPhrase, navigate]);
|
||||
|
||||
const handleRetry = useCallback(() => {
|
||||
fetchPhrase();
|
||||
}, [fetchPhrase]);
|
||||
|
||||
const handleDownload = useCallback(async () => {
|
||||
if (saving || !Array.isArray(words) || words.length === 0) {
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
setError("");
|
||||
|
||||
try {
|
||||
const text = formatWordsAsText(words);
|
||||
triggerDownload(text);
|
||||
|
||||
const response = await axios.post("/api/users/me/security-phrase/downloaded");
|
||||
const downloadedAt = response.data?.item?.downloadedAt || null;
|
||||
const sessionPayload = response.data?.session;
|
||||
|
||||
if (sessionPayload?.user) {
|
||||
setSession(sessionPayload);
|
||||
} else {
|
||||
await refreshSession();
|
||||
}
|
||||
|
||||
setDownloaded(Boolean(downloadedAt));
|
||||
navigate("/dashboard/stacks", { replace: true });
|
||||
} catch (err) {
|
||||
const message = err?.response?.data?.error || err?.message || "Download konnte nicht bestätigt werden.";
|
||||
setError(message);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}, [words, saving, triggerDownload, refreshSession, navigate, setSession]);
|
||||
|
||||
if (!requiresDownload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center bg-blue-gray-50/50 px-4 py-10">
|
||||
<div className="mb-6 text-center">
|
||||
<Typography variant="h2" className="font-semibold text-blue-gray-800">
|
||||
Sicherheitsschlüssel sichern
|
||||
</Typography>
|
||||
<Typography color="blue-gray" className="mt-2 max-w-3xl text-base">
|
||||
Bitte lade den einmaligen Sicherheitsschlüssel herunter und bewahre ihn sicher auf. Er wird benötigt, um dein Passwort zurückzusetzen.
|
||||
Ohne den Download kannst du die Anwendung nicht weiter nutzen.
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<Card className="w-full max-w-3xl shadow-lg">
|
||||
<CardBody className="space-y-6">
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-10">
|
||||
<Spinner className="h-10 w-10 text-blue-gray-500" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && error && (
|
||||
<Alert
|
||||
color="red"
|
||||
className="border border-red-200 bg-red-50 text-red-700"
|
||||
icon={<ArrowPathIcon className="h-5 w-5" />}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<Typography variant="h6" color="red">
|
||||
Fehler
|
||||
</Typography>
|
||||
<Typography color="red" className="text-sm">
|
||||
{error}
|
||||
</Typography>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
color="red"
|
||||
variant="text"
|
||||
onClick={handleRetry}
|
||||
className="normal-case"
|
||||
>
|
||||
Erneut versuchen
|
||||
</Button>
|
||||
</div>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{!loading && !error && (
|
||||
<>
|
||||
<div className="rounded-lg border border-blue-gray-100 bg-blue-gray-50/60 p-6">
|
||||
<Typography variant="h5" className="mb-4 text-blue-gray-800">
|
||||
Deine Sicherheitsphrase
|
||||
</Typography>
|
||||
<div className="space-y-3">
|
||||
{groupedWords.map((row, rowIndex) => (
|
||||
<div
|
||||
key={`phrase-row-${rowIndex}`}
|
||||
className="flex flex-wrap items-center justify-center gap-4 text-lg font-mono tracking-wide text-blue-gray-900 md:text-xl"
|
||||
>
|
||||
{row.map((word, wordIndex) => (
|
||||
<span key={`phrase-${rowIndex}-${wordIndex}`} className="uppercase">
|
||||
{word}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-between gap-4 md:flex-row">
|
||||
<div className="text-sm text-blue-gray-600">
|
||||
Der Download startet im TXT-Format. Bewahre die Datei sicher auf – sie kann später nicht erneut angezeigt werden.
|
||||
</div>
|
||||
<Button
|
||||
color="blue"
|
||||
size="md"
|
||||
className="flex items-center gap-2 normal-case"
|
||||
onClick={handleDownload}
|
||||
disabled={saving || downloaded || words.length === 0}
|
||||
>
|
||||
<ArrowDownTrayIcon className="h-5 w-5" />
|
||||
Sicherheitsschlüssel herunterladen
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SecurityPhrase;
|
||||
@@ -4,6 +4,7 @@ import { io } from "socket.io-client";
|
||||
import { useToast } from "@/components/ToastProvider.jsx";
|
||||
import { useMaintenance } from "@/components/MaintenanceProvider.jsx";
|
||||
import { PaginationControls, usePage } from "@/components/PageProvider.jsx";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
|
||||
import {
|
||||
Typography,
|
||||
@@ -73,6 +74,10 @@ export function Stacks() {
|
||||
script: scriptConfig,
|
||||
ssh: sshConfig,
|
||||
} = useMaintenance();
|
||||
const { hasPermission } = useAuth();
|
||||
const canRedeploySingle = hasPermission("stacks-redeploy-single", "full");
|
||||
const canRedeploySelection = hasPermission("stacks-redeploy-selection", "full");
|
||||
const canRedeployAll = hasPermission("stacks-redeploy-all", "full");
|
||||
|
||||
const maintenanceActive = Boolean(maintenanceMeta?.active);
|
||||
const maintenanceMessage = maintenanceMeta?.message;
|
||||
@@ -519,6 +524,7 @@ export function Stacks() {
|
||||
}, [filteredStacks.length, perPage, page, setPage]);
|
||||
|
||||
const toggleStackSelection = (stackId, disabled) => {
|
||||
if (!canRedeploySelection) return;
|
||||
if (disabled) return;
|
||||
setSelectedStackIds(prev =>
|
||||
prev.includes(stackId)
|
||||
@@ -527,8 +533,20 @@ export function Stacks() {
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!canRedeploySelection) {
|
||||
if (selectedStackIds.length) {
|
||||
setSelectedStackIds([]);
|
||||
}
|
||||
if (selectionPromptVisible) {
|
||||
setSelectionPromptVisible(false);
|
||||
}
|
||||
}
|
||||
}, [canRedeploySelection, selectedStackIds.length, selectionPromptVisible]);
|
||||
|
||||
|
||||
const applySelectionPreference = (action) => {
|
||||
if (!canRedeploySelection) return;
|
||||
const remember = rememberSelectionChoice;
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
@@ -559,6 +577,7 @@ export function Stacks() {
|
||||
};
|
||||
|
||||
const clearStoredSelectionPreference = () => {
|
||||
if (!canRedeploySelection) return;
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
window.sessionStorage.removeItem(SELECTION_PROMPT_STORAGE_KEY);
|
||||
@@ -577,15 +596,20 @@ export function Stacks() {
|
||||
};
|
||||
|
||||
const clearSelection = () => {
|
||||
if (!canRedeploySelection) return;
|
||||
setSelectedStackIds([]);
|
||||
setSelectionPromptVisible(false);
|
||||
};
|
||||
|
||||
const handleChipRemove = (stackId) => {
|
||||
if (!canRedeploySelection) return;
|
||||
setSelectedStackIds((prev) => prev.filter((id) => id !== stackId));
|
||||
};
|
||||
|
||||
const handleRedeploy = async (stackId) => {
|
||||
if (!canRedeploySingle) {
|
||||
return;
|
||||
}
|
||||
const snapshot = stacksByIdRef.current.get(stackId);
|
||||
const phase = snapshot?.redeployPhase;
|
||||
if (phase === REDEPLOY_PHASES.STARTED || phase === REDEPLOY_PHASES.QUEUED) return;
|
||||
@@ -636,6 +660,9 @@ export function Stacks() {
|
||||
};
|
||||
|
||||
const handleRedeployAll = async () => {
|
||||
if (!canRedeployAll) {
|
||||
return;
|
||||
}
|
||||
if (!eligibleFilteredStacks.length) return;
|
||||
|
||||
const targetIdList = eligibleFilteredStacks.map((stack) => stack.Id);
|
||||
@@ -687,6 +714,9 @@ export function Stacks() {
|
||||
};
|
||||
|
||||
const handleRedeploySelection = async () => {
|
||||
if (!canRedeploySelection) {
|
||||
return;
|
||||
}
|
||||
if (!selectedStackIds.length) return;
|
||||
|
||||
const eligibleIds = selectedStackIds.filter((id) => {
|
||||
@@ -747,22 +777,25 @@ export function Stacks() {
|
||||
}
|
||||
};
|
||||
|
||||
const hasSelection = selectedStackIds.length > 0;
|
||||
const hasSelection = canRedeploySelection && selectedStackIds.length > 0;
|
||||
const hasOutdatedStacks = eligibleFilteredStacks.length > 0;
|
||||
const bulkButtonLabel = hasSelection
|
||||
const showBulkButton = (canRedeploySelection && hasSelection) || canRedeployAll;
|
||||
const bulkButtonLabel = canRedeploySelection && hasSelection
|
||||
? `Redeploy Auswahl (${selectedStackIds.length})`
|
||||
: 'Redeploy Alle';
|
||||
|
||||
const bulkActionDisabled = maintenanceLocked || (hasSelection
|
||||
? selectionPromptVisible || selectedStackIds.length === 0 || selectedStackIds.every(id => {
|
||||
const targetStack = stacks.find(stack => stack.Id === id);
|
||||
if (!targetStack) return true;
|
||||
if (targetStack.updateStatus === '✅') return true;
|
||||
if (targetStack.redeployDisabled) return true;
|
||||
const phase = targetStack.redeployPhase;
|
||||
return phase === REDEPLOY_PHASES.STARTED || phase === REDEPLOY_PHASES.QUEUED;
|
||||
})
|
||||
: !hasOutdatedStacks);
|
||||
const bulkActionDisabled = maintenanceLocked || (
|
||||
canRedeploySelection && hasSelection
|
||||
? selectionPromptVisible || selectedStackIds.every(id => {
|
||||
const targetStack = stacks.find(stack => stack.Id === id);
|
||||
if (!targetStack) return true;
|
||||
if (targetStack.updateStatus === '✅') return true;
|
||||
if (targetStack.redeployDisabled) return true;
|
||||
const phase = targetStack.redeployPhase;
|
||||
return phase === REDEPLOY_PHASES.STARTED || phase === REDEPLOY_PHASES.QUEUED;
|
||||
})
|
||||
: (!canRedeployAll || !hasOutdatedStacks)
|
||||
);
|
||||
|
||||
const handleBulkRedeploy = () => {
|
||||
if (maintenanceLocked) {
|
||||
@@ -774,9 +807,9 @@ export function Stacks() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasSelection) {
|
||||
if (canRedeploySelection && hasSelection) {
|
||||
handleRedeploySelection();
|
||||
} else {
|
||||
} else if (canRedeployAll) {
|
||||
handleRedeployAll();
|
||||
}
|
||||
};
|
||||
@@ -868,7 +901,7 @@ export function Stacks() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-3 mt-5">
|
||||
{selectionPreferenceStored && (
|
||||
{canRedeploySelection && selectionPreferenceStored && (
|
||||
<button
|
||||
onClick={clearStoredSelectionPreference}
|
||||
className="block antialiased font-sans text-sm leading-normal text-inherit text-xs font-medium text-warmAmberGlow-500 underline underline-offset-2 transition hover:text-warmAmberGlow-600"
|
||||
@@ -881,7 +914,7 @@ export function Stacks() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectionPromptVisible && (
|
||||
{canRedeploySelection && selectionPromptVisible && (
|
||||
<div className="flex flex-col mt-5">
|
||||
|
||||
<div className="flex flex-col gap-3 rounded-lg border border-arcticBlue-800 bg-arcticBlue-900/90 px-4 py-3 text-arcticBlue-100 md:flex-row md:items-center md:justify-between">
|
||||
@@ -921,8 +954,9 @@ export function Stacks() {
|
||||
|
||||
</div>
|
||||
)}
|
||||
<div id="collect" className="mt-8 flex flex-col gap-4 md:flex-row md:items-start md:gap-6">
|
||||
{selectedStackIds.length > 0 && (
|
||||
{showBulkButton && (
|
||||
<div id="collect" className="mt-8 flex flex-col gap-4 md:flex-row md:items-start md:gap-6">
|
||||
{canRedeploySelection && selectedStackIds.length > 0 && (
|
||||
<div className="w-full md:order-first order-last md:w-3/4">
|
||||
<div className="flex flex-col gap-3 text-sm text-gray-300">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
@@ -963,7 +997,8 @@ export function Stacks() {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
@@ -978,6 +1013,7 @@ export function Stacks() {
|
||||
const isCurrent = stack.updateStatus === '✅';
|
||||
const isSelfStack = Boolean(stack.redeployDisabled);
|
||||
const isSelectable = !isBusy && !isCurrent && !isSelfStack;
|
||||
const canSelectStack = canRedeploySelection && isSelectable;
|
||||
return (
|
||||
|
||||
<Card key={stack.Id}>
|
||||
@@ -989,13 +1025,15 @@ export function Stacks() {
|
||||
${!isSelectable || isBusy ? 'opacity-75 bg-stormGrey-200/20' : ''}`}
|
||||
>
|
||||
<div className="flex items-center space-x-4" id="left">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => toggleStackSelection(stack.Id, !isSelectable)}
|
||||
className={`h-5 w-5 text-purple-500 focus:ring-purple-400 border-gray-600 bg-gray-900 rounded ${!isSelectable ? 'opacity-40 cursor-not-allowed' : ''}`}
|
||||
disabled={!isSelectable}
|
||||
/>
|
||||
{canRedeploySelection && (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => toggleStackSelection(stack.Id, !canSelectStack)}
|
||||
className={`h-5 w-5 text-purple-500 focus:ring-purple-400 border-gray-600 bg-gray-900 rounded ${!canSelectStack ? 'opacity-40 cursor-not-allowed' : ''}`}
|
||||
disabled={!canSelectStack}
|
||||
/>
|
||||
)}
|
||||
<div className={`w-12 h-12 flex items-center justify-center rounded-full
|
||||
${stack.updateStatus === '✅' ? 'bg-mossGreen-600' :
|
||||
stack.updateStatus === '⚠️' ? 'bg-warmAmberGlow-400' :
|
||||
@@ -1031,7 +1069,7 @@ export function Stacks() {
|
||||
<span className="antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-stormGrey-500">Status</span>
|
||||
<span className="text-mossGreen-600">Aktuell</span>
|
||||
</>
|
||||
) : (
|
||||
) : (canRedeploySingle ? (
|
||||
<Button
|
||||
onClick={() => handleRedeploy(stack.Id)}
|
||||
disabled={isBusy}
|
||||
@@ -1039,7 +1077,7 @@ export function Stacks() {
|
||||
>
|
||||
Redeploy
|
||||
</Button>
|
||||
)}
|
||||
) : null)}
|
||||
</div>
|
||||
|
||||
</CardBody>
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useToast } from "@/components/ToastProvider.jsx";
|
||||
import { platformSettingsData } from "@/data";
|
||||
import { AVATAR_COLORS } from "@/data/avatarColors.js";
|
||||
import { useMaintenance } from "@/components/MaintenanceProvider.jsx";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
|
||||
const _ = AVATAR_COLORS.join(" ");
|
||||
|
||||
@@ -55,7 +56,8 @@ const mapUser = (item) => ({
|
||||
lastLogin: item?.lastLogin || null,
|
||||
createdAt: item?.createdAt || null,
|
||||
updatedAt: item?.updatedAt || null,
|
||||
groups: normalizeUserGroups(item?.groups)
|
||||
groups: normalizeUserGroups(item?.groups),
|
||||
securityPhraseDownloadedAt: item?.securityPhraseDownloadedAt || null
|
||||
});
|
||||
|
||||
const extractPrimaryGroupId = (user) => {
|
||||
@@ -94,6 +96,7 @@ export function UserDetails() {
|
||||
const { userId } = useParams();
|
||||
const { showToast } = useToast();
|
||||
const { maintenance } = useMaintenance();
|
||||
const { hasPermission, user: authUser } = useAuth();
|
||||
|
||||
const [user, setUser] = useState(null);
|
||||
const [formValues, setFormValues] = useState(buildInitialFormValues(null));
|
||||
@@ -106,8 +109,23 @@ export function UserDetails() {
|
||||
const [groupsError, setGroupsError] = useState("");
|
||||
const [savingUser, setSavingUser] = useState(false);
|
||||
const [saveError, setSaveError] = useState("");
|
||||
const [securityPhraseWords, setSecurityPhraseWords] = useState([]);
|
||||
const [securityPhraseDownloadedAt, setSecurityPhraseDownloadedAt] = useState(null);
|
||||
const [securityPhraseLoading, setSecurityPhraseLoading] = useState(false);
|
||||
const [securityPhraseError, setSecurityPhraseError] = useState("");
|
||||
const [renewingSecurityPhrase, setRenewingSecurityPhrase] = useState(false);
|
||||
|
||||
const maintenanceActive = Boolean(maintenance?.active);
|
||||
|
||||
const canEditUsers = Boolean(authUser?.isSuperuser || hasPermission("users-edit", "full"));
|
||||
const canReadUsers = Boolean(authUser?.isSuperuser || hasPermission("users-edit", "read"));
|
||||
const canViewSecurityPhrase = Boolean(authUser?.isSuperuser || hasPermission("users-security-phrase", "read"));
|
||||
const canRenewSecurityPhrase = Boolean(authUser?.isSuperuser || hasPermission("users-security-phrase", "full"));
|
||||
|
||||
if (!canReadUsers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isSuperuserUser = useMemo(() => {
|
||||
if (!Array.isArray(user?.groups)) {
|
||||
return false;
|
||||
@@ -140,6 +158,9 @@ export function UserDetails() {
|
||||
throw new Error("USER_NOT_FOUND");
|
||||
}
|
||||
setUser(item);
|
||||
setSecurityPhraseWords([]);
|
||||
setSecurityPhraseDownloadedAt(item.securityPhraseDownloadedAt || null);
|
||||
setSecurityPhraseError("");
|
||||
const initialValues = buildInitialFormValues(item);
|
||||
initialFormValuesRef.current = { ...initialValues };
|
||||
setFormValues(initialValues);
|
||||
@@ -255,30 +276,34 @@ export function UserDetails() {
|
||||
);
|
||||
|
||||
const handleUsernameChange = useCallback((event) => {
|
||||
if (!canEditUsers) return;
|
||||
const { value } = event.target;
|
||||
setFormValues((prev) => ({
|
||||
...prev,
|
||||
username: value
|
||||
}));
|
||||
}, []);
|
||||
}, [canEditUsers]);
|
||||
|
||||
const handleEmailChange = useCallback((event) => {
|
||||
if (!canEditUsers) return;
|
||||
const { value } = event.target;
|
||||
setFormValues((prev) => ({
|
||||
...prev,
|
||||
email: value
|
||||
}));
|
||||
}, []);
|
||||
}, [canEditUsers]);
|
||||
|
||||
const handlePasswordChange = useCallback((event) => {
|
||||
if (!canEditUsers) return;
|
||||
const { value } = event.target;
|
||||
setFormValues((prev) => ({
|
||||
...prev,
|
||||
password: value
|
||||
}));
|
||||
}, []);
|
||||
}, [canEditUsers]);
|
||||
|
||||
const handleGroupChange = useCallback((value) => {
|
||||
if (!canEditUsers) return;
|
||||
if (!value) {
|
||||
setFormValues((prev) => ({
|
||||
...prev,
|
||||
@@ -291,17 +316,18 @@ export function UserDetails() {
|
||||
...prev,
|
||||
groupId: Number.isFinite(numeric) && numeric > 0 ? numeric : null
|
||||
}));
|
||||
}, []);
|
||||
}, [canEditUsers]);
|
||||
|
||||
const handleAvatarColorChange = useCallback((value) => {
|
||||
if (!canEditUsers) return;
|
||||
setFormValues((prev) => ({
|
||||
...prev,
|
||||
avatarColor: value || ""
|
||||
}));
|
||||
}, []);
|
||||
}, [canEditUsers]);
|
||||
|
||||
const handleSaveUser = useCallback(async () => {
|
||||
if (!user || !hasChanges) {
|
||||
if (!canEditUsers || !user || !hasChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -364,7 +390,89 @@ export function UserDetails() {
|
||||
} finally {
|
||||
setSavingUser(false);
|
||||
}
|
||||
}, [user, hasChanges, formValues, showToast, isSuperuserUser]);
|
||||
}, [canEditUsers, user, hasChanges, formValues, showToast, isSuperuserUser]);
|
||||
|
||||
const fetchSecurityPhrase = useCallback(async () => {
|
||||
if (!canViewSecurityPhrase || !numericUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSecurityPhraseLoading(true);
|
||||
setSecurityPhraseError("");
|
||||
|
||||
try {
|
||||
const response = await axios.get(`/api/users/${numericUserId}/security-phrase`);
|
||||
const words = Array.isArray(response.data?.item?.words) ? response.data.item.words : [];
|
||||
setSecurityPhraseWords(words);
|
||||
setSecurityPhraseDownloadedAt(response.data?.item?.downloadedAt || null);
|
||||
} catch (err) {
|
||||
const serverError = err.response?.data?.error;
|
||||
let message = "Sicherheitsschlüssel konnte nicht geladen werden.";
|
||||
|
||||
if (serverError === "USER_NOT_FOUND") {
|
||||
message = "Der Benutzer wurde nicht gefunden.";
|
||||
} else if (serverError === "INVALID_USER_ID") {
|
||||
message = "Die Benutzer-ID ist ungültig.";
|
||||
} else if (serverError === "INSUFFICIENT_PERMISSIONS") {
|
||||
message = "Keine Berechtigung zum Anzeigen des Sicherheitsschlüssels.";
|
||||
}
|
||||
|
||||
setSecurityPhraseError(message);
|
||||
setSecurityPhraseWords([]);
|
||||
setSecurityPhraseDownloadedAt(null);
|
||||
} finally {
|
||||
setSecurityPhraseLoading(false);
|
||||
}
|
||||
}, [canViewSecurityPhrase, numericUserId]);
|
||||
|
||||
const handleReloadSecurityPhrase = useCallback(() => {
|
||||
if (securityPhraseLoading || renewingSecurityPhrase) {
|
||||
return;
|
||||
}
|
||||
fetchSecurityPhrase();
|
||||
}, [fetchSecurityPhrase, renewingSecurityPhrase, securityPhraseLoading]);
|
||||
|
||||
const handleRenewSecurityPhrase = useCallback(async () => {
|
||||
if (!canRenewSecurityPhrase || !numericUserId || maintenanceActive || renewingSecurityPhrase) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRenewingSecurityPhrase(true);
|
||||
setSecurityPhraseError("");
|
||||
|
||||
try {
|
||||
const response = await axios.post(`/api/users/${numericUserId}/security-phrase/renew`);
|
||||
const words = Array.isArray(response.data?.item?.words) ? response.data.item.words : [];
|
||||
setSecurityPhraseWords(words);
|
||||
setSecurityPhraseDownloadedAt(response.data?.item?.downloadedAt || null);
|
||||
showToast({
|
||||
variant: "success",
|
||||
title: "Sicherheitsschlüssel erneuert",
|
||||
description: "Der Benutzer muss den neuen Schlüssel erneut herunterladen."
|
||||
});
|
||||
} catch (err) {
|
||||
const serverError = err.response?.data?.error;
|
||||
let message = "Der Sicherheitsschlüssel konnte nicht erneuert werden.";
|
||||
|
||||
if (serverError === "USER_NOT_FOUND") {
|
||||
message = "Der Benutzer wurde nicht gefunden.";
|
||||
} else if (serverError === "INVALID_USER_ID") {
|
||||
message = "Die Benutzer-ID ist ungültig.";
|
||||
} else if (serverError === "INSUFFICIENT_PERMISSIONS") {
|
||||
message = "Keine Berechtigung zum Erneuern des Sicherheitsschlüssels.";
|
||||
}
|
||||
|
||||
setSecurityPhraseError(message);
|
||||
} finally {
|
||||
setRenewingSecurityPhrase(false);
|
||||
}
|
||||
}, [
|
||||
canRenewSecurityPhrase,
|
||||
numericUserId,
|
||||
maintenanceActive,
|
||||
renewingSecurityPhrase,
|
||||
showToast
|
||||
]);
|
||||
|
||||
const avatarLabel = useMemo(() => {
|
||||
const source = (formValues.username || formValues.email || "").trim();
|
||||
@@ -381,6 +489,37 @@ export function UserDetails() {
|
||||
return user?.avatarColor || "";
|
||||
}, [formValues.avatarColor, user]);
|
||||
|
||||
const securityPhraseRows = useMemo(() => {
|
||||
if (!Array.isArray(securityPhraseWords) || securityPhraseWords.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const rows = [];
|
||||
for (let index = 0; index < securityPhraseWords.length; index += 4) {
|
||||
rows.push(securityPhraseWords.slice(index, index + 4));
|
||||
}
|
||||
return rows;
|
||||
}, [securityPhraseWords]);
|
||||
|
||||
const securityPhraseStatus = useMemo(() => {
|
||||
if (!securityPhraseDownloadedAt) {
|
||||
return {
|
||||
text: "Noch nicht heruntergeladen",
|
||||
tone: "text-red-500"
|
||||
};
|
||||
}
|
||||
const date = new Date(securityPhraseDownloadedAt);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return {
|
||||
text: "Zuletzt heruntergeladen: unbekannt",
|
||||
tone: "text-blue-gray-500"
|
||||
};
|
||||
}
|
||||
return {
|
||||
text: `Zuletzt heruntergeladen: ${date.toLocaleString("de-DE")}`,
|
||||
tone: "text-blue-gray-500"
|
||||
};
|
||||
}, [securityPhraseDownloadedAt]);
|
||||
|
||||
const filteredGroups = useMemo(() => {
|
||||
if (!Array.isArray(availableGroups)) {
|
||||
return [];
|
||||
@@ -402,9 +541,25 @@ export function UserDetails() {
|
||||
}
|
||||
}, [filteredGroups, formValues.groupId, isSuperuserUser]);
|
||||
|
||||
const selectDisabled = maintenanceActive || savingUser || !user || groupsLoading;
|
||||
const avatarSelectDisabled = maintenanceActive || savingUser || !user;
|
||||
const inputDisabled = maintenanceActive || savingUser || !user;
|
||||
useEffect(() => {
|
||||
if (!hasLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canViewSecurityPhrase || !numericUserId) {
|
||||
setSecurityPhraseWords([]);
|
||||
setSecurityPhraseDownloadedAt(null);
|
||||
setSecurityPhraseError("");
|
||||
setSecurityPhraseLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
fetchSecurityPhrase();
|
||||
}, [hasLoaded, canViewSecurityPhrase, fetchSecurityPhrase, numericUserId]);
|
||||
|
||||
const inputDisabled = maintenanceActive || savingUser || !user || !canEditUsers;
|
||||
const selectDisabled = maintenanceActive || savingUser || !user || groupsLoading || !canEditUsers || isSuperuserUser;
|
||||
const avatarSelectDisabled = maintenanceActive || savingUser || !user || !canEditUsers;
|
||||
const groupSelectValue = formValues.groupId ? String(formValues.groupId) : "";
|
||||
|
||||
return (
|
||||
@@ -523,11 +678,6 @@ export function UserDetails() {
|
||||
<Typography className="mb-2 block text-xs font-semibold uppercase text-blue-gray-500">
|
||||
Globale Rolle
|
||||
</Typography>
|
||||
{groupsError && !isSuperuserUser && (
|
||||
<div className="mb-3 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
|
||||
{groupsError}
|
||||
</div>
|
||||
)}
|
||||
{isSuperuserUser ? (
|
||||
<Typography className="block text-xs font-semibold uppercase text-blue-gray-500">
|
||||
Die Rolle für den Superuser kann nicht geändert werden.
|
||||
@@ -615,6 +765,99 @@ export function UserDetails() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{canViewSecurityPhrase && (
|
||||
<div className="mt-10 border-t border-blue-gray-50 pt-8">
|
||||
<div className="mb-4 flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<Typography variant="h6" color="blue-gray">
|
||||
Sicherheitsschlüssel
|
||||
</Typography>
|
||||
<Typography className={`text-sm ${securityPhraseStatus.tone}`}>
|
||||
{securityPhraseStatus.text}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="blue"
|
||||
className="normal-case"
|
||||
onClick={handleReloadSecurityPhrase}
|
||||
disabled={
|
||||
!canViewSecurityPhrase ||
|
||||
securityPhraseLoading ||
|
||||
renewingSecurityPhrase
|
||||
}
|
||||
>
|
||||
Aktualisieren
|
||||
</Button>
|
||||
{canRenewSecurityPhrase && (
|
||||
<Button
|
||||
color="red"
|
||||
className="normal-case"
|
||||
onClick={handleRenewSecurityPhrase}
|
||||
disabled={
|
||||
maintenanceActive ||
|
||||
securityPhraseLoading ||
|
||||
renewingSecurityPhrase
|
||||
}
|
||||
>
|
||||
{renewingSecurityPhrase ? "Erneuert ..." : "Schlüssel erneuern"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{securityPhraseLoading ? (
|
||||
<div className="flex items-center gap-3 rounded-lg border border-blue-gray-50 bg-blue-gray-50/50 px-4 py-3 text-sm text-blue-gray-500">
|
||||
<Spinner className="h-4 w-4" />
|
||||
<span>Sicherheitsschlüssel wird geladen ...</span>
|
||||
</div>
|
||||
) : securityPhraseError ? (
|
||||
<div className="flex flex-wrap items-center justify-between gap-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
<span>{securityPhraseError}</span>
|
||||
<Button
|
||||
variant="text"
|
||||
size="sm"
|
||||
color="red"
|
||||
className="normal-case"
|
||||
onClick={handleReloadSecurityPhrase}
|
||||
disabled={securityPhraseLoading || renewingSecurityPhrase}
|
||||
>
|
||||
Erneut laden
|
||||
</Button>
|
||||
</div>
|
||||
) : securityPhraseRows.length > 0 ? (
|
||||
<div className="rounded-lg border border-blue-gray-100 bg-blue-gray-50/60 p-4">
|
||||
<div className="space-y-2 text-center font-mono text-base tracking-wide text-blue-gray-900 md:text-lg">
|
||||
{securityPhraseRows.map((row, rowIndex) => (
|
||||
<div
|
||||
key={`security-phrase-row-${rowIndex}`}
|
||||
className="flex flex-wrap items-center justify-center gap-4"
|
||||
>
|
||||
{row.map((word, wordIndex) => (
|
||||
<span
|
||||
key={`security-phrase-${rowIndex}-${wordIndex}`}
|
||||
className="uppercase"
|
||||
>
|
||||
{word}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Typography className="text-sm text-blue-gray-500">
|
||||
Kein Sicherheitsschlüssel verfügbar.
|
||||
</Typography>
|
||||
)}
|
||||
{canRenewSecurityPhrase && (
|
||||
<Typography className="mt-3 text-sm text-blue-gray-500">
|
||||
Nach dem Erneuern muss der Benutzer den neuen Sicherheitsschlüssel erneut herunterladen.
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</CardBody>
|
||||
</Card></div>
|
||||
</>
|
||||
|
||||
@@ -10,11 +10,16 @@ import {
|
||||
Select,
|
||||
Option,
|
||||
Input,
|
||||
Chip
|
||||
Chip,
|
||||
Radio,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemPrefix
|
||||
} from "@material-tailwind/react";
|
||||
|
||||
import { useToast } from "@/components/ToastProvider.jsx";
|
||||
import { useMaintenance } from "@/components/MaintenanceProvider.jsx";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
import { AVATAR_COLORS } from "@/data/avatarColors.js";
|
||||
|
||||
const _ = AVATAR_COLORS.join(" ");
|
||||
@@ -53,34 +58,158 @@ const buildInitialFormValues = (group) => {
|
||||
};
|
||||
};
|
||||
|
||||
const LEVEL_OPTIONS = [
|
||||
{ value: "full", label: "Vollzugriff" },
|
||||
{ value: "read", label: "Nur lesen" },
|
||||
{ value: "none", label: "Kein Zugriff" }
|
||||
];
|
||||
|
||||
export function UserGroupDetail() {
|
||||
const { groupId } = useParams();
|
||||
const { showToast } = useToast();
|
||||
const { maintenance } = useMaintenance();
|
||||
const { hasPermission, user: authUser } = useAuth();
|
||||
|
||||
const [group, setGroup] = useState(null);
|
||||
const [formValues, setFormValues] = useState(buildInitialFormValues(null));
|
||||
const initialFormValuesRef = useRef(buildInitialFormValues(null));
|
||||
const [initialPermissionSelection, setInitialPermissionSelection] = useState({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [hasLoaded, setHasLoaded] = useState(false);
|
||||
const [savingGroup, setSavingGroup] = useState(false);
|
||||
const [saveError, setSaveError] = useState("");
|
||||
const [permissionSections, setPermissionSections] = useState([]);
|
||||
const [permissionSelection, setPermissionSelection] = useState({});
|
||||
const [permissionDefaults, setPermissionDefaults] = useState({});
|
||||
const [permissionsLoading, setPermissionsLoading] = useState(false);
|
||||
const [permissionsError, setPermissionsError] = useState("");
|
||||
|
||||
const maintenanceActive = Boolean(maintenance?.active);
|
||||
const isSuperuserGroup = useMemo(() => (group?.name || "").toLowerCase() === "superuser", [group]);
|
||||
const isSuperuserAccount = Boolean(authUser?.isSuperuser);
|
||||
const canEditGroupDetails = Boolean(isSuperuserAccount || hasPermission("user-groups-edit", "full"));
|
||||
const canViewPermissionSettings = Boolean(isSuperuserAccount || hasPermission("user-groups-edit", "read"));
|
||||
const canAdjustPermissions = Boolean(isSuperuserAccount || hasPermission("user-groups-edit", "full"));
|
||||
|
||||
const numericGroupId = useMemo(() => {
|
||||
const candidate = Number(groupId);
|
||||
return Number.isFinite(candidate) ? candidate : null;
|
||||
}, [groupId]);
|
||||
|
||||
const fetchGroupPermissions = useCallback(
|
||||
async (targetGroupId) => {
|
||||
const numericTargetId = Number(targetGroupId);
|
||||
if (!Number.isFinite(numericTargetId) || numericTargetId <= 0) {
|
||||
setPermissionSections([]);
|
||||
setPermissionSelection({});
|
||||
setPermissionDefaults({});
|
||||
setPermissionsError("");
|
||||
setInitialPermissionSelection({});
|
||||
setPermissionsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setPermissionsLoading(true);
|
||||
setPermissionsError("");
|
||||
|
||||
try {
|
||||
const response = await axios.get(`/api/groups/${numericTargetId}/permissions`);
|
||||
const rawSections = Array.isArray(response.data?.sections) ? response.data.sections : [];
|
||||
const rawValues =
|
||||
response.data?.values && typeof response.data.values === "object" ? response.data.values : {};
|
||||
|
||||
const defaults = {};
|
||||
const initialSelection = {};
|
||||
|
||||
const normalizeItem = (item, index) => {
|
||||
if (!item || typeof item !== "object") {
|
||||
return null;
|
||||
}
|
||||
const defaultLevel =
|
||||
typeof item.defaultLevel === "string" && item.defaultLevel ? item.defaultLevel : "none";
|
||||
defaults[item.key] = defaultLevel;
|
||||
const assigned = rawValues[item.key];
|
||||
initialSelection[item.key] = typeof assigned === "string" && assigned ? assigned : defaultLevel;
|
||||
|
||||
const availableLevels =
|
||||
Array.isArray(item.availableLevels) && item.availableLevels.length
|
||||
? item.availableLevels
|
||||
: LEVEL_OPTIONS.map((option) => option.value);
|
||||
|
||||
const dependencies = Array.isArray(item.dependencies) ? item.dependencies : [];
|
||||
|
||||
return {
|
||||
...item,
|
||||
availableLevels,
|
||||
dependencies,
|
||||
sortOrder: typeof item.sortOrder === "number" ? item.sortOrder : index
|
||||
};
|
||||
};
|
||||
|
||||
const normalizedSections = rawSections.map((section, sectionIndex) => {
|
||||
const sectionItems = Array.isArray(section.items)
|
||||
? section.items.map((item, itemIdx) => normalizeItem(item, itemIdx)).filter(Boolean)
|
||||
: [];
|
||||
|
||||
const sectionGroups = Array.isArray(section.groups)
|
||||
? section.groups.map((group, groupIdx) => {
|
||||
const groupItems = Array.isArray(group.items)
|
||||
? group.items.map((item, itemIdx) => normalizeItem(item, itemIdx)).filter(Boolean)
|
||||
: [];
|
||||
return {
|
||||
...group,
|
||||
sortOrder: typeof group.sortOrder === "number" ? group.sortOrder : groupIdx,
|
||||
items: groupItems
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
return {
|
||||
...section,
|
||||
sortOrder: typeof section.sortOrder === "number" ? section.sortOrder : sectionIndex,
|
||||
hasNavigation: Boolean(section.hasNavigation),
|
||||
items: sectionItems,
|
||||
groups: sectionGroups
|
||||
};
|
||||
});
|
||||
|
||||
setPermissionSections(normalizedSections);
|
||||
setPermissionDefaults(defaults);
|
||||
setPermissionSelection({ ...initialSelection });
|
||||
setInitialPermissionSelection({ ...initialSelection });
|
||||
} catch (err) {
|
||||
let message = "Die Berechtigungen konnten nicht geladen werden.";
|
||||
if (err.response?.data?.error === "GROUP_NOT_FOUND") {
|
||||
message = "Die angeforderte Benutzergruppe wurde nicht gefunden.";
|
||||
}
|
||||
setPermissionsError(message);
|
||||
setPermissionSections([]);
|
||||
setPermissionSelection({});
|
||||
setPermissionDefaults({});
|
||||
setInitialPermissionSelection({});
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: "Fehler beim Laden",
|
||||
description: message
|
||||
});
|
||||
} finally {
|
||||
setPermissionsLoading(false);
|
||||
}
|
||||
},
|
||||
[showToast]
|
||||
);
|
||||
|
||||
const fetchGroupDetails = useCallback(async () => {
|
||||
if (!numericGroupId) {
|
||||
setError("Ungültige Gruppen-ID.");
|
||||
setGroup(null);
|
||||
setFormValues(buildInitialFormValues(null));
|
||||
initialFormValuesRef.current = buildInitialFormValues(null);
|
||||
setPermissionSections([]);
|
||||
setPermissionSelection({});
|
||||
setPermissionDefaults({});
|
||||
setPermissionsError("");
|
||||
setHasLoaded(true);
|
||||
return;
|
||||
}
|
||||
@@ -99,6 +228,25 @@ export function UserGroupDetail() {
|
||||
initialFormValuesRef.current = { ...initialValues };
|
||||
setFormValues(initialValues);
|
||||
setSaveError("");
|
||||
|
||||
const superuserDetected = (item.name || "").toLowerCase() === "superuser";
|
||||
if (superuserDetected) {
|
||||
setPermissionSections([]);
|
||||
setPermissionSelection({});
|
||||
setPermissionDefaults({});
|
||||
setPermissionsError("");
|
||||
setInitialPermissionSelection({});
|
||||
setPermissionsLoading(false);
|
||||
} else if (canViewPermissionSettings) {
|
||||
fetchGroupPermissions(item.id);
|
||||
} else {
|
||||
setPermissionSections([]);
|
||||
setPermissionSelection({});
|
||||
setPermissionDefaults({});
|
||||
setPermissionsError("");
|
||||
setInitialPermissionSelection({});
|
||||
setPermissionsLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
const serverError = err.response?.data?.error;
|
||||
let message = "Gruppendetails konnten nicht geladen werden.";
|
||||
@@ -114,6 +262,11 @@ export function UserGroupDetail() {
|
||||
setGroup(null);
|
||||
initialFormValuesRef.current = buildInitialFormValues(null);
|
||||
setFormValues(buildInitialFormValues(null));
|
||||
setPermissionSections([]);
|
||||
setPermissionSelection({});
|
||||
setPermissionDefaults({});
|
||||
setPermissionsError("");
|
||||
setPermissionsLoading(false);
|
||||
setError(message);
|
||||
showToast({
|
||||
variant: "error",
|
||||
@@ -124,13 +277,13 @@ export function UserGroupDetail() {
|
||||
setLoading(false);
|
||||
setHasLoaded(true);
|
||||
}
|
||||
}, [numericGroupId, showToast]);
|
||||
}, [numericGroupId, showToast, fetchGroupPermissions, canViewPermissionSettings]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroupDetails();
|
||||
}, [fetchGroupDetails]);
|
||||
|
||||
const hasChanges = useMemo(() => {
|
||||
const detailsChanged = useMemo(() => {
|
||||
if (!hasLoaded || !group) {
|
||||
return false;
|
||||
}
|
||||
@@ -156,6 +309,28 @@ export function UserGroupDetail() {
|
||||
);
|
||||
}, [formValues, hasLoaded, group]);
|
||||
|
||||
const permissionsChanged = useMemo(() => {
|
||||
if (isSuperuserGroup) {
|
||||
return false;
|
||||
}
|
||||
const initial = initialPermissionSelection || {};
|
||||
const current = permissionSelection || {};
|
||||
const allKeys = new Set([...Object.keys(initial), ...Object.keys(current)]);
|
||||
for (const key of allKeys) {
|
||||
const initialValue = initial[key] ?? null;
|
||||
const currentValue = current[key] ?? null;
|
||||
if (initialValue !== currentValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, [permissionSelection, initialPermissionSelection, isSuperuserGroup]);
|
||||
|
||||
const hasChanges = useMemo(
|
||||
() => detailsChanged || permissionsChanged,
|
||||
[detailsChanged, permissionsChanged]
|
||||
);
|
||||
|
||||
const handleNameChange = useCallback((event) => {
|
||||
const { value } = event.target;
|
||||
setFormValues((prev) => ({
|
||||
@@ -180,7 +355,21 @@ export function UserGroupDetail() {
|
||||
}, []);
|
||||
|
||||
const handleSaveGroup = useCallback(async () => {
|
||||
if (!group || !hasChanges) {
|
||||
if (!group || (!detailsChanged && !permissionsChanged)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (permissionsChanged && isSuperuserGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (detailsChanged && !canEditGroupDetails) {
|
||||
setSaveError("Dir fehlt die Berechtigung, diese Gruppe zu bearbeiten.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (permissionsChanged && !canAdjustPermissions) {
|
||||
setSaveError("Dir fehlt die Berechtigung, die Gruppenrechte anzupassen.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -188,29 +377,51 @@ export function UserGroupDetail() {
|
||||
setSaveError("");
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
avatarColor: formValues.avatarColor
|
||||
};
|
||||
if (detailsChanged) {
|
||||
const payload = {
|
||||
avatarColor: formValues.avatarColor
|
||||
};
|
||||
|
||||
if (!isSuperuserGroup) {
|
||||
payload.name = formValues.name;
|
||||
payload.description = formValues.description;
|
||||
if (!isSuperuserGroup) {
|
||||
payload.name = formValues.name;
|
||||
payload.description = formValues.description;
|
||||
}
|
||||
|
||||
const response = await axios.put(`/api/groups/${group.id}`, payload);
|
||||
const updated = mapGroup(response.data?.item || response.data?.group);
|
||||
if (updated?.id) {
|
||||
setGroup(updated);
|
||||
const nextInitial = buildInitialFormValues(updated);
|
||||
initialFormValuesRef.current = { ...nextInitial };
|
||||
setFormValues(nextInitial);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await axios.put(`/api/groups/${group.id}`, payload);
|
||||
const updated = mapGroup(response.data?.item || response.data?.group);
|
||||
setGroup(updated);
|
||||
const nextInitial = buildInitialFormValues(updated);
|
||||
initialFormValuesRef.current = { ...nextInitial };
|
||||
setFormValues(nextInitial);
|
||||
showToast({
|
||||
variant: "success",
|
||||
title: "Gruppe gespeichert",
|
||||
description: "Die Änderungen wurden erfolgreich gespeichert."
|
||||
});
|
||||
if (permissionsChanged && !isSuperuserGroup) {
|
||||
await axios.put(`/api/groups/${group.id}/permissions`, {
|
||||
values: permissionSelection
|
||||
});
|
||||
setInitialPermissionSelection({ ...permissionSelection });
|
||||
}
|
||||
|
||||
if (detailsChanged) {
|
||||
showToast({
|
||||
variant: "success",
|
||||
title: "Gruppe gespeichert",
|
||||
description: "Die Änderungen wurden erfolgreich gespeichert."
|
||||
});
|
||||
} else if (permissionsChanged) {
|
||||
showToast({
|
||||
variant: "success",
|
||||
title: "Berechtigungen gespeichert",
|
||||
description: "Die Berechtigungen wurden erfolgreich gespeichert."
|
||||
});
|
||||
}
|
||||
|
||||
setSaveError("");
|
||||
} catch (err) {
|
||||
const serverError = err.response?.data?.error;
|
||||
let message = "Die Gruppe konnte nicht gespeichert werden.";
|
||||
let message = "Die Änderungen konnten nicht gespeichert werden.";
|
||||
|
||||
if (serverError === "GROUP_NAME_REQUIRED") {
|
||||
message = "Bitte einen Gruppennamen angeben.";
|
||||
@@ -221,7 +432,13 @@ export function UserGroupDetail() {
|
||||
} else if (serverError === "GROUP_NOT_FOUND") {
|
||||
message = "Die Benutzergruppe wurde nicht gefunden.";
|
||||
} else if (serverError === "GROUP_SUPERUSER_PROTECTED") {
|
||||
message = "Für die Superuser-Gruppe kann nur die Avatar-Farbe angepasst werden.";
|
||||
message = "Für die Superuser-Gruppe können Berechtigungen nicht angepasst werden.";
|
||||
} else if (serverError === "PERMISSION_INVALID_PAYLOAD") {
|
||||
message = "Ungültige Berechtigungsdaten übermittelt.";
|
||||
} else if (serverError === "PERMISSION_INVALID_LEVEL") {
|
||||
message = "Für mindestens eine Berechtigung wurde ein nicht erlaubter Wert übermittelt.";
|
||||
} else if (serverError === "PERMISSION_UNKNOWN_KEY") {
|
||||
message = "Es wurde eine unbekannte Berechtigung übermittelt.";
|
||||
}
|
||||
|
||||
setSaveError(message);
|
||||
@@ -233,7 +450,17 @@ export function UserGroupDetail() {
|
||||
} finally {
|
||||
setSavingGroup(false);
|
||||
}
|
||||
}, [group, hasChanges, formValues, showToast, isSuperuserGroup]);
|
||||
}, [
|
||||
group,
|
||||
detailsChanged,
|
||||
permissionsChanged,
|
||||
formValues,
|
||||
showToast,
|
||||
isSuperuserGroup,
|
||||
permissionSelection,
|
||||
canEditGroupDetails,
|
||||
canAdjustPermissions
|
||||
]);
|
||||
|
||||
const avatarLabel = useMemo(() => {
|
||||
const source = (formValues.name || formValues.description || "").trim();
|
||||
@@ -250,10 +477,168 @@ export function UserGroupDetail() {
|
||||
return group?.avatarColor || "";
|
||||
}, [formValues.avatarColor, group]);
|
||||
|
||||
const selectDisabled = maintenanceActive || savingGroup || !group;
|
||||
const inputDisabled = maintenanceActive || savingGroup || !group || isSuperuserGroup;
|
||||
const inputDisabled = maintenanceActive || savingGroup || !group || isSuperuserGroup || !canEditGroupDetails;
|
||||
const selectDisabled = maintenanceActive || savingGroup || !group || !canEditGroupDetails;
|
||||
const selectValue = formValues.avatarColor || "";
|
||||
|
||||
const handlePermissionChange = useCallback((permissionKey, value) => {
|
||||
if (!permissionKey || !canAdjustPermissions || isSuperuserGroup) {
|
||||
return;
|
||||
}
|
||||
setPermissionSelection((prev) => {
|
||||
if (prev[permissionKey] === value) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
[permissionKey]: value
|
||||
};
|
||||
});
|
||||
}, [canAdjustPermissions, isSuperuserGroup]);
|
||||
|
||||
const getPermissionValue = useCallback(
|
||||
(permissionKey) => {
|
||||
if (!permissionKey) {
|
||||
return null;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(permissionSelection, permissionKey)) {
|
||||
return permissionSelection[permissionKey];
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(permissionDefaults, permissionKey)) {
|
||||
return permissionDefaults[permissionKey];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[permissionSelection, permissionDefaults]
|
||||
);
|
||||
|
||||
const radioIcon = useMemo(
|
||||
() => (
|
||||
<span className="mx-auto block h-2.5 w-2.5 rounded-full border border-blue-gray-400 bg-black" />
|
||||
),
|
||||
[]
|
||||
);
|
||||
const radioCheckedIcon = useMemo(
|
||||
() => (
|
||||
<span className="mx-auto block h-2.5 w-2.5 rounded-full border border-blue-gray-700 bg-gray-900" />
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
const isPermissionRowVisible = useCallback(
|
||||
(row) => {
|
||||
if (!row || !row.key) {
|
||||
return false;
|
||||
}
|
||||
const dependencies = Array.isArray(row.dependencies) ? row.dependencies : [];
|
||||
return dependencies.every((dependency) => {
|
||||
if (!dependency || !dependency.key) {
|
||||
return true;
|
||||
}
|
||||
const value = getPermissionValue(dependency.key) ?? "none";
|
||||
const requirement = dependency.requiredLevel || "!=none";
|
||||
|
||||
if (requirement === "!=none") {
|
||||
return value !== "none";
|
||||
}
|
||||
if (requirement.startsWith("!=")) {
|
||||
return value !== requirement.substring(2);
|
||||
}
|
||||
if (requirement.startsWith("=")) {
|
||||
return value === requirement.substring(1);
|
||||
}
|
||||
if (!requirement) {
|
||||
return true;
|
||||
}
|
||||
return value === requirement;
|
||||
});
|
||||
},
|
||||
[getPermissionValue]
|
||||
);
|
||||
|
||||
const renderPermissionRow = (row, ownerKey, options = {}) => {
|
||||
if (!isPermissionRowVisible(row)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowName = `permission-${ownerKey}-${row.key}`;
|
||||
const currentValue = getPermissionValue(row.key) ?? "none";
|
||||
const { addTopBorder = true } = options;
|
||||
|
||||
const rowClasses = [
|
||||
"flex flex-col gap-2.5 px-4 py-4 md:flex-row md:items-center md:gap-6",
|
||||
addTopBorder ? "border-t border-blue-gray-100" : ""
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
|
||||
const availableLevels = new Set(
|
||||
Array.isArray(row.availableLevels) && row.availableLevels.length
|
||||
? row.availableLevels
|
||||
: LEVEL_OPTIONS.map((option) => option.value)
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={`${ownerKey}-${row.key}`} className={rowClasses}>
|
||||
<div className="text-sm font-medium leading-5 text-blue-gray-600 md:w-1/2">
|
||||
{row.label}
|
||||
</div>
|
||||
<List className="flex-col gap-1.5 md:ml-auto md:flex-row md:flex-nowrap md:w-1/2 md:items-center md:justify-end md:gap-3">
|
||||
{LEVEL_OPTIONS.map((option) => {
|
||||
const optionId = `${rowName}-${option.value}`;
|
||||
const checked = currentValue === option.value;
|
||||
const disabled =
|
||||
!availableLevels.has(option.value) ||
|
||||
permissionsLoading ||
|
||||
savingGroup ||
|
||||
!canAdjustPermissions ||
|
||||
isSuperuserGroup;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={`${ownerKey}-${row.key}-${option.value}`}
|
||||
className="w-full min-w-[140px] p-0 md:w-auto md:flex-1"
|
||||
>
|
||||
<label
|
||||
htmlFor={optionId}
|
||||
className={`inline-flex w-full items-center gap-2 rounded-lg px-3 py-2 min-h-[38px] ${
|
||||
disabled ? "cursor-not-allowed bg-blue-gray-50/40" : "cursor-pointer hover:bg-blue-gray-50/60"
|
||||
}`}
|
||||
>
|
||||
<ListItemPrefix className="flex-shrink-0">
|
||||
<Radio
|
||||
id={optionId}
|
||||
name={rowName}
|
||||
value={option.value}
|
||||
checked={checked}
|
||||
onChange={() => handlePermissionChange(row.key, option.value)}
|
||||
disabled={disabled}
|
||||
ripple={false}
|
||||
className="h-4 w-4 hover:before:opacity-0"
|
||||
containerProps={{
|
||||
className: "p-0"
|
||||
}}
|
||||
icon={radioIcon}
|
||||
checkedIcon={radioCheckedIcon}
|
||||
/>
|
||||
</ListItemPrefix>
|
||||
<Typography
|
||||
color="blue-gray"
|
||||
className={`text-sm font-medium leading-5 whitespace-nowrap ${
|
||||
disabled ? "text-blue-gray-300" : "text-blue-gray-600"
|
||||
}`}
|
||||
>
|
||||
{option.label}
|
||||
</Typography>
|
||||
</label>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-12 flex flex-col gap-12">
|
||||
@@ -415,11 +800,90 @@ export function UserGroupDetail() {
|
||||
<p>Mitglieder insgesamt: {group?.memberCount ?? 0}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:col-span-2 xl:col-span-3">
|
||||
<Typography variant="h6" color="blue-gray" className="mb-4">
|
||||
Berechtigungen (Ansicht)
|
||||
</Typography>
|
||||
{isSuperuserGroup ? (
|
||||
<div className="rounded-xl border border-blue-gray-100 bg-white px-4 py-4 text-sm text-blue-gray-600 shadow-sm">
|
||||
Die Superuser-Gruppe besitzt automatisch Vollzugriff auf alle Bereiche. Berechtigungen können hier nicht angepasst werden.
|
||||
</div>
|
||||
) : !canViewPermissionSettings ? null : (
|
||||
<div className="rounded-xl border border-blue-gray-100 bg-white shadow-sm">
|
||||
{permissionsLoading && (
|
||||
<div className="border-b border-blue-gray-100 px-4 py-4">
|
||||
<div className="flex items-center gap-3 text-sm text-blue-gray-500">
|
||||
<Spinner className="h-4 w-4" />
|
||||
<span>Berechtigungen werden geladen ...</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!permissionsLoading && permissionsError && (
|
||||
<div className="border-b border-blue-gray-100 px-4 py-4 text-sm text-red-700">
|
||||
{permissionsError}
|
||||
</div>
|
||||
)}
|
||||
{!permissionsLoading && !permissionsError &&
|
||||
(permissionSections.length === 0 ? (
|
||||
<div className="border-b border-blue-gray-100 px-4 py-4 text-sm text-blue-gray-500">
|
||||
Für diese Gruppe sind keine Berechtigungen definiert.
|
||||
</div>
|
||||
) : (
|
||||
permissionSections.map((section, sectionIndex) => {
|
||||
const sectionItems = Array.isArray(section.items) ? section.items : [];
|
||||
const sectionGroups = Array.isArray(section.groups) ? section.groups : [];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={section.key || sectionIndex}
|
||||
className={sectionIndex === 0 ? "" : "border-t border-blue-gray-100"}
|
||||
>
|
||||
<div className="border-b border-blue-gray-100 px-4 py-4">
|
||||
<Typography variant="h6" className="text-lg font-semibold text-blue-gray-700">
|
||||
{section.title}
|
||||
</Typography>
|
||||
</div>
|
||||
{sectionItems.map((row, rowIndex) =>
|
||||
renderPermissionRow(row, section.key || sectionIndex, {
|
||||
addTopBorder: rowIndex !== 0
|
||||
})
|
||||
)}
|
||||
{sectionGroups.map((group, groupIdx) => {
|
||||
const groupItems = Array.isArray(group.items) ? group.items : [];
|
||||
const hasVisibleRows = groupItems.some(isPermissionRowVisible);
|
||||
|
||||
if (!hasVisibleRows) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={group.key || `${section.key || sectionIndex}-${groupIdx}`} className="border-t border-blue-gray-100">
|
||||
<div className="border-b border-blue-gray-100 px-4 py-3">
|
||||
<Typography className="text-sm font-semibold uppercase tracking-wide text-blue-gray-600">
|
||||
{group.title}
|
||||
</Typography>
|
||||
</div>
|
||||
{groupItems.map((row, rowIndex) =>
|
||||
renderPermissionRow(row, group.key || `${section.key || sectionIndex}-${groupIdx}`, {
|
||||
addTopBorder: rowIndex !== 0
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card></div>
|
||||
</>
|
||||
);
|
||||
</Card>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserGroupDetail;
|
||||
export default UserGroupDetail;
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { PaginationControls, usePage } from "@/components/PageProvider.jsx";
|
||||
import { useMaintenance } from "@/components/MaintenanceProvider.jsx";
|
||||
import { useToast } from "@/components/ToastProvider.jsx";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
|
||||
export function Usergroups() {
|
||||
const [groups, setGroups] = useState([]);
|
||||
@@ -31,6 +32,10 @@ export function Usergroups() {
|
||||
const { maintenance } = useMaintenance();
|
||||
const maintenanceActive = Boolean(maintenance?.active);
|
||||
const navigate = useNavigate();
|
||||
const { hasPermission } = useAuth();
|
||||
|
||||
const canEditGroups = hasPermission("user-groups-edit", "full");
|
||||
const canDeleteGroups = hasPermission("user-groups-delete", "full");
|
||||
|
||||
const {
|
||||
page,
|
||||
@@ -188,6 +193,10 @@ export function Usergroups() {
|
||||
if (maintenanceActive) {
|
||||
return;
|
||||
}
|
||||
if (!canEditGroups) {
|
||||
setCreateGroupError("Du verfügst nicht über die Berechtigung zum Anlegen von Gruppen.");
|
||||
return;
|
||||
}
|
||||
const trimmedName = newGroupName.trim();
|
||||
if (!trimmedName) {
|
||||
setCreateGroupError("Gruppenname ist erforderlich.");
|
||||
@@ -228,6 +237,7 @@ export function Usergroups() {
|
||||
}
|
||||
}, [
|
||||
maintenanceActive,
|
||||
canEditGroups,
|
||||
newGroupName,
|
||||
newGroupDescription,
|
||||
showToast,
|
||||
@@ -242,6 +252,10 @@ export function Usergroups() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canDeleteGroups) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSuperuserGroup = (group?.name || "").toLowerCase() === "superuser";
|
||||
if (isSuperuserGroup) {
|
||||
showToast({
|
||||
@@ -308,7 +322,7 @@ export function Usergroups() {
|
||||
} finally {
|
||||
setDeletingGroupId(null);
|
||||
}
|
||||
}, [maintenanceActive, showToast, fetchGroups]);
|
||||
}, [maintenanceActive, canDeleteGroups, showToast, fetchGroups]);
|
||||
|
||||
return (
|
||||
<div className="mt-12 mb-8 flex flex-col gap-12">
|
||||
@@ -327,43 +341,45 @@ export function Usergroups() {
|
||||
</Typography>
|
||||
</CardHeader>
|
||||
<CardBody className="pt-0">
|
||||
<div className="mb-8 rounded-lg border border-blue-gray-100 bg-white p-4 shadow-sm">
|
||||
<Typography variant="h6" color="blue-gray" className="mb-2">
|
||||
Neue Benutzergruppe anlegen
|
||||
</Typography>
|
||||
<Typography variant="small" className="text-sm text-stormGrey-500 mb-4">
|
||||
Der Gruppenname ist Pflicht, die Beschreibung optional.
|
||||
</Typography>
|
||||
{createGroupError && (
|
||||
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
|
||||
{createGroupError}
|
||||
{canEditGroups && (
|
||||
<div className="mb-8 rounded-lg border border-blue-gray-100 bg-white p-4 shadow-sm">
|
||||
<Typography variant="h6" color="blue-gray" className="mb-2">
|
||||
Neue Benutzergruppe anlegen
|
||||
</Typography>
|
||||
<Typography variant="small" className="text-sm text-stormGrey-500 mb-4">
|
||||
Der Gruppenname ist Pflicht, die Beschreibung optional.
|
||||
</Typography>
|
||||
{createGroupError && (
|
||||
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
|
||||
{createGroupError}
|
||||
</div>
|
||||
)}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Input
|
||||
label="Gruppenname"
|
||||
value={newGroupName}
|
||||
onChange={(event) => setNewGroupName(event.target.value)}
|
||||
disabled={createGroupDisabled}
|
||||
crossOrigin=""
|
||||
/>
|
||||
<Input
|
||||
label="Beschreibung (optional)"
|
||||
value={newGroupDescription}
|
||||
onChange={(event) => setNewGroupDescription(event.target.value)}
|
||||
disabled={createGroupDisabled}
|
||||
crossOrigin=""
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<Button color="green" onClick={handleCreateGroup} disabled={createGroupDisabled}>
|
||||
{creatingGroup ? "Speichert ..." : "Gruppe anlegen"}
|
||||
</Button>
|
||||
<Button variant="text" color="blue-gray" onClick={resetNewGroupForm} disabled={creatingGroup}>
|
||||
Formular zurücksetzen
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Input
|
||||
label="Gruppenname"
|
||||
value={newGroupName}
|
||||
onChange={(event) => setNewGroupName(event.target.value)}
|
||||
disabled={createGroupDisabled}
|
||||
crossOrigin=""
|
||||
/>
|
||||
<Input
|
||||
label="Beschreibung (optional)"
|
||||
value={newGroupDescription}
|
||||
onChange={(event) => setNewGroupDescription(event.target.value)}
|
||||
disabled={createGroupDisabled}
|
||||
crossOrigin=""
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<Button color="green" onClick={handleCreateGroup} disabled={createGroupDisabled}>
|
||||
{creatingGroup ? "Speichert ..." : "Gruppe anlegen"}
|
||||
</Button>
|
||||
<Button variant="text" color="blue-gray" onClick={resetNewGroupForm} disabled={creatingGroup}>
|
||||
Formular zurücksetzen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-8">
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
@@ -475,24 +491,21 @@ export function Usergroups() {
|
||||
>
|
||||
Details
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="text"
|
||||
color="red"
|
||||
onClick={() => handleDeleteGroup(group)}
|
||||
disabled={
|
||||
maintenanceActive ||
|
||||
deletingGroupId === group.id ||
|
||||
Number(group.memberCount) > 0 ||
|
||||
isSuperuserGroup
|
||||
}
|
||||
>
|
||||
{isSuperuserGroup
|
||||
? ""
|
||||
: deletingGroupId === group.id
|
||||
? "Löscht ..."
|
||||
: "Löschen"}
|
||||
</Button>
|
||||
{canDeleteGroups && !isSuperuserGroup && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="text"
|
||||
color="red"
|
||||
onClick={() => handleDeleteGroup(group)}
|
||||
disabled={
|
||||
maintenanceActive ||
|
||||
deletingGroupId === group.id ||
|
||||
Number(group.memberCount) > 0
|
||||
}
|
||||
>
|
||||
{deletingGroupId === group.id ? "Löscht ..." : "Löschen"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import { PaginationControls, usePage } from "@/components/PageProvider.jsx";
|
||||
import { useMaintenance } from "@/components/MaintenanceProvider.jsx";
|
||||
import { useToast } from "@/components/ToastProvider.jsx";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
|
||||
const normalizeUserGroups = (rawGroups) => {
|
||||
if (!Array.isArray(rawGroups)) {
|
||||
@@ -64,6 +65,10 @@ export function Users() {
|
||||
const maintenanceActive = Boolean(maintenance?.active);
|
||||
const navigate = useNavigate();
|
||||
const noop = useCallback(() => { }, []);
|
||||
const { hasPermission } = useAuth();
|
||||
|
||||
const canManageUsers = hasPermission("users-edit", "full");
|
||||
const canDeleteUsers = hasPermission("users-delete", "full");
|
||||
|
||||
const {
|
||||
page,
|
||||
@@ -251,7 +256,9 @@ export function Users() {
|
||||
if (maintenanceActive || !user?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canDeleteUsers) {
|
||||
return;
|
||||
}
|
||||
const isSuperuserUser = Array.isArray(user?.groups)
|
||||
? user.groups.some((group) => (group?.name || "").toLowerCase() === "superuser")
|
||||
: false;
|
||||
@@ -307,12 +314,15 @@ export function Users() {
|
||||
} finally {
|
||||
setDeletingUserId(null);
|
||||
}
|
||||
}, [maintenanceActive, showToast, fetchUsers]);
|
||||
}, [maintenanceActive, canDeleteUsers, showToast, fetchUsers]);
|
||||
|
||||
const handleToggleUserStatus = useCallback(async (user) => {
|
||||
if (!user?.id) {
|
||||
return;
|
||||
}
|
||||
if (!canManageUsers) {
|
||||
return;
|
||||
}
|
||||
if (maintenanceActive) {
|
||||
showToast({
|
||||
variant: "error",
|
||||
@@ -376,12 +386,16 @@ export function Users() {
|
||||
} finally {
|
||||
setTogglingUserId(null);
|
||||
}
|
||||
}, [maintenanceActive, showToast, fetchUsers]);
|
||||
}, [maintenanceActive, canManageUsers, showToast, fetchUsers]);
|
||||
|
||||
const handleCreateUser = useCallback(async () => {
|
||||
if (maintenanceActive) {
|
||||
return;
|
||||
}
|
||||
if (!canManageUsers) {
|
||||
setCreateError("Du verfügst nicht über die Berechtigung zum Anlegen von Benutzern.");
|
||||
return;
|
||||
}
|
||||
const trimmedUsername = newUsername.trim();
|
||||
if (!trimmedUsername) {
|
||||
setCreateError("Benutzername ist erforderlich.");
|
||||
@@ -454,6 +468,7 @@ export function Users() {
|
||||
}
|
||||
}, [
|
||||
maintenanceActive,
|
||||
canManageUsers,
|
||||
newUsername,
|
||||
newEmail,
|
||||
newPassword,
|
||||
@@ -499,84 +514,86 @@ export function Users() {
|
||||
<CardBody className="pt-0">
|
||||
|
||||
|
||||
<div className="mb-8 rounded-lg border border-blue-gray-100 bg-white p-4 shadow-sm">
|
||||
<Typography variant="h6" color="blue-gray" className="mb-2">
|
||||
Neuen Benutzer anlegen
|
||||
</Typography>
|
||||
{hasSelectableGroups ? (
|
||||
<>
|
||||
<Typography variant="small" className="text-sm text-stormGrey-500 mb-4">
|
||||
Benutzername, Passwort (mindestens 8 Zeichen) und eine globale Rolle sind Pflichtfelder.
|
||||
</Typography>
|
||||
{createError && (
|
||||
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
|
||||
{createError}
|
||||
{canManageUsers && (
|
||||
<div className="mb-8 rounded-lg border border-blue-gray-100 bg-white p-4 shadow-sm">
|
||||
<Typography variant="h6" color="blue-gray" className="mb-2">
|
||||
Neuen Benutzer anlegen
|
||||
</Typography>
|
||||
{hasSelectableGroups ? (
|
||||
<>
|
||||
<Typography variant="small" className="text-sm text-stormGrey-500 mb-4">
|
||||
Benutzername, Passwort (mindestens 8 Zeichen) und eine globale Rolle sind Pflichtfelder.
|
||||
</Typography>
|
||||
{createError && (
|
||||
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
|
||||
{createError}
|
||||
</div>
|
||||
)}
|
||||
{groupsError && (
|
||||
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
|
||||
{groupsError}
|
||||
</div>
|
||||
)}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Input
|
||||
label="Benutzername"
|
||||
value={newUsername}
|
||||
onChange={(event) => setNewUsername(event.target.value)}
|
||||
disabled={maintenanceActive || creatingUser}
|
||||
crossOrigin=""
|
||||
/>
|
||||
<Input
|
||||
label="E-Mail (optional)"
|
||||
value={newEmail}
|
||||
onChange={(event) => setNewEmail(event.target.value)}
|
||||
disabled={maintenanceActive || creatingUser}
|
||||
crossOrigin=""
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
label="Passwort"
|
||||
value={newPassword}
|
||||
onChange={(event) => setNewPassword(event.target.value)}
|
||||
disabled={maintenanceActive || creatingUser}
|
||||
crossOrigin=""
|
||||
/>
|
||||
<Select
|
||||
label="Globale Rolle"
|
||||
variant="outlined"
|
||||
value={newGroupId}
|
||||
onChange={setNewGroupId}
|
||||
disabled={groupSelectDisabled}
|
||||
selected={renderSelectedGroup}
|
||||
>
|
||||
<Option value="">Bitte auswählen</Option>
|
||||
{availableGroups.map((group) => (
|
||||
<Option key={group.id} value={String(group.id)}>
|
||||
{group.name}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
{groupsError && (
|
||||
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
|
||||
{groupsError}
|
||||
<div className="mt-4 text-sm text-stormGrey-500">
|
||||
{groupsLoading
|
||||
? "Benutzergruppen werden geladen ..."
|
||||
: ""}
|
||||
</div>
|
||||
)}
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Input
|
||||
label="Benutzername"
|
||||
value={newUsername}
|
||||
onChange={(event) => setNewUsername(event.target.value)}
|
||||
disabled={maintenanceActive || creatingUser}
|
||||
crossOrigin=""
|
||||
/>
|
||||
<Input
|
||||
label="E-Mail (optional)"
|
||||
value={newEmail}
|
||||
onChange={(event) => setNewEmail(event.target.value)}
|
||||
disabled={maintenanceActive || creatingUser}
|
||||
crossOrigin=""
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
label="Passwort"
|
||||
value={newPassword}
|
||||
onChange={(event) => setNewPassword(event.target.value)}
|
||||
disabled={maintenanceActive || creatingUser}
|
||||
crossOrigin=""
|
||||
/>
|
||||
<Select
|
||||
label="Globale Rolle"
|
||||
variant="outlined"
|
||||
value={newGroupId}
|
||||
onChange={setNewGroupId}
|
||||
disabled={groupSelectDisabled}
|
||||
selected={renderSelectedGroup}
|
||||
>
|
||||
<Option value="">Bitte auswählen</Option>
|
||||
{availableGroups.map((group) => (
|
||||
<Option key={group.id} value={String(group.id)}>
|
||||
{group.name}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<div className="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<Button color="green" onClick={handleCreateUser} disabled={createDisabled}>
|
||||
{creatingUser ? "Speichert ..." : "Benutzer anlegen"}
|
||||
</Button>
|
||||
<Button variant="text" color="blue-gray" onClick={resetNewUserForm} disabled={creatingUser}>
|
||||
Formular zurücksetzen
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">
|
||||
Es existiert keine verfügbare Benutzergruppe. Bitte lege zuerst eine Gruppe an.
|
||||
</div>
|
||||
<div className="mt-4 text-sm text-stormGrey-500">
|
||||
{groupsLoading
|
||||
? "Benutzergruppen werden geladen ..."
|
||||
: ""}
|
||||
</div>
|
||||
<div className="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<Button color="green" onClick={handleCreateUser} disabled={createDisabled}>
|
||||
{creatingUser ? "Speichert ..." : "Benutzer anlegen"}
|
||||
</Button>
|
||||
<Button variant="text" color="blue-gray" onClick={resetNewUserForm} disabled={creatingUser}>
|
||||
Formular zurücksetzen
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">
|
||||
Es existiert keine verfügbare Benutzergruppe. Bitte lege zuerst eine Gruppe an.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-8">
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mt-8">
|
||||
@@ -674,20 +691,34 @@ export function Users() {
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<button
|
||||
type="button"
|
||||
className={`inline-flex items-center rounded-full px-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${maintenanceActive ? "cursor-not-allowed" : "cursor-pointer"}`}
|
||||
onClick={() => handleToggleUserStatus(user)}
|
||||
disabled={maintenanceActive || togglingUserId === user.id || (Array.isArray(user.groups) && user.groups.some((group) => (group?.name || "").toLowerCase() === "superuser" && user.isActive))}
|
||||
>
|
||||
{canManageUsers ? (
|
||||
<button
|
||||
type="button"
|
||||
className={`inline-flex items-center rounded-full px-1 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${maintenanceActive ? "cursor-not-allowed" : "cursor-pointer"}`}
|
||||
onClick={() => handleToggleUserStatus(user)}
|
||||
disabled={
|
||||
maintenanceActive ||
|
||||
togglingUserId === user.id ||
|
||||
(Array.isArray(user.groups) &&
|
||||
user.groups.some((group) => (group?.name || "").toLowerCase() === "superuser" && user.isActive))
|
||||
}
|
||||
>
|
||||
<Chip
|
||||
value={togglingUserId === user.id ? "Wechselt ..." : user.isActive ? "Aktiv" : "Deaktiviert"}
|
||||
size="sm"
|
||||
color={user.isActive ? "green" : "red"}
|
||||
variant="ghost"
|
||||
className="pointer-events-none"
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<Chip
|
||||
value={togglingUserId === user.id ? "Wechselt ..." : user.isActive ? "Aktiv" : "Deaktiviert"}
|
||||
value={user.isActive ? "Aktiv" : "Deaktiviert"}
|
||||
size="sm"
|
||||
color={user.isActive ? "green" : "red"}
|
||||
variant="ghost"
|
||||
className="pointer-events-none"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<Typography variant="small" className="antialiased font-sans mb-1 block text-xs font-medium text-stormGrey-600">
|
||||
@@ -709,19 +740,17 @@ export function Users() {
|
||||
>
|
||||
Details
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="text"
|
||||
color="red"
|
||||
onClick={() => handleDeleteUser(user)}
|
||||
disabled={maintenanceActive || deletingUserId === user.id || isSuperuserUser}
|
||||
>
|
||||
{isSuperuserUser
|
||||
? ""
|
||||
: deletingUserId === user.id
|
||||
? "Löscht ..."
|
||||
: "Löschen"}
|
||||
</Button>
|
||||
{canDeleteUsers && !isSuperuserUser && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="text"
|
||||
color="red"
|
||||
onClick={() => handleDeleteUser(user)}
|
||||
disabled={maintenanceActive || deletingUserId === user.id}
|
||||
>
|
||||
{deletingUserId === user.id ? "Löscht ..." : "Löschen"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
+42
-6
@@ -4,11 +4,14 @@ import {
|
||||
ListBulletIcon,
|
||||
ServerStackIcon,
|
||||
RectangleStackIcon,
|
||||
ArrowLeftOnRectangleIcon,
|
||||
UserIcon,
|
||||
UserGroupIcon
|
||||
UserGroupIcon,
|
||||
KeyIcon
|
||||
} from "@heroicons/react/24/solid";
|
||||
import { Stacks, Maintenance, Logs, Users, Usergroups } from "@/pages/dashboard";
|
||||
import { SignIn, SignUp } from "@/pages/auth";
|
||||
import { SignIn, SignUp, SignOut, ForgotPassword } from "@/pages/auth";
|
||||
import PermissionGate from "@/components/PermissionGate.jsx";
|
||||
|
||||
const icon = {
|
||||
className: "w-5 h-5 text-inherit",
|
||||
@@ -23,30 +26,51 @@ export const routes = [
|
||||
name: "stacks",
|
||||
path: "/stacks",
|
||||
element: <Stacks />,
|
||||
permission: null,
|
||||
},
|
||||
{
|
||||
icon: <WrenchScrewdriverIcon {...icon} />,
|
||||
name: "wartung",
|
||||
path: "/maintenance",
|
||||
element: <Maintenance />,
|
||||
element: (
|
||||
<PermissionGate permission="maintenance-access" requiredLevel="read">
|
||||
<Maintenance />
|
||||
</PermissionGate>
|
||||
),
|
||||
permission: { key: "maintenance-access", requiredLevel: "read" },
|
||||
},
|
||||
{
|
||||
icon: <ListBulletIcon {...icon} />,
|
||||
name: "logs",
|
||||
path: "/logs",
|
||||
element: <Logs />,
|
||||
element: (
|
||||
<PermissionGate permission="logs-access" requiredLevel="full">
|
||||
<Logs />
|
||||
</PermissionGate>
|
||||
),
|
||||
permission: { key: "logs-access", requiredLevel: "full" },
|
||||
},
|
||||
{
|
||||
icon: <UserIcon {...icon} />,
|
||||
name: "benutzer",
|
||||
path: "/users",
|
||||
element: <Users />,
|
||||
element: (
|
||||
<PermissionGate permission="users-access" requiredLevel="read">
|
||||
<Users />
|
||||
</PermissionGate>
|
||||
),
|
||||
permission: { key: "users-access", requiredLevel: "read" },
|
||||
},
|
||||
{
|
||||
icon: <UserGroupIcon {...icon} />,
|
||||
name: "rechtegruppen",
|
||||
path: "/usergroups",
|
||||
element: <Usergroups />,
|
||||
element: (
|
||||
<PermissionGate permission="user-groups-access" requiredLevel="read">
|
||||
<Usergroups />
|
||||
</PermissionGate>
|
||||
),
|
||||
permission: { key: "user-groups-access", requiredLevel: "read" },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -60,6 +84,18 @@ export const routes = [
|
||||
path: "/sign-in",
|
||||
element: <SignIn />,
|
||||
},
|
||||
{
|
||||
icon: <KeyIcon {...icon} />,
|
||||
name: "passwort vergessen",
|
||||
path: "/forgot-password",
|
||||
element: <ForgotPassword />,
|
||||
},
|
||||
{
|
||||
icon: <ArrowLeftOnRectangleIcon {...icon} />,
|
||||
name: "log out",
|
||||
path: "/logout",
|
||||
element: <SignOut />,
|
||||
},
|
||||
{
|
||||
icon: <RectangleStackIcon {...icon} />,
|
||||
name: "sign up",
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { Link, NavLink } from "react-router-dom";
|
||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
IconButton,
|
||||
Typography,
|
||||
} from "@material-tailwind/react";
|
||||
import { Avatar, Button, IconButton, Typography } from "@material-tailwind/react";
|
||||
import { useMaterialTailwindController, setOpenSidenav } from "@/components";
|
||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||
import logo from "@/assets/images/stackpulse.png";
|
||||
|
||||
export function Sidenav({ brandImg, brandName, routes }) {
|
||||
const [controller, dispatch] = useMaterialTailwindController();
|
||||
const { sidenavColor, sidenavType, openSidenav } = controller;
|
||||
const { initialized: authInitialized, hasPermission } = useAuth();
|
||||
const sidenavTypes = {
|
||||
dark: "bg-gradient-to-br from-gray-800 to-gray-900",
|
||||
white: "bg-white shadow-sm",
|
||||
@@ -38,8 +35,28 @@ export function Sidenav({ brandImg, brandName, routes }) {
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="m-4">
|
||||
{routes.map(({ layout, title, pages }, key) => (
|
||||
<ul key={key} className="mb-4 flex flex-col gap-1">
|
||||
{routes.map(({ layout, title, pages }, key) => {
|
||||
const visiblePages = pages.filter(({ permission }) => {
|
||||
if (!permission || !permission.key) {
|
||||
return true;
|
||||
}
|
||||
if (!authInitialized) {
|
||||
return false;
|
||||
}
|
||||
const requiredLevel = permission.requiredLevel || permission.level || "full";
|
||||
return hasPermission(permission.key, requiredLevel);
|
||||
});
|
||||
|
||||
if (layout !== "dashboard" && layout !== "auth") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (visiblePages.length === 0 && !title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul key={key} className="mb-4 flex flex-col gap-1">
|
||||
{title && (
|
||||
<li className="mx-3.5 mt-4 mb-2">
|
||||
<Typography
|
||||
@@ -51,7 +68,7 @@ export function Sidenav({ brandImg, brandName, routes }) {
|
||||
</Typography>
|
||||
</li>
|
||||
)}
|
||||
{pages.map(({ icon, name, path }) => (
|
||||
{visiblePages.map(({ icon, name, path }) => (
|
||||
<li key={name}>
|
||||
<NavLink to={`/${layout}${path}`}>
|
||||
{({ isActive }) => (
|
||||
@@ -78,9 +95,10 @@ export function Sidenav({ brandImg, brandName, routes }) {
|
||||
)}
|
||||
</NavLink>
|
||||
</li>
|
||||
))}
|
||||
))}
|
||||
</ul>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user