## 소스맵 노출 차단 (공개 이슈 )
배포본에 .map 이 포함돼 원본 TS/TSX 전문(sourcesContent, 한글 주석 포함)이
그대로 노출됐다. 생성·서빙·저장소 유입 3계층을 모두 막는다.
- 빌드 config 15곳(코어 3 + _bundled 12): sourcemap 하드코딩 제거.
G7_BUILD_SOURCEMAP 판정식으로 교체 — 미설정(로컬 npm run build)이면 생성해
개발 디버깅 경험을 보존하고, 0 이면 미생성.
- Build{Core,Module,Plugin,Template}Command: 죽어 있던 --production 플래그 복구.
$productionMode 를 콘솔 문자열에만 쓰고 npm 프로세스에 env 를 넘기지 않아
산출물이 개발 빌드와 동일했다. runNpmCommand 에 $env 를 추가해 주입
(--watch 에는 미주입). Symfony Process 는 부모 환경에 병합하므로 PATH 유실 없음.
- Allowed{Module,Plugin}FileType / {Module,Plugin}Service::getMimeType:
허용 확장자 map 제거 — 익명 200 서빙 차단(템플릿은 원래 미허용, 3종 정책 일치).
- ExtensionBundleService: CSS 번들의 /*# sourceMappingURL=... */ strip 추가.
CSS 는 블록 주석이라 JS 의 //# 패턴으로는 검출되지 않던 구멍.
- 추적 .map 13개 삭제 + .gitignore 전역 *.map 규칙(경로 한정 시 새 확장에서 누락).
실측: 산출 JS 의 sourceMappingURL 13 → 0, 추적 .map 13 → 0,
확장 디렉토리 npm run build 는 여전히 map 생성(개발 경험 회귀 없음).
## 관리자 환경설정 탭 지연 로딩
코어/이커머스/게시판 환경설정 화면이 진입 즉시 전 탭의 데이터소스를 한꺼번에
호출했다. 각 데이터소스에 활성 탭 조건(if)을 걸어 해당 탭에서만 fetch 하도록
변경하고, 결제 플러그인 3종의 테스트모드 상태 조회도 주문설정 탭으로 한정.
## 동반 결함 수정 (무관 결함 — 재발 방지 목적으로 같은 세션 처리)
- 나이스페이먼츠: 체크아웃 캐시 프리페치가 라우트를 가리지 않아 플러그인 번들이
로드되는 모든 페이지(홈/게시판/로그인)에서 주문서를 조회 → 비회원 매번 422.
형제 플러그인(kginicis)의 기존 가드 패턴을 적용해 체크아웃 경로로 한정.
- admin smoke 8건: 대부분 구현이 아니라 테스트 결함이었다 — URL 오타(단수 board),
존재하지 않는 권한키, 앱 로케일이 en 인데 한국어 라벨 로케이터, DataGrid 버튼을
먼저 잡던 휴리스틱 로케이터. 레이아웃에는 sticky className / 안정 id 를 부여.
- PHPDoc ↔ Pint 충돌 3건: 설명 없는 @param 은 no_superfluous_phpdoc_tags 가
삭제해 audit phpdoc-public-method 와 무한 충돌. 한국어 설명을 붙여 양쪽 충족
(리스너 3종의 설명 누락 메서드도 전수 보강).
- Log::spy 500 3건: spy 가 Log::channel('activity') 를 null 로 만들고
logActivity 가 null->info 로 \Error → 같은 줄 catch(\Exception) 은 못 잡아
통과. 운영에선 채널이 항상 로거이므로 제품 무결 → 테스트에 activity 채널
보존 헬퍼 추가.
- NhnKcpApiServiceTest 5건: 스텁이 Unix 바이너리만 만들어 Windows 분기
(pp_cli_exe.exe)에서만 실패. #!/bin/sh 는 Windows 실행 불가 → 파일 내 기존
컨벤션대로 skip 가드.
## 테스트
- 소스맵 범위 130 passed (신규: BuildCommandSourcemapEnv, NoSourcemapArtifacts,
ExtensionBundleService CSS strip, Allowed*FileType 반전, 자산 서빙 거부)
- 결제 3플러그인 505 passed / 0 failed (skipped 9 = Windows 전용 CLI, 사유 명시)
- 나이스페이먼츠 프론트 38 passed (가드 제거 시 red 실증 후 복원)
- E2E: production-sourcemap-exposure.spec.ts 6건 + 시나리오 매니페스트
버전: sirsoft-admin_basic 1.0.2 → 1.0.3, sirsoft-board 1.0.1 → 1.0.2
(각 manifest/package/lock/composer 동기화). 코어 7.0.4 는 미출시 누적.
862 lines
30 KiB
JSON
862 lines
30 KiB
JSON
{
|
|
"version": "1.0.0",
|
|
"layout_name": "admin_settings",
|
|
"permissions": [
|
|
"core.settings.read"
|
|
],
|
|
"extends": "_admin_base",
|
|
"meta": {
|
|
"title": "$t:admin.settings.page_title",
|
|
"description": "$t:admin.settings.page_description"
|
|
},
|
|
"computed": {
|
|
"isReadOnly": "{{!!_global.settingsAbilities && !(_global.settingsAbilities?.can_update ?? false)}}"
|
|
},
|
|
"transition_overlay": {
|
|
"target": "settings_tab_content",
|
|
"fallback_target": "right_content_area",
|
|
"wait_for": [
|
|
"settings",
|
|
"systemInfo",
|
|
"appKey",
|
|
"notificationDefinitions",
|
|
"availableChannels",
|
|
"identityProviders",
|
|
"identityPurposes",
|
|
"identityMessages",
|
|
"adminIdentityPolicies",
|
|
"policies",
|
|
"language_packs"
|
|
]
|
|
},
|
|
"data_sources": [
|
|
{
|
|
"id": "language_packs",
|
|
"label_key": "$t:editor.data_source.language_packs",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/language-packs",
|
|
"method": "GET",
|
|
"if": "{{(query.tab || _global.activeSettingsTab || 'general') === 'language_packs'}}",
|
|
"auto_fetch": true,
|
|
"auth_required": true,
|
|
"params": {
|
|
"page": "{{query.page}}",
|
|
"per_page": "{{query.per_page ?? 10}}",
|
|
"scope": "{{_local.filterScope || query.scope}}",
|
|
"target_identifier": "{{_local.filterTarget || query.target_identifier}}",
|
|
"locale": "{{query.locale}}",
|
|
"status": "{{query.status}}",
|
|
"search": "{{query.search}}",
|
|
"exclude_protected": "{{query.exclude_protected ?? '1'}}"
|
|
},
|
|
"fallback": {
|
|
"data": {
|
|
"data": [],
|
|
"meta": {
|
|
"total": 0,
|
|
"active": 0,
|
|
"installed": 0,
|
|
"inactive": 0,
|
|
"error": 0
|
|
},
|
|
"abilities": {}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"id": "language_pack_detail",
|
|
"label_key": "$t:editor.data_source.language_pack_detail",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/language-packs/{{_local.detailId}}",
|
|
"method": "GET",
|
|
"auto_fetch": false,
|
|
"auth_required": true,
|
|
"depends_on": [
|
|
"_local.detailId"
|
|
]
|
|
},
|
|
{
|
|
"id": "language_pack_changelog",
|
|
"label_key": "$t:editor.data_source.language_pack_changelog",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/language-packs/{{_local.detailId}}/changelog",
|
|
"method": "GET",
|
|
"auto_fetch": false,
|
|
"auth_required": true,
|
|
"depends_on": [
|
|
"_local.detailId"
|
|
]
|
|
},
|
|
{
|
|
"id": "settings",
|
|
"label_key": "$t:editor.data_source.settings",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/settings",
|
|
"method": "GET",
|
|
"auto_fetch": true,
|
|
"auth_required": true,
|
|
"initLocal": {
|
|
"form": "{{data}}",
|
|
"originalDrivers": "drivers"
|
|
},
|
|
"initGlobal": {
|
|
"settingsAbilities": "abilities"
|
|
},
|
|
"refetchOnMount": true,
|
|
"fallback": {
|
|
"data": []
|
|
}
|
|
},
|
|
{
|
|
"id": "systemInfo",
|
|
"label_key": "$t:editor.data_source.systemInfo",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/settings/system-info",
|
|
"method": "GET",
|
|
"if": "{{(query.tab || _global.activeSettingsTab || 'general') === 'info'}}",
|
|
"auto_fetch": true,
|
|
"auth_required": true,
|
|
"fallback": {
|
|
"data": {
|
|
"php_version": "-",
|
|
"laravel_version": "-",
|
|
"database_type": "-",
|
|
"server_software": "-"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"id": "appKey",
|
|
"label_key": "$t:editor.data_source.appKey",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/settings/app-key",
|
|
"method": "GET",
|
|
"if": "{{(query.tab || _global.activeSettingsTab || 'general') === 'security'}}",
|
|
"auto_fetch": true,
|
|
"auth_required": true,
|
|
"fallback": {
|
|
"data": {
|
|
"key": "-"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"id": "notificationDefinitions",
|
|
"label_key": "$t:editor.data_source.notificationDefinitions",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/notification-definitions",
|
|
"method": "GET",
|
|
"if": "{{(query.tab || _global.activeSettingsTab || 'general') === 'notification_definitions'}}",
|
|
"auto_fetch": true,
|
|
"auth_required": true,
|
|
"params": {
|
|
"page": "{{query.page || 1}}",
|
|
"per_page": "{{query.per_page || 20}}",
|
|
"search": "{{query.search}}",
|
|
"search_type": "{{query.search_type}}",
|
|
"extension_identifier": "core"
|
|
},
|
|
"fallback": {
|
|
"data": {
|
|
"data": [],
|
|
"pagination": []
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"id": "availableChannels",
|
|
"label_key": "$t:editor.data_source.availableChannels",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/notification-channels",
|
|
"method": "GET",
|
|
"if": "{{(query.tab || _global.activeSettingsTab || 'general') === 'notification_definitions'}}",
|
|
"auto_fetch": true,
|
|
"auth_required": true,
|
|
"fallback": {
|
|
"data": []
|
|
}
|
|
},
|
|
{
|
|
"id": "identityProviders",
|
|
"label_key": "$t:editor.data_source.identityProviders",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/identity/providers",
|
|
"method": "GET",
|
|
"if": "{{(query.tab || _global.activeSettingsTab || 'general') === 'identity'}}",
|
|
"auto_fetch": true,
|
|
"auth_required": true,
|
|
"fallback": {
|
|
"data": []
|
|
}
|
|
},
|
|
{
|
|
"id": "identityPurposes",
|
|
"label_key": "$t:editor.data_source.identityPurposes",
|
|
"type": "api",
|
|
"endpoint": "/api/identity/purposes",
|
|
"method": "GET",
|
|
"if": "{{(query.tab || _global.activeSettingsTab || 'general') === 'identity'}}",
|
|
"auto_fetch": true,
|
|
"fallback": {
|
|
"data": []
|
|
}
|
|
},
|
|
{
|
|
"id": "identityMessages",
|
|
"label_key": "$t:editor.data_source.identityMessages",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/identity/messages/definitions",
|
|
"method": "GET",
|
|
"if": "{{(query.tab || _global.activeSettingsTab || 'general') === 'identity'}}",
|
|
"auto_fetch": true,
|
|
"auth_required": true,
|
|
"params": {
|
|
"page": "{{query.messages_page || 1}}",
|
|
"per_page": "{{query.messages_per_page || 20}}",
|
|
"sort_by": "id",
|
|
"sort_order": "asc"
|
|
},
|
|
"fallback": {
|
|
"data": {
|
|
"data": []
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"id": "adminIdentityPolicies",
|
|
"label_key": "$t:editor.data_source.adminIdentityPolicies",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/identity/policies",
|
|
"method": "GET",
|
|
"if": "{{(query.tab || _global.activeSettingsTab || 'general') === 'identity'}}",
|
|
"auto_fetch": true,
|
|
"auth_required": true,
|
|
"params": {
|
|
"source_type": "admin",
|
|
"per_page": 100
|
|
},
|
|
"fallback": {
|
|
"data": {
|
|
"data": []
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"id": "policies",
|
|
"label_key": "$t:editor.data_source.policies",
|
|
"type": "api",
|
|
"endpoint": "/api/admin/identity/policies",
|
|
"method": "GET",
|
|
"if": "{{(query.tab || _global.activeSettingsTab || 'general') === 'identity'}}",
|
|
"auto_fetch": true,
|
|
"auth_required": true,
|
|
"params": {
|
|
"scope": "{{query.scope}}",
|
|
"purpose": "{{query.purpose}}",
|
|
"source_type": "{{query.source_type}}",
|
|
"enabled": "{{query.enabled}}",
|
|
"search": "{{query.search}}",
|
|
"page": "{{query.page || 1}}",
|
|
"per_page": "{{query.per_page || 10}}"
|
|
},
|
|
"fallback": {
|
|
"data": {
|
|
"data": [],
|
|
"meta": {}
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"slots": {
|
|
"content": [
|
|
{
|
|
"id": "settings_content",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"dataKey": "form",
|
|
"trackChanges": true,
|
|
"props": {
|
|
"className": "admin-page-content-responsive"
|
|
},
|
|
"children": [
|
|
{
|
|
"id": "page_header",
|
|
"type": "composite",
|
|
"name": "PageHeader",
|
|
"props": {
|
|
"title": "$t:admin.settings.page_title"
|
|
},
|
|
"children": [
|
|
{
|
|
"id": "refresh_button",
|
|
"type": "basic",
|
|
"name": "Button",
|
|
"props": {
|
|
"type": "button",
|
|
"className": "btn-icon"
|
|
},
|
|
"actions": [
|
|
{
|
|
"type": "click",
|
|
"handler": "refetchDataSource",
|
|
"params": {
|
|
"dataSourceId": "settings"
|
|
}
|
|
}
|
|
],
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Icon",
|
|
"props": {
|
|
"name": "refresh",
|
|
"className": "w-5 h-5"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "readonly_banner",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"if": "{{_computed.isReadOnly}}",
|
|
"props": {
|
|
"className": "alert-warning"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "flex-center gap-3"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Icon",
|
|
"props": {
|
|
"name": "lock",
|
|
"className": "w-5 h-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0"
|
|
}
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "P",
|
|
"props": {
|
|
"className": "text-warning-soft"
|
|
},
|
|
"text": "$t:admin.settings.readonly_notice"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "sticky_header",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "sticky-tab-nav-responsive"
|
|
},
|
|
"children": [
|
|
{
|
|
"id": "tab_navigation",
|
|
"type": "composite",
|
|
"name": "TabNavigation",
|
|
"props": {
|
|
"className": "sticky-tab-nav-responsive",
|
|
"tabs": [
|
|
{
|
|
"id": "general",
|
|
"label": "$t:admin.settings.tabs.general"
|
|
},
|
|
{
|
|
"id": "mail",
|
|
"label": "$t:admin.settings.tabs.mail"
|
|
},
|
|
{
|
|
"id": "upload",
|
|
"label": "$t:admin.settings.tabs.upload"
|
|
},
|
|
{
|
|
"id": "seo",
|
|
"label": "$t:admin.settings.tabs.seo"
|
|
},
|
|
{
|
|
"id": "security",
|
|
"label": "$t:admin.settings.tabs.security"
|
|
},
|
|
{
|
|
"id": "drivers",
|
|
"label": "$t:admin.settings.tabs.drivers"
|
|
},
|
|
{
|
|
"id": "advanced",
|
|
"label": "$t:admin.settings.tabs.advanced"
|
|
},
|
|
{
|
|
"id": "notification_definitions",
|
|
"label": "$t:admin.settings.tabs.notification_definitions"
|
|
},
|
|
{
|
|
"id": "language_packs",
|
|
"label": "$t:admin.settings.tabs.language_packs"
|
|
},
|
|
{
|
|
"id": "identity",
|
|
"label": "$t:admin.settings.tabs.identity"
|
|
},
|
|
{
|
|
"id": "info",
|
|
"label": "$t:admin.settings.tabs.info"
|
|
}
|
|
],
|
|
"activeTabId": "{{_global.activeSettingsTab || query.tab || 'general'}}",
|
|
"variant": "default"
|
|
},
|
|
"actions": [
|
|
{
|
|
"event": "onTabChange",
|
|
"type": "change",
|
|
"handler": "sequence",
|
|
"params": {
|
|
"actions": [
|
|
{
|
|
"handler": "setState",
|
|
"params": {
|
|
"target": "local",
|
|
"errors": null
|
|
}
|
|
},
|
|
{
|
|
"handler": "setState",
|
|
"params": {
|
|
"target": "global",
|
|
"activeSettingsTab": "{{$args[0]}}"
|
|
}
|
|
},
|
|
{
|
|
"handler": "navigate",
|
|
"params": {
|
|
"path": "/admin/settings",
|
|
"query": {
|
|
"tab": "{{$args[0]}}"
|
|
},
|
|
"replace": true
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "identity_sub_tab_navigation",
|
|
"type": "composite",
|
|
"name": "TabNavigationScroll",
|
|
"if": "{{(_global.activeSettingsTab || query.tab) === 'identity'}}",
|
|
"props": {
|
|
"tabs": [
|
|
{
|
|
"id": "basic",
|
|
"label": "$t:admin.settings.identity.sub_tabs.basic"
|
|
},
|
|
{
|
|
"id": "providers",
|
|
"label": "$t:admin.settings.identity.sub_tabs.providers"
|
|
},
|
|
{
|
|
"id": "policies",
|
|
"label": "$t:admin.settings.identity.sub_tabs.policies"
|
|
},
|
|
{
|
|
"id": "messages",
|
|
"label": "$t:admin.settings.identity.sub_tabs.messages"
|
|
}
|
|
],
|
|
"className": "sub-tab-nav",
|
|
"sectionIdPrefix": "identity_section_",
|
|
"enableScrollSpy": true,
|
|
"scrollContainerId": "right_content_area",
|
|
"scrollSpyOffset": 180,
|
|
"scrollOffset": 180
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "settings_tab_content",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"children": [
|
|
{
|
|
"id": "validation_error",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"if": "{{_local.errors}}",
|
|
"props": {
|
|
"className": "alert-danger"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"props": {
|
|
"className": "flex gap-3"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Icon",
|
|
"props": {
|
|
"name": "triangle-exclamation",
|
|
"className": "w-5 h-5 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5"
|
|
}
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "H3",
|
|
"props": {
|
|
"className": "text-error-strong"
|
|
},
|
|
"text": "$t:admin.settings.validation_error"
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "P",
|
|
"if": "{{typeof _local.errors === 'string'}}",
|
|
"props": {
|
|
"className": "text-error-soft mt-2"
|
|
},
|
|
"text": "{{_local.errors}}"
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Ul",
|
|
"if": "{{typeof _local.errors === 'object' && Object.keys(_local.errors).length > 0}}",
|
|
"props": {
|
|
"className": "mt-2 list-disc list-inside space-y-1"
|
|
},
|
|
"iteration": {
|
|
"source": "Object.entries(_local.errors ?? {}).flatMap(([field, messages]) => Array.isArray(messages) ? messages : [messages])",
|
|
"item_var": "message"
|
|
},
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Li",
|
|
"props": {
|
|
"className": "text-error-soft"
|
|
},
|
|
"text": "{{message}}"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_tab_general.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_tab_mail.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_tab_upload.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_tab_seo.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_tab_security.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_tab_drivers.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_tab_advanced.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_tab_notification_definitions.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_tab_language_packs.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_tab_identity.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_tab_info.json"
|
|
}
|
|
],
|
|
"props": {
|
|
"className": "tab-content"
|
|
}
|
|
},
|
|
{
|
|
"id": "footer_buttons",
|
|
"type": "basic",
|
|
"name": "Div",
|
|
"if": "{{!_computed.isReadOnly && (_global.activeSettingsTab || query.tab || 'general') !== 'info' && (_global.activeSettingsTab || query.tab || 'general') !== 'language_packs'}}",
|
|
"props": {
|
|
"className": "sticky-footer-buttons"
|
|
},
|
|
"children": [
|
|
{
|
|
"id": "cancel_button",
|
|
"type": "basic",
|
|
"name": "Button",
|
|
"props": {
|
|
"className": "btn btn-secondary"
|
|
},
|
|
"actions": [
|
|
{
|
|
"type": "click",
|
|
"handler": "refetchDataSource",
|
|
"params": {
|
|
"dataSourceId": "settings"
|
|
}
|
|
}
|
|
],
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"text": "$t:common.cancel"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "save_button",
|
|
"type": "basic",
|
|
"name": "Button",
|
|
"props": {
|
|
"disabled": "{{!_local.hasChanges || _local.isSaving || ((_global.activeSettingsTab || query.tab) === 'drivers' && ((_local.form?.drivers?.storage_driver === 's3' && !_local.driverTestResults?.s3?.success && ((_local.form?.drivers?.storage_driver || '') !== (_local.originalDrivers?.storage_driver || '') || (_local.form?.drivers?.s3_bucket || '') !== (_local.originalDrivers?.s3_bucket || '') || (_local.form?.drivers?.s3_region || '') !== (_local.originalDrivers?.s3_region || '') || (_local.form?.drivers?.s3_access_key || '') !== (_local.originalDrivers?.s3_access_key || '') || (_local.form?.drivers?.s3_secret_key || '') !== (_local.originalDrivers?.s3_secret_key || ''))) || (_local.form?.drivers?.cache_driver === 'redis' && !_local.driverTestResults?.redis?.success && ((_local.form?.drivers?.cache_driver || '') !== (_local.originalDrivers?.cache_driver || '') || (_local.form?.drivers?.redis_host || '') !== (_local.originalDrivers?.redis_host || '') || String(_local.form?.drivers?.redis_port ?? '') !== String(_local.originalDrivers?.redis_port ?? '') || (_local.form?.drivers?.redis_password || '') !== (_local.originalDrivers?.redis_password || ''))) || (_local.form?.drivers?.cache_driver === 'memcached' && !_local.driverTestResults?.memcached?.success && ((_local.form?.drivers?.cache_driver || '') !== (_local.originalDrivers?.cache_driver || '') || (_local.form?.drivers?.memcached_host || '') !== (_local.originalDrivers?.memcached_host || '') || String(_local.form?.drivers?.memcached_port ?? '') !== String(_local.originalDrivers?.memcached_port ?? ''))) || (_local.form?.drivers?.session_driver === 'redis' && !_local.driverTestResults?.redis?.success && ((_local.form?.drivers?.session_driver || '') !== (_local.originalDrivers?.session_driver || '') || (_local.form?.drivers?.redis_host || '') !== (_local.originalDrivers?.redis_host || '') || String(_local.form?.drivers?.redis_port ?? '') !== String(_local.originalDrivers?.redis_port ?? '') || (_local.form?.drivers?.redis_password || '') !== (_local.originalDrivers?.redis_password || ''))) || (_local.form?.drivers?.queue_driver === 'redis' && !_local.driverTestResults?.redis?.success && ((_local.form?.drivers?.queue_driver || '') !== (_local.originalDrivers?.queue_driver || '') || (_local.form?.drivers?.redis_host || '') !== (_local.originalDrivers?.redis_host || '') || String(_local.form?.drivers?.redis_port ?? '') !== String(_local.originalDrivers?.redis_port ?? '') || (_local.form?.drivers?.redis_password || '') !== (_local.originalDrivers?.redis_password || ''))) || (_local.form?.drivers?.websocket_enabled && !_local.driverTestResults?.websocket?.success && (Boolean(_local.form?.drivers?.websocket_enabled) !== Boolean(_local.originalDrivers?.websocket_enabled) || (_local.form?.drivers?.websocket_host || '') !== (_local.originalDrivers?.websocket_host || '') || String(_local.form?.drivers?.websocket_port ?? '') !== String(_local.originalDrivers?.websocket_port ?? '') || (_local.form?.drivers?.websocket_scheme || '') !== (_local.originalDrivers?.websocket_scheme || '')))))}}",
|
|
"className": "btn btn-primary flex-center gap-1.5"
|
|
},
|
|
"actions": [
|
|
{
|
|
"type": "click",
|
|
"handler": "sequence",
|
|
"params": {
|
|
"actions": [
|
|
{
|
|
"handler": "emitEvent",
|
|
"params": {
|
|
"event": "upload:site_logo"
|
|
}
|
|
},
|
|
{
|
|
"handler": "setState",
|
|
"params": {
|
|
"target": "local",
|
|
"isSaving": true,
|
|
"errors": null
|
|
}
|
|
},
|
|
{
|
|
"handler": "apiCall",
|
|
"auth_required": true,
|
|
"target": "/api/admin/settings",
|
|
"params": {
|
|
"method": "POST",
|
|
"body": "{{(function() { const tab = _global.activeSettingsTab || query.tab || 'general'; const form = _local.form || {}; if (tab === 'notification_definitions') { return { _tab: 'notifications', notifications: { channels: form.notifications?.channels || [] } }; } return { _tab: tab, [tab]: form[tab] || {} }; })()}}"
|
|
},
|
|
"onSuccess": [
|
|
{
|
|
"handler": "setState",
|
|
"params": {
|
|
"target": "local",
|
|
"isSaving": false,
|
|
"hasChanges": false
|
|
}
|
|
},
|
|
{
|
|
"handler": "setState",
|
|
"params": {
|
|
"target": "global",
|
|
"settings": "{{result.data.settings}}",
|
|
"uploadSettings": "{{result.data.settings.upload}}"
|
|
}
|
|
},
|
|
{
|
|
"handler": "toast",
|
|
"params": {
|
|
"type": "success",
|
|
"message": "$t:admin.settings.save_success"
|
|
}
|
|
},
|
|
{
|
|
"handler": "refetchDataSource",
|
|
"params": {
|
|
"dataSourceId": "settings"
|
|
}
|
|
}
|
|
],
|
|
"onError": [
|
|
{
|
|
"handler": "setState",
|
|
"params": {
|
|
"target": "local",
|
|
"isSaving": false,
|
|
"errors": "{{error.errors}}"
|
|
}
|
|
},
|
|
{
|
|
"handler": "toast",
|
|
"params": {
|
|
"type": "error",
|
|
"message": "$t:admin.settings.save_error"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"children": [
|
|
{
|
|
"type": "basic",
|
|
"name": "Icon",
|
|
"if": "{{_local.isSaving}}",
|
|
"props": {
|
|
"name": "spinner",
|
|
"className": "spinner-sm"
|
|
}
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"if": "{{!_local.isSaving}}",
|
|
"text": "$t:common.save"
|
|
},
|
|
{
|
|
"type": "basic",
|
|
"name": "Span",
|
|
"if": "{{_local.isSaving}}",
|
|
"text": "$t:common.saving"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
"modals": [
|
|
{
|
|
"partial": "partials/admin_language_pack_list/_modal_install.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_language_pack_list/_modal_install_bundled.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_language_pack_list/_modal_detail.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_language_pack_list/_modal_update.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_language_pack_list/_modal_uninstall.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_language_pack_list/_modal_refresh_cache.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_language_pack_list/_drawer_manifest_preview.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_password_confirm.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_cache_delete.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_notification_template_form.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_notification_template_preview.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_notification_definition_reset.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_core_update_result.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_core_changelog.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_core_update_guide.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_identity_policy_form.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_identity_policy_delete.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_identity_message_template_form.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_identity_message_template_preview.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_identity_message_definition_add.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_identity_message_definition_delete.json"
|
|
},
|
|
{
|
|
"partial": "partials/admin_settings/_modal_identity_message_definition_reset.json"
|
|
}
|
|
],
|
|
"initLocal": {
|
|
"driverTestResults": [],
|
|
"activeNotificationChannel": null,
|
|
"editingDefinition": null,
|
|
"editingTemplate": null,
|
|
"editLang": "ko",
|
|
"previewData": null,
|
|
"isSaving": false,
|
|
"templateErrors": null,
|
|
"checkingCoreUpdate": false,
|
|
"coreUpdateInfo": null,
|
|
"coreChangelog": null,
|
|
"loadingChangelog": false,
|
|
"policyForm": null,
|
|
"policyErrors": null,
|
|
"policyIsNew": true,
|
|
"policyIsSaving": false
|
|
},
|
|
"init_actions": [
|
|
{
|
|
"handler": "setState",
|
|
"params": {
|
|
"target": "local",
|
|
"notificationDefinitionFilter": {
|
|
"search": "{{query.search || ''}}",
|
|
"searchType": "{{query.search_type || 'all'}}"
|
|
},
|
|
"filterScope": "{{query.scope || ''}}",
|
|
"filterStatus": "{{query.status || ''}}",
|
|
"filterLocale": "{{query.locale || ''}}",
|
|
"filterSearch": "{{query.search || ''}}",
|
|
"filterTarget": "",
|
|
"scopeLocked": false
|
|
}
|
|
}
|
|
]
|
|
}
|