This commit is contained in:
62
.drone.yml
62
.drone.yml
@ -70,6 +70,32 @@ steps:
|
|||||||
from_secret: HARBOR_PASSWORD
|
from_secret: HARBOR_PASSWORD
|
||||||
no_push_metadata: true
|
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
|
fi
|
||||||
- echo "✅ Frontend deployed to PROD (image:$IMAGE_TAG)"
|
- 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
|
kind: pipeline
|
||||||
type: kubernetes
|
type: kubernetes
|
||||||
|
|||||||
@ -43,6 +43,8 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: team-planner-secrets
|
name: team-planner-secrets
|
||||||
key: db-password
|
key: db-password
|
||||||
|
- name: KEYCLOAK_REALM_URL
|
||||||
|
value: "https://auth.vigdorov.ru/realms/team-planner"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
memory: "256Mi"
|
memory: "256Mi"
|
||||||
|
|||||||
4
keycloak-theme/Dockerfile
Normal file
4
keycloak-theme/Dockerfile
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
FROM quay.io/keycloak/keycloak:26.5.0
|
||||||
|
|
||||||
|
# Копируем кастомную тему
|
||||||
|
COPY team-planner /opt/keycloak/themes/team-planner
|
||||||
102
keycloak-theme/README.md
Normal file
102
keycloak-theme/README.md
Normal file
@ -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`.
|
||||||
82
keycloak-theme/team-planner/login/login.ftl
Normal file
82
keycloak-theme/team-planner/login/login.ftl
Normal file
@ -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">
|
||||||
|
<div style="text-align: center; margin-bottom: 32px;">
|
||||||
|
<h1 style="font-size: 1.75rem; font-weight: 700; color: rgba(0, 0, 0, 0.87); margin: 0 0 8px 0;">Team Planner</h1>
|
||||||
|
<p style="font-size: 0.875rem; color: rgba(0, 0, 0, 0.6); margin: 0;">Приложение для управления бэклогом идей команды</p>
|
||||||
|
</div>
|
||||||
|
<#elseif section = "form">
|
||||||
|
<div id="kc-form">
|
||||||
|
<div id="kc-form-wrapper">
|
||||||
|
<#if realm.password>
|
||||||
|
<form id="kc-form-login" onsubmit="login.disabled = true; return true;" action="${url.loginAction}" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username" class="control-label">
|
||||||
|
<#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if>
|
||||||
|
</label>
|
||||||
|
<input tabindex="1" id="username" class="form-control" name="username" value="${(login.username!'')}" type="text" autofocus autocomplete="off"
|
||||||
|
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>" />
|
||||||
|
<#if messagesPerField.existsError('username','password')>
|
||||||
|
<span class="input-error" aria-live="polite">
|
||||||
|
${kcSanitize(messagesPerField.getFirstError('username','password'))?no_esc}
|
||||||
|
</span>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password" class="control-label">${msg("password")}</label>
|
||||||
|
<input tabindex="2" id="password" class="form-control" name="password" type="password" autocomplete="off"
|
||||||
|
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<#if realm.rememberMe && !usernameHidden??>
|
||||||
|
<div class="checkbox" style="margin-bottom: 0;">
|
||||||
|
<input tabindex="3" id="rememberMe" name="rememberMe" type="checkbox" <#if login.rememberMe??>checked</#if>>
|
||||||
|
<label for="rememberMe">${msg("rememberMe")}</label>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
<#if realm.resetPasswordAllowed>
|
||||||
|
<span><a tabindex="5" href="${url.loginResetCredentialsUrl}">${msg("doForgotPassword")}</a></span>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="margin-top: 24px;">
|
||||||
|
<input type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
|
||||||
|
<input tabindex="4" class="btn-primary" name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<#elseif section = "info">
|
||||||
|
<div id="kc-info-wrapper" style="text-align: center; margin-top: 24px;">
|
||||||
|
<#if realm.password && realm.registrationAllowed && !registrationDisabled??>
|
||||||
|
<div id="kc-registration">
|
||||||
|
<span>${msg("noAccount")} <a tabindex="6" href="${url.registrationUrl}">${msg("doRegister")}</a></span>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
<p style="font-size: 0.875rem; color: rgba(0, 0, 0, 0.6); margin-top: 16px;">
|
||||||
|
Для получения доступа обратитесь к Николаю Вигдорову
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<#elseif section = "socialProviders">
|
||||||
|
<#if realm.password && social.providers??>
|
||||||
|
<div id="kc-social-providers">
|
||||||
|
<h4 style="text-align: center; color: rgba(0, 0, 0, 0.6); margin-bottom: 16px;">Или войти через</h4>
|
||||||
|
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||||
|
<#list social.providers as p>
|
||||||
|
<li style="margin-bottom: 8px;">
|
||||||
|
<a id="social-${p.alias}" class="social-provider-button" type="button" href="${p.loginUrl}">
|
||||||
|
<#if p.iconClasses?has_content>
|
||||||
|
<i class="${p.iconClasses!}" aria-hidden="true"></i>
|
||||||
|
</#if>
|
||||||
|
<span>${p.displayName!}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</#list>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
||||||
@ -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=Неверное имя пользователя или пароль.
|
||||||
356
keycloak-theme/team-planner/login/resources/css/login.css
Normal file
356
keycloak-theme/team-planner/login/resources/css/login.css
Normal file
@ -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);
|
||||||
|
}
|
||||||
5
keycloak-theme/team-planner/login/theme.properties
Normal file
5
keycloak-theme/team-planner/login/theme.properties
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Team Planner Keycloak Theme
|
||||||
|
parent=keycloak
|
||||||
|
import=common/keycloak
|
||||||
|
|
||||||
|
styles=css/login.css
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
85
tests/playwright-report/index.html
Normal file
85
tests/playwright-report/index.html
Normal file
File diff suppressed because one or more lines are too long
@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
"status": "passed",
|
"status": "failed",
|
||||||
"failedTests": []
|
"failedTests": [
|
||||||
|
"17e3fe6f4d9d8bd79c6b-60f085b113a677673906"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Reference in New Issue
Block a user