Custom content

Add custom content to your Kimai installation

A Kimai plugin which allows adding custom content for:

  • Stylesheet (embedded in all pages)
  • Javascript (embedded in all pages, except security screens)
  • A global warning message, shown to every logged-in user
  • An entire new page to display (markdown formatted) information for your users

News page

You can edit two fields:

  • The news title is the name of the menu entry
  • The content for the page (markdown is supported for formatting)

Deactivate the news: if the title is empty, the menu will be hidden.

Alert

That's how the **alert / warning message** looks like. You can even include _markdown_ and [links](/en/custom-content-news) !

Javascript

Make your tables horizontal scrollable if they are too wide:

function scrollableTable() {
    [].slice.call(document.querySelectorAll('div.dataTables_wrapper')).map((element) => {
        element.style.overflowX = 'scroll';
    });
}
document.addEventListener('kimai.initialized', scrollableTable);
document.addEventListener('kimai.reloadedContent', scrollableTable);

Remove the activity from the timesheet form. You have to create one global activity and grab the ID from the URL, replace 5095 in the code with your new ID:

document.addEventListener('show.bs.modal', (e) => {
    const activity = e.srcElement.querySelector('#timesheet_edit_form_activity');
    if (activity !== null) {
        activity.value = '5095'; 
        activity.dispatchEvent(new Event('change'));
        const activityRow = e.srcElement.querySelector('.timesheet_edit_form_row_activity');
        if (activityRow !== null && !activityRow.classList.contains('d-none')) {
            activityRow.classList.add('d-none');
        }
    }
});

Make the header fixed at the screen top and remove that stickyness from the table headers (both are not possible right now):

function stickyHeader() {
    [].slice.call(document.querySelectorAll('thead.sticky-top')).map((element) => {
        element.classList.toggle('sticky-top');
    });
    const header = document.querySelector('div.page header.navbar');
    if (!header.classList.contains('sticky-top')) {
        header.classList.toggle('sticky-top');
    }
}
document.addEventListener('kimai.initialized', stickyHeader);
document.addEventListener('kimai.reloadedContent', stickyHeader);

Hide the dashboard edit button (for all non super-admins only):

document.addEventListener('kimai.initialized', function (event) {
    if (event.detail.kimai.getUser().isSuperAdmin() && document.querySelector('section.dashboard') !== null) {
        document.querySelector('.page-header').style.setProperty("display", "none", "important");
    }
});

React on global events and utilize the Kimai Javascript API:

document.addEventListener('kimai.initialized', function(event) {
    alert(event.detail.kimai.getTranslation().get('confirm'));
});

Select an Activity e.g. to simulate a global default activity. The ID 6451 is the Activity ID to be selected (should be global).

document.addEventListener('show.bs.modal', (e) => {
    const activity = e.srcElement.querySelector('#timesheet_edit_form_activity');
    if (activity !== null) {
        activity.value = '6451'; 
        activity.dispatchEvent(new Event('change'));
    }
});

Set the “activity description” upon selection as “timesheet description”:

document.addEventListener('show.bs.modal', (e) => {
    const desc = e.srcElement.querySelector('#timesheet_edit_form_description');
    if (desc !== null) {
        e.srcElement.querySelector('#timesheet_edit_form_activity').addEventListener('change', (e) => {
            kimai.getPlugin('api').get('/api/activities/' + e.target.value, {}, function(data) {
                desc.value = data.comment;
            });
        });
    }
});

Make the timesheet description field mandatory:

document.addEventListener('show.bs.modal', (e) => {
    const desc = e.srcElement.querySelector('#timesheet_edit_form_description');
    if (desc !== null) {
        desc.required = true;
        const label = e.srcElement.querySelector('label[for=timesheet_edit_form_description]');
        if (label !== null && !label.classList.contains('required')) { label.classList.add('required'); }
    }
});

Always deactivate the export checkbox:

document.addEventListener('kimai.initialized', function(event) {
    const ec = document.getElementById('markAsExportedCheck');
    if (ec !== null && ec.checked) { ec.checked = false; document.getElementById('markAsExported').value = 0; }
});

Deactivate a certain field by ID in a modal if the current user is not an Admin or SuperAdmin:

function deactivateField(source, selector) {
    const field = source.querySelector(selector);
    if (field !== null) { field.disabled = true; if (field.tomselect) { field.tomselect.disable(); } }
}

