Files
chatballs/apps/internal-ui/src/layout/styles.css
T
AndreyandClaude Fable 5.1 5396dda8ad ✨ feat(updates): обновление установки из интерфейса
Установка раз в шесть часов и по кнопке проверяет страницу релизов
GitHub. Администратор установки видит баннер о новой версии и ставит
её одной кнопкой; карточка «Обновления» — в «Настройки → Платформа».

Установку выполняет отдельный сервис updater с Docker-сокетом:
backend-app общается с ним только файлами в томе chatballs-updates
(heartbeat, request.json, status.json). Updater принимает лишь релизы
своего репозитория с образами по digest и применяет compose.yaml в
одноразовом контейнере-помощнике, после чего кладёт файл в каталог
установки. Запрос установки попадает в аудит.

Релизный workflow собирает образ updater и пришпиливает его в
compose.yaml; версия бэкенда зашивается в образ (CHATBALLS_VERSION).
Миграции updates.0001 и tenancy.0034 (гранты на таблицу состояния).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 00:43:11 +03:00

827 lines
15 KiB
CSS

.hub-shell {
height: 100vh;
display: flex;
overflow: hidden;
background: var(--n-11);
}
/* Страницы вне чата не адаптированы под мобильный — держат десктопную ширину.
Чат (кадры M1/M2) адаптивен и ограничение снимает. */
.hub-shell:not(.is-chat-route) {
min-width: 1024px;
}
/* Сайдбар 220px (дизайн-базлайн v2, решение 1): фон surface2, без верхней панели. */
.hub-sidebar {
position: relative;
width: var(--sidebar-width, 220px);
height: 100vh;
display: flex;
flex: none;
flex-direction: column;
background: var(--surface-sidebar);
border-right: 1px solid var(--n-8);
}
/* Ручка изменения ширины панели (сайдбар, список диалогов): невидимая полоса
на правом крае, подсвечивается при наведении и во время перетаскивания. */
.pane-resizer {
position: absolute;
top: 0;
right: -3px;
bottom: 0;
width: 6px;
z-index: 5;
cursor: col-resize;
}
.pane-resizer::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 2px;
width: 2px;
background: var(--primary);
opacity: 0;
transition: opacity 0.12s;
}
.pane-resizer:hover::after,
.is-resizing > .pane-resizer::after {
opacity: 1;
}
@media (max-width: 1024px) {
.pane-resizer {
display: none;
}
}
.hub-sidebar-body {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
/* Рейка 60px (дизайн-базлайн v2, кадр S2): показывается только на ≤1024px. */
.hub-rail {
display: none;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 14px 0 12px;
height: 100%;
}
.hub-rail .hub-brand-mark {
margin-bottom: 8px;
}
.hub-rail-button {
width: 38px;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
border: 0;
border-radius: 9px;
color: var(--n-4);
background: transparent;
}
.hub-rail-button:hover {
background: var(--n-9);
}
.hub-rail-button.is-active {
color: var(--primary-text);
background: var(--primary-bg);
}
.hub-rail-spacer {
flex: 1;
}
.hub-rail-avatar {
width: 34px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
border: 0;
border-radius: 50%;
color: #ffffff;
background: var(--primary);
font-size: 12px;
font-weight: 600;
}
.hub-rail-backdrop {
display: none;
position: fixed;
inset: 0;
z-index: 19;
border: 0;
background: rgba(0, 0, 0, 0.25);
}
@media (max-width: 1024px) {
.hub-sidebar {
width: 60px !important;
}
.hub-rail {
display: flex;
}
.hub-sidebar-body {
display: none;
}
.hub-sidebar.is-rail-expanded .hub-rail-backdrop {
display: block;
}
.hub-sidebar.is-rail-expanded .hub-sidebar-body {
display: flex;
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 248px;
z-index: 20;
background: var(--surface-card);
border-right: 1px solid var(--n-8);
box-shadow: 8px 0 24px rgba(0, 0, 0, 0.1);
}
}
.hub-brand {
width: 100%;
height: 56px;
display: flex;
align-items: center;
flex: none;
gap: 10px;
padding: 14px 14px 10px;
}
.hub-brand-mark {
border: 0;
padding: 0;
}
/* Переключатель организации (A1): имя + шеврон, 32px. */
.hub-brand-switch {
flex: 1;
min-width: 0;
height: 32px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
padding: 0 6px 0 8px;
border: 0;
border-radius: 8px;
background: transparent;
color: var(--n-1);
font-size: 14px;
font-weight: 700;
text-align: left;
}
.hub-brand-switch:hover {
background: var(--n-9);
}
.hub-brand-switch span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hub-brand-switch svg {
flex: none;
color: var(--n-4);
}
/* Логотип — сам по себе, без подложки: цвет текста темы. */
.hub-brand-mark {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
flex: none;
color: var(--n-1);
background: transparent;
border-radius: 8px;
}
.hub-brand-mark svg {
width: 26px;
height: 26px;
}
.hub-brand-mark.has-logo {
overflow: hidden;
color: inherit;
background: var(--surface-card);
border: 1px solid var(--n-7);
}
.hub-brand-mark img {
width: 100%;
height: 100%;
display: block;
object-fit: contain;
}
.profile-link strong {
display: block;
overflow: hidden;
color: var(--n-1);
font-size: 13px;
font-weight: 600;
line-height: 1.3;
text-overflow: ellipsis;
white-space: nowrap;
}
.profile-link small {
display: block;
overflow: hidden;
color: var(--n-4);
font-size: 11.5px;
font-weight: 400;
line-height: 1.3;
text-overflow: ellipsis;
white-space: nowrap;
}
.hub-nav {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
overflow-y: auto;
padding: 4px 10px 8px;
}
.hub-nav-item {
position: relative;
width: 100%;
display: flex;
align-items: center;
flex: none;
gap: 10px;
height: 36px;
padding: 0 10px;
color: var(--n-2);
background: transparent;
border: none;
border-radius: 8px;
font-size: 13.5px;
font-weight: 500;
text-align: left;
text-decoration: none;
}
.profile-link {
position: relative;
width: 100%;
display: flex;
align-items: center;
gap: 10px;
color: var(--n-3);
background: transparent;
border: none;
text-align: left;
}
.hub-nav-item:not(:disabled):hover,
.profile-link:hover {
color: var(--n-2);
background: var(--n-9);
}
.hub-nav-item.is-active,
.profile-link.is-active {
color: var(--primary-text);
background: var(--primary-bg);
font-weight: 600;
}
.hub-subnav {
display: flex;
flex-direction: column;
gap: 2px;
padding: 2px 0 2px 30px;
}
.hub-subnav-item {
width: 100%;
display: flex;
align-items: center;
padding: 6px 12px;
border: 0;
border-radius: 7px;
color: var(--n-3);
background: transparent;
font-size: 13px;
font-weight: 500;
line-height: 1.4;
text-align: left;
}
.hub-subnav-item:not(:disabled):hover {
color: var(--n-2);
background: var(--n-9);
}
.hub-subnav-item.is-active {
color: var(--primary-text);
background: var(--primary-bg);
font-weight: 600;
}
.hub-subnav-item:disabled {
color: var(--n-5);
cursor: not-allowed;
}
.hub-nav-chevron {
display: inline-flex;
margin-left: auto;
transition: transform 0.15s ease;
}
.hub-nav-section-toggle[aria-expanded="false"] .hub-nav-chevron {
transform: rotate(-90deg);
}
.hub-subnav-item b {
min-width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: auto;
padding: 0 6px;
color: #ffffff;
background: var(--warning);
border-radius: 10px;
font-size: 11px;
font-weight: 700;
}
/* Бейдж очереди у «Чат» (A1): жёлтая плашка 20px. */
.hub-nav-badge {
min-width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: auto;
padding: 0 6px;
color: var(--warning-text);
background: var(--warning-bg);
border: 1px solid var(--warning-border);
border-radius: 10px;
font-size: 11px;
font-weight: 700;
}
.hub-nav-group {
padding: 14px 12px 6px;
color: var(--n-5);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.06em;
}
.hub-nav-divider {
flex: none;
height: 1px;
margin: 8px 2px 4px;
background: var(--n-8);
}
/* Дерево «Диалоги» сотрудника (дизайн-базлайн v2 §3): сайдбар = фильтр чата. */
.chat-scope-tree {
padding-bottom: 12px;
}
.chat-scope-head {
display: flex;
align-items: center;
gap: 9px;
height: 34px;
padding: 0 8px;
color: var(--n-1);
font-size: 13.5px;
font-weight: 700;
}
.chat-scope-section {
display: flex;
align-items: center;
gap: 8px;
height: 30px;
margin: 6px 0 0 16px;
padding: 0 10px 0 12px;
color: var(--n-4);
font-size: 12.5px;
font-weight: 600;
}
.chat-scope-item {
display: flex;
align-items: center;
gap: 9px;
height: 32px;
padding: 0 10px 0 12px;
border: 0;
border-radius: 8px;
color: var(--n-2);
background: transparent;
font-size: 13px;
font-weight: 500;
text-align: left;
}
.chat-scope-item.is-top {
height: 34px;
margin-left: 16px;
}
.chat-scope-item.is-nested {
margin-left: 34px;
}
.chat-scope-item:hover {
background: var(--n-9);
}
.chat-scope-item.is-active {
color: var(--primary-text);
background: var(--primary-bg);
font-weight: 600;
}
.chat-scope-item > span:not(.chat-scope-agent) {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-scope-item small {
color: var(--n-5);
font-size: 11.5px;
font-weight: 600;
}
.chat-scope-item.is-active small {
color: var(--primary-text);
}
.chat-scope-dot {
flex: none;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--primary);
}
.chat-scope-dot.is-muted {
background: var(--n-5);
}
.chat-scope-agent {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 5px;
color: var(--ai);
background: color-mix(in srgb, var(--ai) 14%, var(--surface-card));
}
.profile-link {
margin: 0;
padding: 12px 14px;
border-top: 1px solid var(--n-8);
border-radius: 0;
}
.profile-link:hover {
background: var(--n-9);
}
.profile-link > span:nth-child(2) {
min-width: 0;
flex: 1;
}
.profile-avatar {
position: relative;
flex: none;
display: inline-flex;
}
.profile-avatar .avatar {
width: 34px;
height: 34px;
}
/* Онлайн-точка: сотрудник в системе. */
.profile-avatar i {
position: absolute;
right: -1px;
bottom: -1px;
width: 10px;
height: 10px;
border: 2px solid var(--surface-sidebar);
border-radius: 50%;
background: var(--success);
}
.profile-more {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
color: var(--n-4);
border-radius: 7px;
}
.profile-link:hover .profile-more {
background: var(--n-8);
}
.profile-more {
position: relative;
}
/* Непрочитанные уведомления — точка на кнопке меню профиля. */
.profile-unread {
position: absolute;
top: 4px;
right: 4px;
width: 7px;
height: 7px;
background: var(--error);
border: 1.5px solid var(--surface-sidebar);
border-radius: 50%;
}
.profile-menu-badge {
min-width: 18px;
height: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: auto;
padding: 0 5px;
color: #ffffff;
background: var(--error);
border-radius: 9px;
font-size: 10.5px;
font-weight: 700;
}
.avatar {
width: 32px;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
flex: none;
overflow: hidden;
color: #ffffff;
background: var(--primary);
border-radius: 50%;
font-size: 13px;
font-weight: 600;
}
.avatar.has-photo {
background: var(--n-9);
}
.avatar img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.hub-main {
min-width: 0;
height: 100vh;
display: flex;
flex: 1;
flex-direction: column;
}
/* --- Блок «Запуск» (SPEC-CHATBALLS-0031 §5, дизайн-базлайн v2 A1) --- */
.launch-checklist {
flex: none;
margin: 6px 10px 8px;
padding: 12px 12px 10px;
border: 1px solid var(--border-default);
border-radius: 10px;
background: var(--surface-card);
}
.launch-checklist-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 10px;
}
.launch-checklist-head strong {
color: var(--text-heading);
font-size: 13px;
font-weight: 700;
}
.launch-checklist-head small {
margin-left: 7px;
color: var(--text-tertiary);
font-size: 11.5px;
font-weight: 500;
}
.launch-checklist-head button {
width: 22px;
height: 22px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--text-disabled);
font-size: 15px;
line-height: 1;
cursor: pointer;
}
.launch-checklist-head button:hover {
background: var(--n-9);
color: var(--text-body);
}
.launch-checklist-progress {
height: 3px;
border-radius: 2px;
background: var(--n-8);
margin-bottom: 12px;
overflow: hidden;
}
.launch-checklist-progress i {
display: block;
height: 100%;
background: var(--primary);
}
.launch-checklist-steps {
display: flex;
flex-direction: column;
gap: 8px;
}
.launch-checklist-step {
display: flex;
align-items: center;
gap: 9px;
color: var(--text-tertiary);
font-size: 12.5px;
}
.launch-checklist-step.is-current {
color: var(--text-heading);
font-weight: 600;
}
.launch-checklist-step.is-done span:last-child {
text-decoration: line-through;
}
.launch-checklist-mark {
width: 16px;
height: 16px;
flex: none;
border: 1.5px solid var(--border-strong);
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
color: #fff;
}
.launch-checklist-step.is-done .launch-checklist-mark {
border-color: var(--primary);
background: var(--primary);
}
.launch-checklist-action {
margin-top: 11px;
width: 100%;
height: 30px;
border: none;
border-radius: 7px;
background: var(--primary);
color: #fff;
font-size: 12.5px;
font-weight: 600;
cursor: pointer;
font-family: inherit;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.launch-checklist-action:hover {
background: var(--primary-hover);
}
/* Полоса установки демо-данных: пока worker наполняет организацию, человек
видит причину пустых разделов, а не гадает. */
.demo-install-banner {
display: flex;
flex: none;
align-items: center;
gap: 9px;
padding: 9px 20px;
border-bottom: 1px solid var(--n-8);
background: var(--primary-bg);
color: var(--primary-text);
font-size: 13px;
font-weight: 500;
}
.demo-install-banner.is-done {
background: var(--success-bg);
color: var(--success-text);
}
.demo-install-banner.is-failed {
background: var(--error-bg);
color: var(--error-text);
}
.update-banner a.link {
flex: none;
font-size: 13px;
}
.demo-install-banner > svg {
flex: none;
}
.demo-install-banner > span {
flex: 1;
min-width: 0;
}
.demo-install-banner button {
height: 26px;
padding: 0 11px;
border: 1px solid currentColor;
border-radius: 7px;
background: transparent;
color: inherit;
font-family: inherit;
font-size: 12.5px;
font-weight: 600;
cursor: pointer;
}
.demo-install-banner .demo-install-close {
display: flex;
width: 24px;
height: 24px;
align-items: center;
justify-content: center;
padding: 0;
border: 0;
opacity: 0.65;
}
.demo-install-banner .demo-install-close:hover { opacity: 1; }