// VL_VERSION:4.2.13
// @meta component summary:"系统配置 - 登录配置编辑（UC-008A）+ OAuth provider 管理（secret 不回显）+ 运行时信息只读" keywords:(["settings","系统配置","登录配置","OAuth","provider"])
<Section-AdminSettingsPage "root"> containerType:col min-height:"0"

# Frontend Public Props

# Frontend Public Events

# Frontend Public Methods
METHOD ReloadConfig();RETURN {success:BOOL}
-$loading = true
-<ServiceDomain-UserCenterService "ucService">.GetClientConfig("preview", "default", "", "") -> _r
-IF _r.success
--$config = _r
-<ServiceDomain-UserCenterService "ucService">.GetLoginConfig() -> _lc
-IF _lc.success
--$lcDisplayName = _lc.displayName
--$lcRegistrationOpen = _lc.registrationOpen
--$lcRequireApproval = _lc.requireApproval
--$lcPasswordLoginOpen = _lc.passwordLoginOpen
--$lcDefaultRoleCode = _lc.defaultRoleCode
--$lcSessionDuration = _lc.sessionDuration
--$lcMaxConcurrent = _lc.maxConcurrent
-<ServiceDomain-UserCenterService "ucService">.ListOAuthProviders() -> _op
-IF _op.success
--$providers = _op.providers
-$loading = false
-$errorMessage = ""
-RETURN {success:true}

# Frontend Global Vars
$config({}) = {}
$loading(BOOL) = false
$errorMessage(STRING) = ""
$infoMessage(STRING) = ""
$showRuntime(BOOL) = false
$lcDisplayName(STRING) = ""
$lcRegistrationOpen(BOOL) = true
$lcRequireApproval(BOOL) = false
$lcPasswordLoginOpen(BOOL) = true
$lcDefaultRoleCode(STRING) = ""
$lcSessionDuration(INT) = 7200
$lcMaxConcurrent(INT) = 5
$lcSaving(BOOL) = false
$providers([{}]) = []
$provEditOpen(BOOL) = false
$provCode(STRING) = ""
$provEnabled(BOOL) = false
$provDisplayName(STRING) = ""
$provButtonText(STRING) = ""
$provLoginUrl(STRING) = ""
$provClientId(STRING) = ""
$provScope(STRING) = ""
$provCallbackUrl(STRING) = ""
$provSecret(STRING) = ""
$provSaving(BOOL) = false