document.addEventListener('kimai.initialized', function(event) {
    const kimai = event.detail.kimai;
    if (!kimai.getUser().isAdmin() && !kimai.getUser().isSuperAdmin()) {
        document.addEventListener('show.bs.modal', (e) => { deactivateField(e.srcElement, '#expense_form_metaFields_status_value'); });
        deactivateField(document, '#expense_form_metaFields_status_value');
    }
});

Automatically login with SAML:

document.querySelector('body.login-page #social-login-button')?.click();

Always expand extended timesheet settings:

document.addEventListener('show.bs.modal', (e) => { 
    e.srcElement.querySelector('#timesheet_extended_settings a[data-bs-toggle]')?.click(); 
});

Always set a static time in the timesheet screen:

document.addEventListener('show.bs.modal', (e) => {
    const time = e.srcElement.querySelector('#timesheet_edit_form_begin_time');
    if (time !== null) { time.value = '01:00'; }
});

Set a custom browser title:

document.title = 'My fancy company';

Add IDs to all <h3> header of your custom news page (allowing to use jump anchor links):

document.addEventListener('kimai.initialized', function (event) {
    if (document.getElementById('custom-content-news') !== null) {
        const headings = document.querySelectorAll('#custom-content-news .markdown h3');
        let counter = 0;
        for (const element of headings) {
            element.id = 'news' + (++counter);
        }
    }
});

Make the tags field mandatory:

document.addEventListener('show.bs.modal', (e) => {
    const field = e.srcElement.querySelector('#timesheet_edit_form_tags');
    if (field !== null) {
        field.required = true;
        const label = e.srcElement.querySelector('label[for=timesheet_edit_form_tags-ts-control]');
        if (label !== null && !label.classList.contains('required')) { label.classList.add('required'); }
    }
});

CSS

Hiding a menu:

ul.sidebar-menu li#calendar { display:none; }

Hiding the colored dots:

i.dot, span.dot {display:none !important;}

Activating horizontal scrolling on data-tables:

.box .dataTables_wrapper {
    overflow-x: auto;
    min-height: .01%;
}
.box .dataTables_wrapper > .row {
    margin-left: 0;
    margin-right: 0;
}
.box .dataTables_wrapper > .row > .col-sm-12 {
    padding-left: 0;
    padding-right: 0;
}

Deactivate the background blur for modals, which might be problematic in RDP sessions:

.modal-blur { 
    -webkit-backdrop-filter: blur(0px) !important;
    backdrop-filter: blur(0px) !important;
}

Switching the order of save and cancel buttons:

.modal-footer button[type=submit], .box-footer input[type=submit] {
    float: right !important
}
.modal-footer .btn-cancel, .box-footer input[type=reset] {
    float: left !important
}

Remove the red dotted lines between overlapping timesheet entries:

table.dataTable tr.overlapping {
    border-top: none;
}

Highlight active timesheet records:

tr.recording {
    background-color: #ffa059 !important;
}

Hiding the billable field:

label[for=timesheet_edit_form_billable] { display:none; }

Hiding the navigation icons:

.sidebar-menu>li>ul>li>a>i, .sidebar-menu>li>a>i {
    display: none;
}
body.sidebar-collapse .sidebar-menu>li>ul>li>a>i, body.sidebar-collapse .sidebar-menu>li>a>i {
    display: inline-block;
}

Remove the title on security screens (login, reset password):

.login-logo, .register-logo { visibility: hidden; }

Setting a plain background color for security screens:

.login-logo, .register-logo { visibility: hidden; }
.layout-boxed body, .layout-boxed html, body, html { background: #000000; }
.login-page, .register-page { background: none; }

Hide the header on mobile devices:

@media (max-width: 767px) {
    .main-header .logo {
        display: none;
    }
    .fixed .content-wrapper, .fixed .right-side, .control-sidebar, .main-sidebar {
        padding-top: 50px;
    }
}

Permissions

Permission Name Description
edit_custom_content show the “custom content” administration screen
js_custom_content edit the additional javascript
css_custom_content edit the additional stylesheet
alert_custom_content edit the page wide warning message
news_custom_content edit the additional news page

By default, these are assigned to each user with the role ROLE_SUPER_ADMIN.

Top