원클릭으로
app-settings-feature-toggle
Feature-флаг в БД (app_settings) с UI-тумблером на странице профиля
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Feature-флаг в БД (app_settings) с UI-тумблером на странице профиля
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Динамический LdapTemplate из БД + шифрование пароля + отключение авто-LDAP для безопасного старта без AD
Экспорт Excel с фильтром по датам — использование Thymeleaf inline для передачи дат из формы в JS
Полный цикл синхронизации справочника из Active Directory — сброс структуры, сохранение порядка, блокирующий код в WebFlux
Парсинг Distinguished Name из Active Directory, извлечение OU-цепочки, правила маппинга на Department/Division и блокирующие вызовы в WebFlux
Разрешение конфликта Spring Data LDAP + R2DBC — strict mode, @Table на все entity, отключение LDAP-репозиториев
Подключение Spring Boot к Active Directory через LdapTemplate — зависимости, конфигурация, поиск, SearchControls для бинарных атрибутов
| name | app-settings-feature-toggle |
| description | Feature-флаг в БД (app_settings) с UI-тумблером на странице профиля |
| source | auto-skill |
| extracted_at | 2026-06-12T07:38:38.079Z |
Паттерн для включения/выключения функций через БД, с визуальным тумблером в интерфейсе (только для ADMIN).
CREATE TABLE IF NOT EXISTS app_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL DEFAULT ''
);
INSERT INTO app_settings (key, value) VALUES ('ldap.sync.enabled', 'false')
ON CONFLICT (key) DO NOTHING; -- значение по умолчанию, не перезаписывает существующее
@Data @NoArgsConstructor @Table("app_settings")
public class AppSettings {
@Id private String key;
private String value;
}
public interface AppSettingsRepository extends ReactiveCrudRepository<AppSettings, String> {
Mono<AppSettings> findByKey(String key);
}
@Service @RequiredArgsConstructor
public class AppSettingsService {
public static final String LDAP_SYNC_ENABLED = "ldap.sync.enabled";
public Mono<Boolean> isLdapSyncEnabled() {
return repository.findByKey(LDAP_SYNC_ENABLED)
.map(s -> "true".equalsIgnoreCase(s.getValue()))
.defaultIfEmpty(false);
}
public Mono<Void> setLdapSyncEnabled(boolean enabled) {
return repository.save(new AppSettings(LDAP_SYNC_ENABLED, String.valueOf(enabled))).then();
}
}
@GetMapping("/admin/settings/ldap-sync")
@PreAuthorize("hasRole('ADMIN')")
public Mono<Boolean> getLdapSyncEnabled() {
return appSettingsService.isLdapSyncEnabled();
}
@PutMapping("/admin/settings/ldap-sync")
@PreAuthorize("hasRole('ADMIN')")
public Mono<Void> setLdapSyncEnabled(@RequestBody Map<String, Boolean> body) {
return appSettingsService.setLdapSyncEnabled(body.getOrDefault("enabled", false));
}
<div class="profile-card__footer" th:if="${admin}">
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="ldapSyncToggle" onchange="toggleLdapSync()">
<label class="form-check-label" for="ldapSyncToggle" style="font-size:0.82rem;color:#aaa;">LDAP синхронизация</label>
</div>
<button class="btn btn-smoke w-100" id="syncBtn" style="display:none;" onclick="syncDirectory()">
<i class="bi bi-arrow-repeat"></i> Синхронизировать с AD
</button>
</div>
// Инициализация тумблера
(function(){
fetch('/api/admin/settings/ldap-sync')
.then(function(r) { return r.json(); })
.then(function(enabled) {
document.getElementById('ldapSyncToggle').checked = enabled;
document.getElementById('syncBtn').style.display = enabled ? '' : 'none';
});
})();
function toggleLdapSync() {
var enabled = document.getElementById('ldapSyncToggle').checked;
document.getElementById('syncBtn').style.display = enabled ? '' : 'none';
// PUT с CSRF
var csrfToken = document.querySelector("meta[name='_csrf']").getAttribute("content");
var csrfHeader = document.querySelector("meta[name='_csrf_header']").getAttribute("content");
fetch('/api/admin/settings/ldap-sync', {
method: 'PUT',
headers: { 'Content-Type': 'application/json', [csrfHeader]: csrfToken },
body: JSON.stringify({ enabled: enabled })
});
}
@Scheduled(cron = "0 0 * * * *")
public void scheduledSync() {
Boolean enabled = appSettingsService.isLdapSyncEnabled().block();
if (enabled == null || !enabled) {
log.debug("LDAP синхронизация отключена — пропускаем");
return;
}
// ... выполнение ...
}
.block() в шедулере безопасен — @Scheduled работает в отдельном пуле, не в реактивном event loop.