Files
breadcrumb-the-shire/pages/admin/tenants/index(default).phtml
fs 25370a1a55 add composer-unused, comprehensive docs, and project restructure
- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks
- Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists
- Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services
- Add Microsoft OIDC SSO, API token management, and user lifecycle features
- Add swagger-ui vendor integration and OpenAPI spec
- Add production Docker setup and bin/ scripts
- Update composer dependencies, config, templates, and frontend assets throughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 15:27:35 +01:00

182 lines
5.5 KiB
PHTML

<?php
use MintyPHP\Router;
$canCreateTenants = can('tenants.create');
?>
<?php
$breadcrumbs = [
['label' => t('Home'), 'path' => 'admin'],
['label' => t('Tenants')],
];
require templatePath('partials/app-breadcrumb.phtml');
?>
<div class="app-list-titlebar">
<h1><?php e(t('Tenants')); ?></h1>
<div class="app-list-titlebar-actions">
<button
class="outline secondary"
type="button"
data-toolbar-toggle
data-toolbar-target="#tenants-toolbar"
data-toolbar-text-show="<?php e(t('Show filters')); ?>"
data-toolbar-text-hide="<?php e(t('Hide filters')); ?>"
>
<i class="bi bi-funnel-fill"></i> <span data-toolbar-label><?php e(t('Hide filters')); ?></span>
</button>
<?php if ($canCreateTenants): ?>
<a role="button" class="primary" href="admin/tenants/create">
<i class="bi bi-plus"></i> <?php e(t('Create tenant')); ?>
</a>
<?php endif; ?>
</div>
</div>
<div class="app-list-toolbar" id="tenants-toolbar">
<input type="search" id="tenant-search" placeholder="<?php e(t('Search...')); ?>">
</div>
<div class="app-list-table">
<div id="tenants-grid"></div>
</div>
<script src="<?php e(asset('vendor/gridjs/gridjs.umd.js')); ?>"></script>
<script type="module">
import { createServerGrid } from "<?php e(assetVersion('js/core/app-grid-factory.js')); ?>";
import { badgeHtml, escapeHtml, getAppBase } from "<?php e(asset('js/pages/app-list-utils.js')); ?>";
const appBase = getAppBase();
const statusLabels = {
active: "<?php e(t('Active')); ?>",
inactive: "<?php e(t('Inactive')); ?>"
};
const labelDefault = "<?php e(t('Default')); ?>";
const labelMicrosoftOnly = "<?php e(t('Microsoft only')); ?>";
const labelDisabled = "<?php e(t('Inactive')); ?>";
const initTenantsGrid = () => {
const formatBadge = (value) => badgeHtml(gridjs, value);
const formatStatus = (value) => {
const normalized = String(value ?? 'active').toLowerCase();
const variant = normalized === 'inactive' ? 'danger' : 'success';
const label = statusLabels[normalized] ?? statusLabels.active;
return gridjs.html(`<span class="badge" data-variant="${variant}">${label}</span>`);
};
const formatTheme = (cell) => {
if (!cell || typeof cell !== 'object') {
return gridjs.html('');
}
const themeLabel = escapeHtml(cell.label ?? '');
const label = cell.is_override ? themeLabel : `${themeLabel} (${escapeHtml(labelDefault)})`;
return gridjs.html(label);
};
const formatSso = (cell) => {
if (!cell || typeof cell !== 'object') {
return gridjs.html('');
}
if (!cell.enabled) {
return gridjs.html(escapeHtml(labelDisabled));
}
if (cell.microsoft_only) {
return gridjs.html(escapeHtml(labelMicrosoftOnly));
}
return gridjs.html(escapeHtml(statusLabels.active));
};
const formatCount = (value) => {
const number = Number(value ?? 0);
return Number.isFinite(number) ? number : 0;
};
const gridConfig = createServerGrid({
gridjs: window.gridjs,
container: '#tenants-grid',
dataUrl: 'admin/tenants/data',
appBase,
columns: [
{
name: "<?php e(t('Description')); ?>",
sort: true,
formatter: (cell) => {
if (!cell || typeof cell !== 'object') {
return gridjs.html(String(cell ?? ''));
}
const label = cell.label ?? '';
return gridjs.html(`${label}`);
}
},
{
name: "<?php e(t('Status')); ?>",
sort: false,
formatter: (cell) => formatStatus(cell)
},
{
name: "<?php e(t('Theme')); ?>",
sort: true,
formatter: (cell) => formatTheme(cell)
},
{
name: "<?php e(t('SSO')); ?>",
sort: true,
formatter: (cell) => formatSso(cell)
},
{
name: "<?php e(t('Active users')); ?>",
sort: true,
formatter: (cell) => formatCount(cell)
},
{
name: "<?php e(t('Inactive users')); ?>",
sort: true,
formatter: (cell) => formatCount(cell)
},
{
name: "<?php e(t('Created')); ?>",
sort: true,
formatter: (cell) => formatBadge(cell)
},
{
name: "UUID",
hidden: true
}
],
sortColumns: ['description', 'status', 'theme', 'sso', 'active_users', 'inactive_users', 'created', null],
paginationLimit: 10,
language: <?php MintyPHP\Buffer::get('grid_lang'); ?>,
mapData: (data) => data.data.map((row) => [
{
label: row.description,
is_default: row.is_default ? 1 : 0
},
row.status,
row.theme ?? null,
row.sso ?? null,
row.active_users ?? 0,
row.inactive_users ?? 0,
row.created,
row.uuid
]),
actions: { enabled: false },
search: {
input: '#tenant-search',
param: 'search',
debounce: 250
},
urlSync: true,
rowDataset: (row) => ({
uuid: row?.cells?.[7]?.data
}),
rowDblClick: {
getUrl: (rowData) => {
const uuid = rowData?.cells?.[7]?.data;
return uuid ? new URL(`admin/tenants/edit/${uuid}`, appBase).toString() : '';
}
}
});
if (!gridConfig || !gridConfig.grid) {
console.warn('Tenants grid init failed');
return null;
}
return gridConfig;
};
initTenantsGrid();
</script>