# Frontend Tree
<ServiceDomain-UserCenterService "ucService">
-<Service-GetClientConfig> params:(env(STRING),userSpaceCode(STRING),projectId(STRING),configuredNid(STRING)) returns:(success(BOOL),message(STRING),bindingSource(STRING),userCenterCode(STRING),userPermissionCenterNid(STRING),userCenterUrl(STRING),permissionCenterUrl(STRING),authApiBase(STRING),permissionApiBase(STRING),authProviders([{}]),env(STRING),userSpaceCode(STRING))
-<Service-GetLoginConfig> params:() returns:(success(BOOL),message(STRING),displayName(STRING),registrationOpen(BOOL),requireApproval(BOOL),passwordLoginOpen(BOOL),defaultRoleCode(STRING),sessionDuration(INT),maxConcurrent(INT))
-<Service-SaveLoginConfig> params:(displayName(STRING),registrationOpen(BOOL),requireApproval(BOOL),passwordLoginOpen(BOOL),defaultRoleCode(STRING),sessionDuration(INT),maxConcurrent(INT)) returns:(success(BOOL),message(STRING))
-<Service-ListOAuthProviders> params:() returns:(success(BOOL),providers([{}]))
-<Service-UpsertOAuthProvider> params:(providerCode(STRING),enabled(BOOL),displayName(STRING),buttonText(STRING),loginUrl(STRING),clientId(STRING),scope(STRING),callbackUrl(STRING)) returns:(success(BOOL),message(STRING),action(STRING))
-<Service-SetOAuthProviderSecret> params:(providerCode(STRING),clientSecret(STRING)) returns:(success(BOOL),message(STRING))
<Col-Root> width:"100%" flex:"1" min-height:"0" padding:"32px" gap:"24px"
-<Col-Header> width:"100%" gap:"4px"
--<Text-Title> value:"系统配置" style:"caption" size:"lg"
--<Text-Hint> value:"登录配置可在此编辑保存（需 admin 角色）；第三方登录本期仅配置入口，clientSecret 仅存不可逆摘要、任何位置不回显，本期不参与真实 OAuth 换 token。" style:"muted" size:"sm"
-<If-InfoM> conditions:($infoMessage != "")
--<Col-InfoBox> width:"100%" padding:"8px 12px" sk.bg:"#f0fdf4" border:"1px solid #bbf7d0"
---<Text-Info> value:$infoMessage style:"caption" size:"sm" sk.fg:"#15803d"
-<If-ErrM> conditions:($errorMessage != "")
--<Col-ErrBox> width:"100%" padding:"8px 12px" sk.bg:"#fef2f2" border:"1px solid #fecaca"
---<Text-Err> value:$errorMessage style:"hint" size:"sm" sk.fg:"#b91c1c"
-<Col-PanelLogin> width:"100%" border:"1px solid #ebedf1"
--<Row-PLH> width:"100%" padding:"12px 16px" align-items:"center" gap:"12px" sk.borderBottom:"1px solid #ebedf1" sk.bg:"#fafafa"
---<Text-PLT> value:"登录配置（可编辑）" style:"caption" size:"sm" flex:"1"
---<Button-LcSave> value:($lcSaving == true ? "保存中..." : "保存") style:"primary|filled|default|sm|actionable" min-height:"28px" min-width:"72px"
--<Col-PLB> width:"100%" padding:"16px" gap:"12px"
---<Row-LcName> width:"100%" gap:"12px" align-items:"center"
----<Text-LcNameL> value:"显示名" min-width:"160px" style:"muted" size:"sm"
----<Input-LcName> style:"neutral|outlined|default|sm" value:$lcDisplayName placeholder:"用户中心显示名" flex:"1" min-height:"32px"
---<Row-LcSwitches> width:"100%" gap:"8px" align-items:"center"
----<Text-LcSwL> value:"开关" min-width:"160px" style:"muted" size:"sm"
----<Button-LcReg> value:("注册：" + ($lcRegistrationOpen ? "开放" : "关闭")) style:"neutral|outlined|default|sm|actionable" min-height:"28px"
----<Button-LcApproval> value:("注册审批：" + ($lcRequireApproval ? "需要" : "不需要")) style:"neutral|outlined|default|sm|actionable" min-height:"28px"
----<Button-LcPwd> value:("账号密码登录：" + ($lcPasswordLoginOpen ? "开放" : "关闭")) style:"neutral|outlined|default|sm|actionable" min-height:"28px"
---<Row-LcRole> width:"100%" gap:"12px" align-items:"center"
----<Text-LcRoleL> value:"注册默认角色" min-width:"160px" style:"muted" size:"sm"
----<Input-LcRole> style:"neutral|outlined|default|sm" value:$lcDefaultRoleCode placeholder:"roleCode（留空=不自动分配）" min-width:"260px" min-height:"32px"
---<Row-LcSess> width:"100%" gap:"12px" align-items:"center"
----<Text-LcSessL> value:"会话时长（秒）" min-width:"160px" style:"muted" size:"sm"
----<Input-LcSess> style:"neutral|outlined|default|sm" value:($lcSessionDuration + "") placeholder:"7200" min-width:"140px" min-height:"32px"
----<Text-LcConcL> value:"最大并发会话" min-width:"120px" style:"muted" size:"sm"
----<Input-LcConc> style:"neutral|outlined|default|sm" value:($lcMaxConcurrent + "") placeholder:"5" min-width:"100px" min-height:"32px"
-<Col-PanelOAuth> width:"100%" border:"1px solid #ebedf1"
--<Row-POH> width:"100%" padding:"12px 16px" align-items:"center" gap:"12px" sk.borderBottom:"1px solid #ebedf1" sk.bg:"#fafafa"
---<Col-POTCol> flex:"1" gap:"2px"
----<Text-POT> value:("第三方登录配置（" + $providers.length + "）") style:"caption" size:"sm"
----<Text-POScope> value:"本期仅支持配置第三方登录入口（登录按钮 + 跳转地址）：点击后跳转 provider 登录页。本期不做回调换 token、不在回调里自动建号或登录；clientSecret 与 callbackUrl 仅作配置留存（secret 仅存不可逆摘要），待平台密钥管理上线后再启用完整 OAuth。" style:"hint" size:"xs"
---<Button-ProvNew> value:"+ 新建/编辑 provider" style:"primary|outlined|default|sm|actionable" min-height:"28px"
--<Row-PCH> width:"100%" padding:"10px 16px" gap:"12px" align-items:"center" sk.borderBottom:"1px solid #ebedf1" sk.bg:"#fafafa"
---<Text-PH1> value:"code" min-width:"110px" style:"muted" size:"xs"
---<Text-PH2> value:"名称/按钮文案" flex:"1" style:"muted" size:"xs"
---<Text-PH3> value:"clientId" flex:"1" style:"muted" size:"xs"
---<Text-PH4> value:"secret" min-width:"80px" style:"muted" size:"xs"
---<Text-PH5> value:"状态" min-width:"70px" style:"muted" size:"xs"
---<Text-PH6> value:"操作" min-width:"130px" style:"muted" size:"xs"
--<If-PEmpty> conditions:($providers.length == 0)
---<Text-PEmpty> value:"暂无 provider。" padding:"16px" style:"hint" size:"sm"
--<For-Provs> sourceArray:$providers loopVar:[_p,_pIdx]
---<Row-PItem> width:"100%" padding:"10px 16px" gap:"12px" align-items:"center" sk.borderBottom:"1px solid #f1f3f5"
----<Text-PC> value:_p.providerCode min-width:"110px" style:"caption" size:"sm"
----<Text-PN> value:(_p.displayName + (_p.buttonText != "" ? ("（" + _p.buttonText + "）") : "")) flex:"1" style:"muted" size:"sm"
----<Text-PCl> value:(_p.clientId != "" ? _p.clientId : "-") flex:"1" style:"muted" size:"sm"
----<Text-PSec> value:(_p.hasSecret == true ? "已设置" : "未设置") min-width:"80px" style:"caption" size:"xs"
----<If-PEn> conditions:(_p.enabled == true)
-----<Text-PEnV> value:"启用" min-width:"70px" style:"caption" size:"sm" sk.fg:"#15803d"
----<If-PDis> conditions:(_p.enabled != true)
-----<Text-PDisV> value:"停用" min-width:"70px" style:"muted" size:"sm"
----<Row-PAct> min-width:"130px" gap:"6px"
-----<Button-PEdit> value:"编辑" style:"primary|ghost|default|sm|actionable" min-height:"26px"
-----<If-PCanOff> conditions:(_p.enabled == true)
------<Button-POff> value:"停用" style:"danger|ghost|default|sm|actionable" min-height:"26px"
-----<If-PCanOn> conditions:(_p.enabled != true)
------<Button-POn> value:"启用" style:"success|ghost|default|sm|actionable" min-height:"26px"
-<If-ProvEdit> conditions:($provEditOpen == true)
--<Col-PEMask> position:"absolute" top:"0" left:"0" width:"100%" height:"100%" align-items:"center" justify-content:"center" sk.bg:"rgba(0,0,0,0.32)" z-index:"200"
---<Col-PEDialog> width:"520px" sk.bg:"#ffffff" border:"1px solid #ebedf1"
----<Row-PEH> width:"100%" padding:"12px 16px" align-items:"center" gap:"12px" sk.borderBottom:"1px solid #ebedf1" sk.bg:"#fafafa"
-----<Text-PET> value:"新建/编辑 OAuth provider" style:"caption" size:"sm" flex:"1"
-----<Button-PEClose> value:"×" style:"neutral|ghost|default|sm|actionable" min-width:"28px" min-height:"28px"
----<Col-PEB> width:"100%" padding:"16px" gap:"10px"
-----<Row-PECode> width:"100%" gap:"12px" align-items:"center"
------<Text-PECodeL> value:"providerCode" min-width:"130px" style:"muted" size:"sm"
------<Input-PECode> style:"neutral|outlined|default|sm" value:$provCode placeholder:"如 oauth_wechat（已存在则为编辑）" flex:"1" min-height:"32px"
-----<Row-PEName> width:"100%" gap:"12px" align-items:"center"
------<Text-PENameL> value:"显示名 / 按钮文案" min-width:"130px" style:"muted" size:"sm"
------<Input-PEName> style:"neutral|outlined|default|sm" value:$provDisplayName placeholder:"显示名" flex:"1" min-height:"32px"
------<Input-PEBtn> style:"neutral|outlined|default|sm" value:$provButtonText placeholder:"按钮文案" flex:"1" min-height:"32px"
-----<Row-PEUrl> width:"100%" gap:"12px" align-items:"center"
------<Text-PEUrlL> value:"loginUrl" min-width:"130px" style:"muted" size:"sm"
------<Input-PEUrl> style:"neutral|outlined|default|sm" value:$provLoginUrl placeholder:"第三方登录入口 URL" flex:"1" min-height:"32px"
-----<Row-PECid> width:"100%" gap:"12px" align-items:"center"
------<Text-PECidL> value:"clientId / scope" min-width:"130px" style:"muted" size:"sm"
------<Input-PECid> style:"neutral|outlined|default|sm" value:$provClientId placeholder:"clientId" flex:"1" min-height:"32px"
------<Input-PEScope> style:"neutral|outlined|default|sm" value:$provScope placeholder:"scope" flex:"1" min-height:"32px"
-----<Row-PECb> width:"100%" gap:"12px" align-items:"center"
------<Text-PECbL> value:"callbackUrl（预留）" min-width:"130px" style:"muted" size:"sm"
------<Input-PECb> style:"neutral|outlined|default|sm" value:$provCallbackUrl placeholder:"回调 URL（预留，本期不触发回调换 token）" flex:"1" min-height:"32px"
-----<Row-PESec> width:"100%" gap:"12px" align-items:"center"
------<Text-PESecL> value:"clientSecret" min-width:"130px" style:"muted" size:"sm"
------<Input-PESec> style:"neutral|outlined|default|sm" value:$provSecret type:"password" placeholder:"留空=不修改；仅存不可逆摘要、永不回显；本期不用于 OAuth 换 token" flex:"1" min-height:"32px"
-----<Row-PEEn> width:"100%" gap:"12px" align-items:"center"
------<Text-PEEnL> value:"状态" min-width:"130px" style:"muted" size:"sm"
------<Button-PEEnToggle> value:($provEnabled ? "启用" : "停用") style:"neutral|outlined|default|sm|actionable" min-height:"28px"
----<Row-PEF> width:"100%" padding:"12px 16px" gap:"8px" justify-content:"flex-end" sk.borderTop:"1px solid #ebedf1" sk.bg:"#fafafa"
-----<Button-PECancel> value:"取消" style:"neutral|outlined|default|sm|actionable" min-height:"30px" min-width:"72px"
-----<Button-PESave> value:($provSaving == true ? "保存中..." : "保存") style:"primary|filled|default|sm|actionable" min-height:"30px" min-width:"72px"
-<Col-PanelA> width:"100%" border:"1px solid #ebedf1"
--<Row-PAH> width:"100%" padding:"12px 16px" align-items:"center" gap:"8px" sk.borderBottom:"1px solid #ebedf1" sk.bg:"#fafafa"
---<Text-PAT> value:"接入诊断（只读）" style:"caption" size:"sm" flex:"1"
---<Button-ToggleRuntime> value:($showRuntime == true ? "收起" : "展开") style:"neutral|ghost|default|sm|actionable" min-height:"28px" min-width:"56px"
---<Button-Reload> value:"刷新" style:"neutral|outlined|default|sm|actionable" min-height:"28px"
--<If-Runtime> conditions:($showRuntime == true)
---<Col-PAB> width:"100%"
----<Row-FEnv> width:"100%" padding:"12px 16px" gap:"12px" sk.borderBottom:"1px solid #f1f3f5"
-----<Text-FEnvL> value:"环境" min-width:"200px" style:"muted" size:"sm"
-----<Text-FEnvV> value:($config.env ? $config.env : "-") style:"caption" size:"sm" flex:"1"
----<Row-FSelfNid> width:"100%" padding:"12px 16px" gap:"12px" sk.borderBottom:"1px solid #f1f3f5"
-----<Text-FSNL> value:"用户中心后端编号" min-width:"200px" style:"muted" size:"sm"
-----<Text-FSNV> value:($config.userPermissionCenterNid ? $config.userPermissionCenterNid : "-") style:"caption" size:"sm" flex:"1"
----<Row-FSelfUrl> width:"100%" padding:"12px 16px" gap:"12px" sk.borderBottom:"1px solid #f1f3f5"
-----<Text-FSUL> value:"用户中心 URL" min-width:"200px" style:"muted" size:"sm"
-----<Text-FSUV> value:($config.userCenterUrl ? $config.userCenterUrl : "-") style:"caption" size:"sm" flex:"1"
----<Row-FAuthApi> width:"100%" padding:"12px 16px" gap:"12px" sk.borderBottom:"1px solid #f1f3f5"
-----<Text-FAAL> value:"身份接口地址" min-width:"200px" style:"muted" size:"sm"
-----<Text-FAAV> value:($config.authApiBase ? $config.authApiBase : "-") style:"caption" size:"sm" flex:"1"
----<Row-FBUrl> width:"100%" padding:"12px 16px" gap:"12px" sk.borderBottom:"1px solid #f1f3f5"
-----<Text-FBUrlL> value:"权限中心 URL" min-width:"200px" style:"muted" size:"sm"
-----<Text-FBUrlV> value:($config.permissionCenterUrl ? $config.permissionCenterUrl : "-") style:"caption" size:"sm" flex:"1"
----<Row-FPAB> width:"100%" padding:"12px 16px" gap:"12px"
-----<Text-FPABL> value:"权限接口地址" min-width:"200px" style:"muted" size:"sm"
-----<Text-FPABV> value:($config.permissionApiBase ? $config.permissionApiBase : "-") style:"caption" size:"sm" flex:"1"
-<Col-Notice> width:"100%" padding:"16px" gap:"8px" border:"1px solid #ebedf1" sk.bg:"#f5f6f8"
--<Text-NT> value:"说明" style:"caption" size:"sm"
--<Text-N1> value:"用户中心负责身份系统：用户、角色、部门与登录状态。" style:"muted" size:"sm"
--<Text-N2> value:"管理员判定、超级管理员与子管理员授权、项目管理范围在「权限中心」维护。" style:"muted" size:"sm"
--<Text-N3> value:"登录配置、OAuth、实例绑定等保存动作需要管理员角色。" style:"muted" size:"sm"

