This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { Component, createContext, createSignal, JSX, onMount, useContext } from 'solid-js'
|
import { Component, createContext, createSignal, JSX, onMount, useContext } from 'solid-js'
|
||||||
|
import { AuthSuccess } from '~/graphql/generated/graphql'
|
||||||
import { query } from '../graphql'
|
import { query } from '../graphql'
|
||||||
import { ADMIN_LOGIN_MUTATION, ADMIN_LOGOUT_MUTATION } from '../graphql/mutations'
|
import { ADMIN_LOGIN_MUTATION, ADMIN_LOGOUT_MUTATION } from '../graphql/mutations'
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +11,6 @@ import {
|
|||||||
getCsrfTokenFromCookie,
|
getCsrfTokenFromCookie,
|
||||||
saveAuthToken
|
saveAuthToken
|
||||||
} from '../utils/auth'
|
} from '../utils/auth'
|
||||||
import { AuthSuccess } from '~/graphql/generated/graphql'
|
|
||||||
/**
|
/**
|
||||||
* Модуль авторизации
|
* Модуль авторизации
|
||||||
* @module auth
|
* @module auth
|
||||||
@@ -164,10 +164,7 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
|||||||
export const logout = async () => {
|
export const logout = async () => {
|
||||||
console.log('[Auth] Executing standalone logout...')
|
console.log('[Auth] Executing standalone logout...')
|
||||||
try {
|
try {
|
||||||
const result = await query<{ logout: AuthSuccess }>(
|
const result = await query<{ logout: AuthSuccess }>(`${location.origin}/graphql`, ADMIN_LOGOUT_MUTATION)
|
||||||
`${location.origin}/graphql`,
|
|
||||||
ADMIN_LOGOUT_MUTATION
|
|
||||||
)
|
|
||||||
console.log('[Auth] Standalone logout result:', result)
|
console.log('[Auth] Standalone logout result:', result)
|
||||||
if (result?.logout?.success) {
|
if (result?.logout?.success) {
|
||||||
clearAuthTokens()
|
clearAuthTokens()
|
||||||
|
@@ -17,8 +17,14 @@ export interface UserEditModalProps {
|
|||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Доступные роли в системе (без роли Администратор - она определяется автоматически)
|
// Доступные роли в системе
|
||||||
const AVAILABLE_ROLES = [
|
const AVAILABLE_ROLES = [
|
||||||
|
{
|
||||||
|
id: 'Администратор',
|
||||||
|
name: 'Системный администратор',
|
||||||
|
description: 'Администраторы определяются автоматически по настройкам сервера',
|
||||||
|
emoji: '🪄'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'Редактор',
|
id: 'Редактор',
|
||||||
name: 'Редактор',
|
name: 'Редактор',
|
||||||
@@ -51,7 +57,7 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
email: props.user.email || '',
|
email: props.user.email || '',
|
||||||
name: props.user.name || '',
|
name: props.user.name || '',
|
||||||
slug: props.user.slug || '',
|
slug: props.user.slug || '',
|
||||||
roles: props.user.roles?.filter((role) => role !== 'Администратор') || [] // Исключаем админскую роль из ручного управления
|
roles: props.user.roles || [] // Включаем все роли, включая администратора
|
||||||
})
|
})
|
||||||
|
|
||||||
const [errors, setErrors] = createSignal<Record<string, string>>({})
|
const [errors, setErrors] = createSignal<Record<string, string>>({})
|
||||||
@@ -94,7 +100,7 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
email: props.user.email || '',
|
email: props.user.email || '',
|
||||||
name: props.user.name || '',
|
name: props.user.name || '',
|
||||||
slug: props.user.slug || '',
|
slug: props.user.slug || '',
|
||||||
roles: props.user.roles?.filter((role) => role !== 'Администратор') || [] // Исключаем админскую роль
|
roles: props.user.roles || [] // Включаем все роли, включая администратора
|
||||||
})
|
})
|
||||||
setErrors({})
|
setErrors({})
|
||||||
}
|
}
|
||||||
@@ -109,6 +115,11 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleRoleToggle = (roleId: string) => {
|
const handleRoleToggle = (roleId: string) => {
|
||||||
|
// Роль администратора нельзя изменить вручную
|
||||||
|
if (roleId === 'Администратор') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setFormData((prev) => {
|
setFormData((prev) => {
|
||||||
const currentRoles = prev.roles
|
const currentRoles = prev.roles
|
||||||
const newRoles = currentRoles.includes(roleId)
|
const newRoles = currentRoles.includes(roleId)
|
||||||
@@ -149,7 +160,7 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Роли (админы освобождаются от этого требования)
|
// Роли (админы освобождаются от этого требования)
|
||||||
if (!isAdmin() && data.roles.length === 0) {
|
if (!isAdmin() && data.roles.filter((role) => role !== 'Администратор').length === 0) {
|
||||||
newErrors.roles = 'Выберите хотя бы одну роль (или назначьте админский email)'
|
newErrors.roles = 'Выберите хотя бы одну роль (или назначьте админский email)'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +226,7 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Текущие роли в строку */}
|
{/* Текущие роли в строку */}
|
||||||
<div class={formStyles.fieldGroup}>
|
<div class={formStyles.fieldGroup} style={{ display: 'none' }}>
|
||||||
<label class={formStyles.label}>
|
<label class={formStyles.label}>
|
||||||
<span class={formStyles.labelText}>
|
<span class={formStyles.labelText}>
|
||||||
<span class={formStyles.labelIcon}>👤</span>
|
<span class={formStyles.labelIcon}>👤</span>
|
||||||
@@ -339,29 +350,53 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
|
|
||||||
<div class={formStyles.rolesGrid}>
|
<div class={formStyles.rolesGrid}>
|
||||||
<For each={AVAILABLE_ROLES}>
|
<For each={AVAILABLE_ROLES}>
|
||||||
{(role) => (
|
{(role) => {
|
||||||
<label
|
const isAdminRole = role.id === 'Администратор'
|
||||||
class={`${formStyles.roleCard} ${formData().roles.includes(role.id) ? formStyles.roleCardSelected : ''}`}
|
const isSelected = formData().roles.includes(role.id)
|
||||||
>
|
const isDisabled = isAdminRole // Роль администратора всегда заблокирована
|
||||||
<input
|
|
||||||
type="checkbox"
|
return (
|
||||||
checked={formData().roles.includes(role.id)}
|
<label
|
||||||
onChange={() => handleRoleToggle(role.id)}
|
class={`${formStyles.roleCard} ${isSelected ? formStyles.roleCardSelected : ''} ${isDisabled ? formStyles.roleCardDisabled || '' : ''}`}
|
||||||
disabled={loading()}
|
style={{
|
||||||
style={{ display: 'none' }}
|
opacity: isDisabled ? 0.7 : 1,
|
||||||
/>
|
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
||||||
<div class={formStyles.roleHeader}>
|
background: isAdminRole && isSelected ? 'rgba(245, 158, 11, 0.1)' : undefined,
|
||||||
<span class={formStyles.roleName}>
|
border: isAdminRole && isSelected ? '1px solid rgba(245, 158, 11, 0.3)' : undefined
|
||||||
<span style={{ 'margin-right': '0.5rem', 'font-size': '1.1rem' }}>{role.emoji}</span>
|
}}
|
||||||
{role.name}
|
>
|
||||||
</span>
|
<input
|
||||||
<span class={formStyles.roleCheckmark}>
|
type="checkbox"
|
||||||
{formData().roles.includes(role.id) ? '✓' : ''}
|
checked={isSelected}
|
||||||
</span>
|
onChange={() => handleRoleToggle(role.id)}
|
||||||
</div>
|
disabled={loading() || isDisabled}
|
||||||
<div class={formStyles.roleDescription}>{role.description}</div>
|
style={{ display: 'none' }}
|
||||||
</label>
|
/>
|
||||||
)}
|
<div class={formStyles.roleHeader}>
|
||||||
|
<span class={formStyles.roleName}>
|
||||||
|
<span style={{ 'margin-right': '0.5rem', 'font-size': '1.1rem' }}>
|
||||||
|
{role.emoji}
|
||||||
|
</span>
|
||||||
|
{role.name}
|
||||||
|
{isAdminRole && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
'margin-left': '0.5rem',
|
||||||
|
'font-size': '0.75rem',
|
||||||
|
color: '#d97706',
|
||||||
|
'font-weight': 'normal'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
(системная)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span class={formStyles.roleCheckmark}>{isSelected ? '✓' : ''}</span>
|
||||||
|
</div>
|
||||||
|
<div class={formStyles.roleDescription}>{role.description}</div>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -374,9 +409,9 @@ const UserEditModal: Component<UserEditModalProps> = (props) => {
|
|||||||
|
|
||||||
<div class={formStyles.hint}>
|
<div class={formStyles.hint}>
|
||||||
<span class={formStyles.hintIcon}>💡</span>
|
<span class={formStyles.hintIcon}>💡</span>
|
||||||
{isAdmin()
|
Системные роли (администратор) назначаются автоматически и не могут быть изменены вручную.
|
||||||
? 'Администраторы имеют все права автоматически. Дополнительные роли опциональны.'
|
{!isAdmin() &&
|
||||||
: 'Выберите роли для пользователя. Минимум одна роль обязательна.'}
|
' Выберите дополнительные роли для пользователя - минимум одна роль обязательна.'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -355,13 +355,14 @@ const HTMLEditor = (props: HTMLEditorProps) => {
|
|||||||
if (token.startsWith('</')) {
|
if (token.startsWith('</')) {
|
||||||
// Закрывающий тег - уменьшаем отступ
|
// Закрывающий тег - уменьшаем отступ
|
||||||
indent--
|
indent--
|
||||||
formatted += indentStr.repeat(Math.max(0, indent)) + token + '\n'
|
formatted += `${indentStr.repeat(Math.max(0, indent))}${token}\n`
|
||||||
} else if (token.startsWith('<') && token.endsWith('>')) {
|
} else if (token.startsWith('<') && token.endsWith('>')) {
|
||||||
// Открывающий тег
|
// Открывающий тег
|
||||||
const isSelfClosing = token.endsWith('/>') ||
|
const isSelfClosing =
|
||||||
|
token.endsWith('/>') ||
|
||||||
/^<(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)(\s|>)/i.test(token)
|
/^<(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)(\s|>)/i.test(token)
|
||||||
|
|
||||||
formatted += indentStr.repeat(indent) + token + '\n'
|
formatted += `${indentStr.repeat(indent)}${token}\n`
|
||||||
|
|
||||||
if (!isSelfClosing) {
|
if (!isSelfClosing) {
|
||||||
indent++
|
indent++
|
||||||
@@ -369,7 +370,7 @@ const HTMLEditor = (props: HTMLEditorProps) => {
|
|||||||
} else {
|
} else {
|
||||||
// Текстовое содержимое
|
// Текстовое содержимое
|
||||||
if (token.length > 0) {
|
if (token.length > 0) {
|
||||||
formatted += indentStr.repeat(indent) + token + '\n'
|
formatted += `${indentStr.repeat(indent)}${token}\n`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user