Сменить логотип GLPI кажется делом пяти минут — пока следующее обновление всё не сотрёт. Настройка фирменного стиля через правку CSS ядра или шаблона входа работает в тот же день, но это долг, который созревает сам по себе: он исчезает при обновлении, а при переходе с GLPI 10 на 11 элементы DOM страницы входа получили новые имена, поэтому старый хак перестаёт применяться, не выдавая никакой ошибки. В этом руководстве показано, как сделать брендинг, который переживает обновление, где находится каждый элемент в GLPI 11 и какие подводные камни проявляются только в средах клиентов.
Брендинг в GLPI — это не только эстетика
GLPI с обликом организации снижает трение: конечный пользователь узнаёт, что перед ним Service Desk компании, а не незнакомая система, требующая входа. Но скрытая цена не в выборе правильного цвета — а в том, как применяется настройка. Есть три пути, и два из них потом обходятся дорого.
- Правка ядра (CSS, изображения или шаблоны внутри
glpi/css,glpi/picsилиtemplates/): работает, но следующийgit pullили распаковка релиза перезаписывает файлы. Настройка испаряется, и никто не помнит почему. - Тема/CSS, внедрённые извне: переживают обновление файлов, но привязаны к селекторам версии. Когда GLPI меняет структуру HTML — как это произошло при переходе с 10 на 11, — селектор перестаёт существовать, и CSS тихо превращается в мёртвую букву.
- Хук плагина: GLPI предоставляет официальные точки внедрения (
display_login,add_css,add_javascript). Модуль применяет фирменный стиль через эти хуки, не трогая ни одного файла ядра. Это единственный путь, который переживает обновление по своей конструкции.
Что можно настроить — и где находится каждый элемент
Каждый визуальный элемент GLPI находится в своём месте DOM, и замена без знания правильной цели порождает большинство заявок вида «я поменял, а ничего не появилось». Матрица ниже сопоставляет каждый элемент с его ключом конфигурации и реальным механизмом в GLPI 11:
| Элемент | Ключ конфигурации | Механизм в GLPI 11 | Типичная ловушка |
|---|---|---|---|
| Favicon | favicon_picture | <link rel="icon">, переписываемый через JS | Агрессивный кеш браузера удерживает старый значок |
| Заголовок вкладки | page_title | document.title | Страницы, задающие собственный заголовок, возвращают его |
| Логотип входа | login_picture | span.glpi-logo через content: url(...) | Нацеливание на .login-logo, которого в 11 нет |
| Фон входа | login_background | Только body.welcome-anonymous (.page-anonymous остаётся прозрачным) | Нацеливание на body.login-page, которого в 11 нет |
| Логотип заголовка | internal_picture | CSS в <head> через add_css | Путаница с логотипом входа (это разные цели) |
| Нижний колонтитул | footer_mode / footer_text | .copyright (стандартная тема 11) | Вставка несанированного HTML в пользовательский текст |
| Цвета интерфейса | color_* | CSS-переменные Tabler (--tblr-*) | Передача значения не в hex и внедрение сломанного CSS |
Ловушка GLPI 11: логотип входа — это не фон
При сопровождении сред клиентов классическая заявка звучит так: «мы обновили GLPI, и страница входа вернулась к стандартному виду». Почти всегда причина — настройка, сделанная прямо в ядре или в CSS, нацеленном на селекторы GLPI 10. При переходе на 11 шаблон страницы входа (page_card_notlogged.html.twig) был переписан: <body> стал body.welcome-anonymous, контейнер — .page-anonymous, а логотип перестал быть фоновым изображением — теперь это <span class="glpi-logo">, отрисовываемый через content: var(--logo). Мы выяснили это на практике 10 июня 2026 г., разбираясь, почему фон страницы входа у клиента просто не применялся после обновления: старого селектора body.login-page в DOM больше не было. В логе не было ошибок — CSS был корректен для версии, которая больше не работала. Поэтому мы стали выдавать правильные селекторы через хук, версионируя их вместе с модулем. Вот реальный CSS, который внедряет модуль, — и мёртвые селекторы, объясняющие исчезновение:
/* GLPI 11 - REAL login screen selectors (page_card_notlogged.html.twig) */
/* The logo is NOT a background: it is a <span class="glpi-logo"> with content: var(--logo) */
.page-anonymous .glpi-logo {
content: url("/plugins/nextool/ajax/module_ajax.php?module=branding&file=branding.picture.send.php&path=...") !important;
width: 240px !important;
height: 130px !important;
object-fit: contain !important; /* preserves the image aspect ratio */
}
/* The background goes ONLY on body.welcome-anonymous - NEVER on body.login-page.
Painting .page-anonymous too (a direct child) duplicated the artwork at the edges. */
body.welcome-anonymous {
background-image: url("...") !important;
background-size: cover !important; /* or contain, or 100% 100% (stretch) */
background-position: center center !important;
background-repeat: no-repeat !important;
background-attachment: fixed !important; /* sized by the viewport, not the page */
}
.page-anonymous { background-image: none !important; background-color: transparent !important; }
/* GLPI 10 selectors that NO LONGER exist in 11 (why the old hack vanishes with no error):
.login-logo -> now span.glpi-logo
body.login-page -> now body.welcome-anonymous */
Как модуль применяет стиль, не трогая ядро
На странице входа сессии нет, поэтому модуль подключается к хуку display_login и внедряет встроенный блок <style>/<script>. Изображения (favicon, логотип, фон) отдаются stateless-эндпоинтом, не требующим входа, с тремя защитами, о которых стоит сказать, потому что именно здесь небрежные плагины допускают утечки: расширение проверяется по белому списку (png, jpg, jpeg, gif, ico, webp; SVG убран в версии 1.4.0), а реальный тип файла проверяется по содержимому; путь разрешается через realpath() и сверяется с префиксом каталога загрузки (блокировка path traversal); файл сохраняется под случайным именем, сгенерированным сервером, без использования присланного имени, чтобы не было коллизий и перезаписи. На внутренних страницах, уже после аутентификации, цвета, логотип заголовка и скрытый нижний колонтитул добавляются как CSS в <head> через хук add_css, а favicon, заголовок и пользовательский нижний колонтитул — через хук add_javascript.
Деталь производительности, которая видна только при замерах: внутренний JS отдаётся с Cache-Control: private, max-age=86400, а изображения — с public, max-age=31536000, immutable (один год: каждая загрузка создаёт файл с новым именем), но без ETag. Причина конкретна: при одновременном использовании ETag и max-age Chrome выполнял повторную проверку (HTTP 304) при каждой загрузке страницы, и каждый 304 всё равно оплачивал полный bootstrap GLPI на сервере. Хеш конфигурации входит в параметр fv= URL, поэтому сохранение настроек меняет URL и сразу инвалидирует кеш, без повторной проверки.
Настройка за пять шагов
- Активируйте модуль Branding в каталоге модулей NexTool.
- На вкладке Настройки загрузите favicon, логотип входа и логотип заголовка; задайте ширину и высоту каждого логотипа (принимаются
px,em,rem,%,vw,vh). - Загрузите фон страницы входа и настройте заголовок вкладки браузера.
- Выберите поведение нижнего колонтитула: оставить исходный, скрыть или заменить текстом (HTML ограничен безопасными тегами).
- По желанию: задайте цвета интерфейса через CSS-переменные (модуль проверяет каждое значение как hex перед выводом, предотвращая CSS injection). Перезагрузите GLPI, чтобы увидеть результат.
Аудит: кто что изменил
Каждое действие — активация, деактивация, сохранение настроек, загрузка или удаление изображения — записывается в таблицу журнала модуля. В среде с несколькими администраторами этот след отвечает на вопрос «кто сменил логотип в пятницу вечером», не полагаясь на память. Диагностический запрос:
-- Branding module audit trail: who changed the visual identity and when
SELECT l.date_creation AS when_at,
u.name AS user_name,
l.action AS action, -- save_config (image uploads and deletions go in the detail), toggle_enable/disable
l.detail AS detail
FROM glpi_plugin_nextool_branding_log l
LEFT JOIN glpi_users u ON u.id = l.users_id
ORDER BY l.date_creation DESC
LIMIT 20;
Подводные камни на практике
- Favicon не меняется — браузер агрессивно кеширует favicon, по вкладке и по сессии. После загрузки нового значка помогает жёсткое обновление (Ctrl+Shift+R) или закрытие и повторное открытие вкладки; это не ошибка модуля.
- SVG не принимается — начиная с версии 1.4.0 модуль отклоняет SVG для логотипа и favicon, потому что формат может содержать встроенный скрипт, а изображения модуля отдаются без входа. Используйте PNG с прозрачным фоном (или WEBP) для логотипа и PNG или ICO для favicon; сервер проверяет реальное содержимое файла, поэтому SVG, переименованный в .png, тоже будет отклонён.
- Нижний колонтитул со вставленным HTML — пользовательский текст колонтитула проходит санацию, которая разрешает лишь несколько тегов (
a, b, strong, i, em, span, br). Вставленный целиком блок маркетингового HTML просто не отобразится так, как вы ожидаете. - Пропорции логотипа — фиксированные ширина и высота без учёта исходных пропорций искажают изображение. Модуль применяет
object-fit: containименно для того, чтобы не растягивать, но результат стоит проверить на экране. - Обновление с 10 на 11 — если у клиента был ручной брендинг в GLPI 10, в 11 он исчезнет из-за сменившихся селекторов. Рассматривайте брендинг как часть плана обновления, а не как сюрприз после миграции.
Хотите унифицировать фирменный стиль GLPI во всех средах, которые вы администрируете, не возвращаясь к этой теме при каждом обновлении? Модуль Branding бесплатен и применяет фирменный стиль через штатные хуки. А если вы предпочитаете передать внедрение и сопровождение GLPI на аутсорс, NexTool поддерживает среды клиентов с версионированным брендингом, устойчивым к обновлениям.
Проверено командой NexTool Solutions.