# Frontend Event Handlers
<Section-AdminSettingsPage "root">.@init()
-ReloadConfig() -> _r

<Button-Reload>.@click()
-ReloadConfig() -> _r

<Button-ToggleRuntime>.@click()
-$showRuntime = !$showRuntime

<Input-LcName>.@change(value)
-$lcDisplayName = value

<Button-LcReg>.@click()
-$lcRegistrationOpen = !$lcRegistrationOpen

<Button-LcApproval>.@click()
-$lcRequireApproval = !$lcRequireApproval

<Button-LcPwd>.@click()
-$lcPasswordLoginOpen = !$lcPasswordLoginOpen

<Input-LcRole>.@change(value)
-$lcDefaultRoleCode = value

<Input-LcSess>.@change(value)
-$lcSessionDuration = value * 1

<Input-LcConc>.@change(value)
-$lcMaxConcurrent = value * 1

<Button-LcSave>.@click()
-IF $lcSaving == true
--RETURN
-$lcSaving = true
-<ServiceDomain-UserCenterService "ucService">.SaveLoginConfig($lcDisplayName, $lcRegistrationOpen, $lcRequireApproval, $lcPasswordLoginOpen, $lcDefaultRoleCode, $lcSessionDuration, $lcMaxConcurrent) -> _r
-$lcSaving = false
-IF !_r.success
--$errorMessage = _r.message
--RETURN
-$errorMessage = ""
-$infoMessage = "登录配置已保存，注册/登录链路即时生效"
-ReloadConfig() -> _r2

