diff --git a/.drone.yml b/.drone.yml
index b79f433..e1cfb9f 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -70,6 +70,32 @@ steps:
from_secret: HARBOR_PASSWORD
no_push_metadata: true
+# --- Сборка Keycloak темы ---
+- name: build-keycloak-theme
+ image: plugins/kaniko
+ when:
+ changeset:
+ includes:
+ - keycloak-theme/**
+ - .drone.yml
+ excludes:
+ - keycloak-theme/README.md
+ - keycloak-theme/**/*.md
+ settings:
+ registry: registry.vigdorov.ru
+ repo: registry.vigdorov.ru/library/keycloak-team-planner
+ dockerfile: keycloak-theme/Dockerfile
+ context: keycloak-theme
+ tags:
+ - ${DRONE_COMMIT_SHA:0:7}
+ - "26.5.0"
+ - latest
+ username:
+ from_secret: HARBOR_USER
+ password:
+ from_secret: HARBOR_PASSWORD
+ no_push_metadata: true
+
# ============================================================
# ДЕПЛОЙ (только после завершения ОБЕИХ сборок)
# ============================================================
@@ -194,6 +220,42 @@ steps:
fi
- echo "✅ Frontend deployed to PROD (image:$IMAGE_TAG)"
+# --- Развертывание Keycloak темы ---
+- name: deploy-keycloak-theme
+ image: alpine/k8s:1.28.2
+ depends_on:
+ - build-keycloak-theme
+ when:
+ changeset:
+ includes:
+ - keycloak-theme/**
+ - .drone.yml
+ excludes:
+ - keycloak-theme/README.md
+ - keycloak-theme/**/*.md
+ environment:
+ KUBE_CONFIG_CONTENT:
+ from_secret: KUBE_CONFIG
+ commands:
+ - mkdir -p ~/.kube
+ - echo "$KUBE_CONFIG_CONTENT" > ~/.kube/config
+ - chmod 600 ~/.kube/config
+ - sed -i "s|https://127.0.0.1:6443|https://10.10.10.100:6443|g" ~/.kube/config
+ - export KEYCLOAK_NAMESPACE="auth"
+ - export IMAGE_TAG="${DRONE_COMMIT_SHA:0:7}"
+ - export KEYCLOAK_IMAGE="registry.vigdorov.ru/library/keycloak-team-planner:$IMAGE_TAG"
+ - kubectl cluster-info
+ - kubectl set image statefulset/keycloak-keycloakx keycloak-keycloakx=$KEYCLOAK_IMAGE -n $KEYCLOAK_NAMESPACE
+ - echo "📋 Waiting for rollout..."
+ - |
+ if ! kubectl rollout status statefulset/keycloak-keycloakx -n $KEYCLOAK_NAMESPACE --timeout=180s; then
+ echo "❌ Rollout failed! Collecting diagnostics..."
+ kubectl get pods -n $KEYCLOAK_NAMESPACE -l app.kubernetes.io/name=keycloakx -o wide
+ kubectl describe statefulset keycloak-keycloakx -n $KEYCLOAK_NAMESPACE
+ exit 1
+ fi
+ - echo "✅ Keycloak theme deployed (image:$IMAGE_TAG)"
+
---
kind: pipeline
type: kubernetes
diff --git a/k8s/backend-deployment.yaml b/k8s/backend-deployment.yaml
index 568390c..ffe1599 100644
--- a/k8s/backend-deployment.yaml
+++ b/k8s/backend-deployment.yaml
@@ -43,6 +43,8 @@ spec:
secretKeyRef:
name: team-planner-secrets
key: db-password
+ - name: KEYCLOAK_REALM_URL
+ value: "https://auth.vigdorov.ru/realms/team-planner"
resources:
requests:
memory: "256Mi"
diff --git a/keycloak-theme/Dockerfile b/keycloak-theme/Dockerfile
new file mode 100644
index 0000000..357fb93
--- /dev/null
+++ b/keycloak-theme/Dockerfile
@@ -0,0 +1,4 @@
+FROM quay.io/keycloak/keycloak:26.5.0
+
+# Копируем кастомную тему
+COPY team-planner /opt/keycloak/themes/team-planner
diff --git a/keycloak-theme/README.md b/keycloak-theme/README.md
new file mode 100644
index 0000000..d53c9c4
--- /dev/null
+++ b/keycloak-theme/README.md
@@ -0,0 +1,102 @@
+# Team Planner Keycloak Theme
+
+Кастомная тема для Keycloak, стилизованная под основное приложение Team Planner (Material UI).
+
+## Структура
+
+```
+team-planner/
+├── login/
+│ ├── theme.properties # Настройки темы
+│ ├── login.ftl # Шаблон страницы входа
+│ ├── resources/
+│ │ └── css/
+│ │ └── login.css # Стили MUI
+│ └── messages/
+│ └── messages_ru.properties # Русские переводы
+```
+
+## Установка
+
+### Вариант 1: Volume mount (рекомендуется для dev)
+
+```yaml
+# docker-compose.yml
+services:
+ keycloak:
+ image: quay.io/keycloak/keycloak:latest
+ volumes:
+ - ./keycloak-theme/team-planner:/opt/keycloak/themes/team-planner
+```
+
+### Вариант 2: Dockerfile (рекомендуется для production)
+
+```dockerfile
+FROM quay.io/keycloak/keycloak:latest
+
+COPY team-planner /opt/keycloak/themes/team-planner
+```
+
+### Вариант 3: Kubernetes ConfigMap
+
+```bash
+# Создать ConfigMap из директории темы
+kubectl create configmap keycloak-theme \
+ --from-file=team-planner/login/ \
+ -n keycloak
+
+# Примонтировать в deployment
+```
+
+## Активация темы
+
+1. Войдите в Keycloak Admin Console
+2. Выберите realm `team-planner`
+3. Перейдите в **Realm Settings** → **Themes**
+4. В поле **Login theme** выберите `team-planner`
+5. Нажмите **Save**
+
+Или через Keycloak CLI:
+
+```bash
+/opt/keycloak/bin/kcadm.sh update realms/team-planner \
+ -s loginTheme=team-planner \
+ --server http://localhost:8080 \
+ --realm master \
+ --user admin
+```
+
+## Разработка
+
+Для разработки темы:
+
+1. Запустите Keycloak с примонтированной темой
+2. Включите кэширование темы в dev режиме:
+ ```
+ KC_SPI_THEME_STATIC_MAX_AGE=-1
+ KC_SPI_THEME_CACHE_THEMES=false
+ KC_SPI_THEME_CACHE_TEMPLATES=false
+ ```
+3. Изменения в CSS/FTL применяются после обновления страницы
+
+## Кастомизация
+
+### Цвета
+
+Основные цвета определены в CSS переменных в `login.css`:
+
+```css
+:root {
+ --primary-color: #1976d2; /* MUI primary */
+ --primary-hover: #1565c0;
+ --text-primary: rgba(0, 0, 0, 0.87);
+ --text-secondary: rgba(0, 0, 0, 0.6);
+ --background: #f5f5f5;
+ --surface: #ffffff;
+}
+```
+
+### Тексты
+
+Русские переводы в `messages/messages_ru.properties`.
+Для других языков создайте `messages_XX.properties`.
diff --git a/keycloak-theme/team-planner/login/login.ftl b/keycloak-theme/team-planner/login/login.ftl
new file mode 100644
index 0000000..644d7a0
--- /dev/null
+++ b/keycloak-theme/team-planner/login/login.ftl
@@ -0,0 +1,82 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','password') displayInfo=realm.password && realm.registrationAllowed && !registrationDisabled??; section>
+ <#if section = "header">
+
+
Team Planner
+
Приложение для управления бэклогом идей команды
+
+ <#elseif section = "form">
+
+ <#elseif section = "info">
+
+ <#if realm.password && realm.registrationAllowed && !registrationDisabled??>
+
+ #if>
+
+ Для получения доступа обратитесь к Николаю Вигдорову
+
+
+ <#elseif section = "socialProviders">
+ <#if realm.password && social.providers??>
+
+ #if>
+ #if>
+@layout.registrationLayout>
diff --git a/keycloak-theme/team-planner/login/messages/messages_ru.properties b/keycloak-theme/team-planner/login/messages/messages_ru.properties
new file mode 100644
index 0000000..12fc21b
--- /dev/null
+++ b/keycloak-theme/team-planner/login/messages/messages_ru.properties
@@ -0,0 +1,15 @@
+# Russian translations for Team Planner theme
+loginAccountTitle=Вход в Team Planner
+loginTitle=Вход
+loginTitleHtml=Вход
+usernameOrEmail=Имя пользователя или email
+username=Имя пользователя
+email=Email
+password=Пароль
+rememberMe=Запомнить меня
+doLogIn=Войти
+doRegister=Зарегистрироваться
+doForgotPassword=Забыли пароль?
+noAccount=Нет аккаунта?
+invalidUserMessage=Неверное имя пользователя или пароль.
+invalidPasswordMessage=Неверное имя пользователя или пароль.
diff --git a/keycloak-theme/team-planner/login/resources/css/login.css b/keycloak-theme/team-planner/login/resources/css/login.css
new file mode 100644
index 0000000..76c3503
--- /dev/null
+++ b/keycloak-theme/team-planner/login/resources/css/login.css
@@ -0,0 +1,356 @@
+/* Team Planner - Keycloak Login Theme */
+/* Стилизация под MUI (Material Design) */
+
+:root {
+ --primary-color: #1976d2;
+ --primary-hover: #1565c0;
+ --primary-light: #42a5f5;
+ --text-primary: rgba(0, 0, 0, 0.87);
+ --text-secondary: rgba(0, 0, 0, 0.6);
+ --background: #f5f5f5;
+ --surface: #ffffff;
+ --error: #d32f2f;
+ --border-radius: 4px;
+ --shadow: 0px 3px 3px -2px rgba(0,0,0,0.2), 0px 3px 4px 0px rgba(0,0,0,0.14), 0px 1px 8px 0px rgba(0,0,0,0.12);
+}
+
+/* Reset и базовые стили */
+* {
+ box-sizing: border-box;
+}
+
+body {
+ font-family: "Roboto", "Helvetica", "Arial", sans-serif;
+ background-color: var(--background);
+ margin: 0;
+ padding: 0;
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* Скрываем стандартный header Keycloak */
+#kc-header {
+ display: none;
+}
+
+#kc-header-wrapper {
+ display: none;
+}
+
+/* Основной контейнер */
+.login-pf {
+ background-color: var(--background);
+}
+
+#kc-content {
+ width: 100%;
+ max-width: 400px;
+ margin: 0 auto;
+}
+
+#kc-content-wrapper {
+ width: 100%;
+}
+
+/* Карточка логина */
+#kc-form-wrapper,
+.card-pf {
+ background: var(--surface);
+ border-radius: var(--border-radius);
+ box-shadow: var(--shadow);
+ padding: 48px;
+ width: 100%;
+ max-width: 400px;
+ margin: 20px;
+}
+
+/* Заголовок */
+#kc-page-title {
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: var(--text-primary);
+ text-align: center;
+ margin-bottom: 8px;
+}
+
+/* Подзаголовок - скрываем дефолтный */
+#kc-locale {
+ display: none;
+}
+
+/* Кастомный заголовок */
+#kc-form-login::before {
+ content: "Team Planner";
+ display: block;
+ font-size: 1.75rem;
+ font-weight: 700;
+ color: var(--text-primary);
+ text-align: center;
+ margin-bottom: 8px;
+}
+
+#kc-form-login::after {
+ content: "Приложение для управления бэклогом идей команды";
+ display: block;
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ text-align: center;
+ margin-bottom: 32px;
+}
+
+/* Группы форм */
+.form-group {
+ margin-bottom: 24px;
+}
+
+/* Лейблы */
+.form-group label,
+.control-label {
+ display: block;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: var(--text-primary);
+ margin-bottom: 8px;
+}
+
+/* Инпуты */
+.form-control,
+input[type="text"],
+input[type="password"],
+input[type="email"] {
+ width: 100%;
+ padding: 16.5px 14px;
+ font-size: 1rem;
+ font-family: inherit;
+ color: var(--text-primary);
+ background-color: var(--surface);
+ border: 1px solid rgba(0, 0, 0, 0.23);
+ border-radius: var(--border-radius);
+ outline: none;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
+}
+
+.form-control:hover,
+input[type="text"]:hover,
+input[type="password"]:hover,
+input[type="email"]:hover {
+ border-color: var(--text-primary);
+}
+
+.form-control:focus,
+input[type="text"]:focus,
+input[type="password"]:focus,
+input[type="email"]:focus {
+ border-color: var(--primary-color);
+ border-width: 2px;
+ padding: 15.5px 13px;
+ box-shadow: none;
+}
+
+/* Кнопка входа */
+#kc-login,
+.btn-primary,
+input[type="submit"] {
+ width: 100%;
+ padding: 12px 24px;
+ font-size: 0.9375rem;
+ font-weight: 500;
+ font-family: inherit;
+ text-transform: uppercase;
+ letter-spacing: 0.02857em;
+ color: #ffffff;
+ background-color: var(--primary-color);
+ border: none;
+ border-radius: var(--border-radius);
+ cursor: pointer;
+ transition: background-color 0.2s ease, box-shadow 0.2s ease;
+ box-shadow: 0px 3px 1px -2px rgba(0,0,0,0.2), 0px 2px 2px 0px rgba(0,0,0,0.14), 0px 1px 5px 0px rgba(0,0,0,0.12);
+}
+
+#kc-login:hover,
+.btn-primary:hover,
+input[type="submit"]:hover {
+ background-color: var(--primary-hover);
+ box-shadow: 0px 2px 4px -1px rgba(0,0,0,0.2), 0px 4px 5px 0px rgba(0,0,0,0.14), 0px 1px 10px 0px rgba(0,0,0,0.12);
+}
+
+#kc-login:active,
+.btn-primary:active,
+input[type="submit"]:active {
+ box-shadow: 0px 5px 5px -3px rgba(0,0,0,0.2), 0px 8px 10px 1px rgba(0,0,0,0.14), 0px 3px 14px 2px rgba(0,0,0,0.12);
+}
+
+/* Ссылки */
+a {
+ color: var(--primary-color);
+ text-decoration: none;
+ font-size: 0.875rem;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+/* Ссылка "Забыли пароль?" */
+#kc-form-options {
+ margin-top: 16px;
+ text-align: center;
+}
+
+/* Чекбокс "Запомнить меня" */
+.checkbox {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 24px;
+}
+
+.checkbox input[type="checkbox"] {
+ width: 18px;
+ height: 18px;
+ accent-color: var(--primary-color);
+ cursor: pointer;
+}
+
+.checkbox label {
+ margin: 0;
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ cursor: pointer;
+}
+
+/* Сообщения об ошибках */
+.alert,
+.alert-error,
+.kc-feedback-text {
+ background-color: #fdeded;
+ color: var(--error);
+ border: 1px solid #f5c6cb;
+ border-radius: var(--border-radius);
+ padding: 12px 16px;
+ margin-bottom: 24px;
+ font-size: 0.875rem;
+}
+
+.alert-success {
+ background-color: #edf7ed;
+ color: #1e4620;
+ border-color: #c3e6cb;
+}
+
+.alert-warning {
+ background-color: #fff4e5;
+ color: #663c00;
+ border-color: #ffeeba;
+}
+
+/* Информационное сообщение внизу */
+#kc-registration {
+ margin-top: 32px;
+ padding-top: 24px;
+ border-top: 1px solid rgba(0, 0, 0, 0.12);
+ text-align: center;
+}
+
+#kc-registration span {
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+}
+
+/* Социальные провайдеры */
+#kc-social-providers {
+ margin-top: 24px;
+ padding-top: 24px;
+ border-top: 1px solid rgba(0, 0, 0, 0.12);
+}
+
+#kc-social-providers h4 {
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ text-align: center;
+ margin-bottom: 16px;
+}
+
+.social-provider-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ width: 100%;
+ padding: 10px 16px;
+ margin-bottom: 8px;
+ font-size: 0.875rem;
+ color: var(--text-primary);
+ background-color: var(--surface);
+ border: 1px solid rgba(0, 0, 0, 0.23);
+ border-radius: var(--border-radius);
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+}
+
+.social-provider-button:hover {
+ background-color: rgba(0, 0, 0, 0.04);
+}
+
+/* Footer - добавляем информацию о контакте */
+#kc-info-wrapper {
+ margin-top: 24px;
+ text-align: center;
+}
+
+#kc-info-wrapper::after {
+ content: "Для получения доступа обратитесь к Николаю Вигдорову";
+ display: block;
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ margin-top: 16px;
+}
+
+/* Адаптивность */
+@media (max-width: 480px) {
+ #kc-form-wrapper,
+ .card-pf {
+ margin: 10px;
+ padding: 32px 24px;
+ }
+
+ #kc-form-login::before {
+ font-size: 1.5rem;
+ }
+}
+
+/* Скрываем ненужные элементы */
+.pf-c-alert__icon,
+.login-pf-page .login-pf-page-header,
+#kc-attempted-username {
+ display: none;
+}
+
+/* Фикс для Keycloak 21+ */
+.pf-c-login__main {
+ background: var(--surface);
+ border-radius: var(--border-radius);
+ box-shadow: var(--shadow);
+ padding: 48px;
+ max-width: 400px;
+ margin: 20px auto;
+}
+
+.pf-c-form-control {
+ width: 100%;
+ padding: 16.5px 14px;
+ font-size: 1rem;
+ border: 1px solid rgba(0, 0, 0, 0.23);
+ border-radius: var(--border-radius);
+}
+
+.pf-c-button.pf-m-primary {
+ width: 100%;
+ padding: 12px 24px;
+ font-size: 0.9375rem;
+ background-color: var(--primary-color);
+ border: none;
+ border-radius: var(--border-radius);
+}
diff --git a/keycloak-theme/team-planner/login/theme.properties b/keycloak-theme/team-planner/login/theme.properties
new file mode 100644
index 0000000..e523564
--- /dev/null
+++ b/keycloak-theme/team-planner/login/theme.properties
@@ -0,0 +1,5 @@
+# Team Planner Keycloak Theme
+parent=keycloak
+import=common/keycloak
+
+styles=css/login.css
diff --git a/tests/playwright-report/data/7a33d5db6370b6de345e990751aa1f1da65ad675.png b/tests/playwright-report/data/7a33d5db6370b6de345e990751aa1f1da65ad675.png
new file mode 100644
index 0000000..6d360f6
Binary files /dev/null and b/tests/playwright-report/data/7a33d5db6370b6de345e990751aa1f1da65ad675.png differ
diff --git a/tests/playwright-report/index.html b/tests/playwright-report/index.html
new file mode 100644
index 0000000..1db288e
--- /dev/null
+++ b/tests/playwright-report/index.html
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+ Playwright Test Report
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/test-results/.last-run.json b/tests/test-results/.last-run.json
index cbcc1fb..75e7388 100644
--- a/tests/test-results/.last-run.json
+++ b/tests/test-results/.last-run.json
@@ -1,4 +1,6 @@
{
- "status": "passed",
- "failedTests": []
+ "status": "failed",
+ "failedTests": [
+ "17e3fe6f4d9d8bd79c6b-60f085b113a677673906"
+ ]
}
\ No newline at end of file
diff --git a/tests/test-results/auth.setup.ts-authenticate-setup/test-failed-1.png b/tests/test-results/auth.setup.ts-authenticate-setup/test-failed-1.png
new file mode 100644
index 0000000..6d360f6
Binary files /dev/null and b/tests/test-results/auth.setup.ts-authenticate-setup/test-failed-1.png differ