<Button-ProvNew>.@click()
-$provEditOpen = true
-$provCode = ""
-$provEnabled = false
-$provDisplayName = ""
-$provButtonText = ""
-$provLoginUrl = ""
-$provClientId = ""
-$provScope = ""
-$provCallbackUrl = ""
-$provSecret = ""

<Button-PEdit>.@click()
-$provEditOpen = true
-$provCode = _p.providerCode
-$provEnabled = _p.enabled == true
-$provDisplayName = _p.displayName
-$provButtonText = _p.buttonText
-$provLoginUrl = _p.loginUrl
-$provClientId = _p.clientId
-$provScope = _p.scope
-$provCallbackUrl = _p.callbackUrl
-$provSecret = ""

<Button-POff>.@click()
-<ServiceDomain-UserCenterService "ucService">.UpsertOAuthProvider(_p.providerCode, false, _p.displayName, _p.buttonText, _p.loginUrl, _p.clientId, _p.scope, _p.callbackUrl) -> _r
-IF !_r.success
--$errorMessage = _r.message
--RETURN
-$infoMessage = "已停用 " + _p.providerCode
-ReloadConfig() -> _r2

<Button-POn>.@click()
-<ServiceDomain-UserCenterService "ucService">.UpsertOAuthProvider(_p.providerCode, true, _p.displayName, _p.buttonText, _p.loginUrl, _p.clientId, _p.scope, _p.callbackUrl) -> _r
-IF !_r.success
--$errorMessage = _r.message
--RETURN
-$infoMessage = "已启用 " + _p.providerCode
-ReloadConfig() -> _r2

<Button-PEClose>.@click()
-$provEditOpen = false

<Button-PECancel>.@click()
-$provEditOpen = false

<Input-PECode>.@change(value)
-$provCode = value

<Input-PEName>.@change(value)
-$provDisplayName = value

<Input-PEBtn>.@change(value)
-$provButtonText = value

<Input-PEUrl>.@change(value)
-$provLoginUrl = value

<Input-PECid>.@change(value)
-$provClientId = value

<Input-PEScope>.@change(value)
-$provScope = value

<Input-PECb>.@change(value)
-$provCallbackUrl = value

<Input-PESec>.@change(value)
-$provSecret = value

<Button-PEEnToggle>.@click()
-$provEnabled = !$provEnabled

<Button-PESave>.@click()
-IF $provSaving == true
--RETURN
-IF $provCode == ""
--$errorMessage = "providerCode 必填"
--RETURN
-$provSaving = true
-<ServiceDomain-UserCenterService "ucService">.UpsertOAuthProvider($provCode, $provEnabled, $provDisplayName, $provButtonText, $provLoginUrl, $provClientId, $provScope, $provCallbackUrl) -> _r
-IF !_r.success
--$provSaving = false
--$errorMessage = _r.message
--RETURN
-IF $provSecret != ""
--<ServiceDomain-UserCenterService "ucService">.SetOAuthProviderSecret($provCode, $provSecret) -> _sec
--IF !_sec.success
---$provSaving = false
---$errorMessage = "provider 已保存，但 secret 设置失败: " + _sec.message
---$provSecret = ""
---RETURN
-$provSaving = false
-$provSecret = ""
-$provEditOpen = false
-$errorMessage = ""
-$infoMessage = "provider 已保存"
-ReloadConfig() -> _r2

# Frontend Internal Methods

# Frontend Pipeline Funcs

</Section-AdminSettingsPage>
