fix #492: add token context selection
This commit is contained in:
@@ -335,12 +335,29 @@ body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.token-form {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.token-form .rw-btn-primary {
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
.form-grid label {
|
||||
color: var(--rw-muted);
|
||||
font-size: .875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
color: var(--rw-muted);
|
||||
display: block;
|
||||
font-size: .78rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.35;
|
||||
margin-top: .35rem;
|
||||
}
|
||||
|
||||
.form-grid .span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
@@ -547,6 +564,29 @@ body {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.token-context-preview {
|
||||
align-self: stretch;
|
||||
background: var(--rw-surface);
|
||||
border: 1px solid var(--rw-border);
|
||||
border-radius: 8px;
|
||||
padding: .9rem;
|
||||
}
|
||||
|
||||
.token-context-preview .compact-heading {
|
||||
gap: .5rem;
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
|
||||
.token-context-preview .compact-heading h3 {
|
||||
font-size: .92rem;
|
||||
font-weight: 800;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.token-context-preview .form-hint {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.policy-apply-flow {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
@@ -441,6 +441,51 @@ function setWizardPreview(wizard, name, value) {
|
||||
});
|
||||
}
|
||||
|
||||
function setTokenPreview(preview, name, value) {
|
||||
preview.querySelectorAll(`[data-token-preview="${name}"]`).forEach((target) => {
|
||||
target.textContent = value;
|
||||
});
|
||||
}
|
||||
|
||||
function updateTokenContextPreview(select) {
|
||||
const form = select.closest('form');
|
||||
const preview = form?.querySelector('[data-token-context-preview]');
|
||||
if (!preview) {
|
||||
return;
|
||||
}
|
||||
const option = selectedOption(select);
|
||||
if (!option || !option.value) {
|
||||
setTokenPreview(preview, 'status', '미선택');
|
||||
setTokenPreview(preview, 'username', '원문 직접 입력');
|
||||
setTokenPreview(preview, 'prefix', '-');
|
||||
setTokenPreview(preview, 'expiresAt', '-');
|
||||
setTokenPreview(preview, 'directRoles', '-');
|
||||
setTokenPreview(preview, 'groups', '-');
|
||||
setTokenPreview(preview, 'inheritedRoles', '-');
|
||||
setTokenPreview(preview, 'description', '토큰을 선택하면 등록된 사용자/역할 컨텍스트를 먼저 확인할 수 있습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const directRoles = splitList(option.dataset.directRoles || '');
|
||||
const groups = splitList(option.dataset.groups || '');
|
||||
const inheritedRoles = splitList(option.dataset.inheritedRoles || '');
|
||||
setTokenPreview(preview, 'status', option.dataset.status || '-');
|
||||
setTokenPreview(preview, 'username', option.dataset.username || '-');
|
||||
setTokenPreview(preview, 'prefix', option.dataset.prefix || '****');
|
||||
setTokenPreview(preview, 'expiresAt', option.dataset.expiresAt || '-');
|
||||
setTokenPreview(preview, 'directRoles', formatList(directRoles, '직접 역할 없음'));
|
||||
setTokenPreview(preview, 'groups', formatList(groups, '소속 그룹 없음'));
|
||||
setTokenPreview(preview, 'inheritedRoles', formatList(inheritedRoles, '그룹 상속 역할 없음'));
|
||||
setTokenPreview(preview, 'description', option.dataset.description || '설명 없음');
|
||||
}
|
||||
|
||||
function initTokenContextPreviews() {
|
||||
document.querySelectorAll('[data-token-context-select]').forEach((select) => {
|
||||
select.addEventListener('change', () => updateTokenContextPreview(select));
|
||||
updateTokenContextPreview(select);
|
||||
});
|
||||
}
|
||||
|
||||
function sqlLiteral(value) {
|
||||
return `'${String(value || '').replaceAll("'", "''")}'`;
|
||||
}
|
||||
@@ -745,6 +790,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
select.addEventListener('change', () => filterGroupDetail(select.id));
|
||||
filterGroupDetail(select.id);
|
||||
});
|
||||
initTokenContextPreviews();
|
||||
const objectSelect = document.querySelector('select[name="objectRef"]');
|
||||
if (objectSelect) {
|
||||
objectSelect.addEventListener('change', () => {
|
||||
|
||||
@@ -22,12 +22,63 @@
|
||||
<h2>권한 결과 해석</h2>
|
||||
<a class="btn btn-sm btn-outline-secondary" href="/mcp/tools" target="_blank" rel="noreferrer">도구 JSON</a>
|
||||
</div>
|
||||
<form hx-post="/mcp-reasoning" hx-target="#mcp-result" hx-swap="innerHTML" class="form-grid">
|
||||
<form hx-post="/mcp-reasoning" hx-target="#mcp-result" hx-swap="innerHTML" class="form-grid token-form">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
등록 토큰
|
||||
<select class="form-select" name="tokenKeyId" data-token-context-select>
|
||||
<option value="">원문 직접 입력</option>
|
||||
<option th:each="token : ${tokens}"
|
||||
th:value="${token.keyId()}"
|
||||
th:text="${token.displayLabel()}"
|
||||
th:attr="data-username=${token.username()},
|
||||
data-prefix=${token.maskedToken()},
|
||||
data-status=${token.statusLabel()},
|
||||
data-expires-at=${token.expiresAt()},
|
||||
data-description=${token.description()},
|
||||
data-direct-roles=${#strings.listJoin(token.directRoles(), '|')},
|
||||
data-groups=${#strings.listJoin(token.groups(), '|')},
|
||||
data-inherited-roles=${#strings.listJoin(token.inheritedRoles(), '|')}"></option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Bearer Token 원문
|
||||
<input class="form-control" name="bearerToken" type="password" autocomplete="off" required>
|
||||
<span class="form-hint">선택 토큰은 컨텍스트 확인용입니다. ORDS 호출에는 원문 입력이 필요합니다.</span>
|
||||
</label>
|
||||
<aside class="token-context-preview effective-preview span-2" data-token-context-preview>
|
||||
<div class="section-heading compact-heading">
|
||||
<h3>선택 토큰 컨텍스트</h3>
|
||||
<span class="badge text-bg-secondary" data-token-preview="status">미선택</span>
|
||||
</div>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>사용자</dt>
|
||||
<dd data-token-preview="username">원문 직접 입력</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Prefix</dt>
|
||||
<dd><code data-token-preview="prefix">-</code></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>만료</dt>
|
||||
<dd data-token-preview="expiresAt">-</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>직접 역할</dt>
|
||||
<dd data-token-preview="directRoles">-</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>그룹</dt>
|
||||
<dd data-token-preview="groups">-</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>그룹 상속 역할</dt>
|
||||
<dd data-token-preview="inheritedRoles">-</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<p class="form-hint" data-token-preview="description">질문 전에 이 토큰이 어느 사용자/역할 컨텍스트인지 확인합니다.</p>
|
||||
</aside>
|
||||
<label>
|
||||
조회 대상
|
||||
<select class="form-select" id="mcp-reasoning-object" name="objectId" required>
|
||||
|
||||
@@ -16,12 +16,63 @@
|
||||
<h2>ORDS 호출</h2>
|
||||
<a class="btn btn-sm rw-btn-secondary" href="/mcp-reasoning">MCP Reasoning으로 해석</a>
|
||||
</div>
|
||||
<form hx-post="/probe" hx-target="#probe-result" hx-swap="innerHTML" class="form-grid">
|
||||
<form hx-post="/probe" hx-target="#probe-result" hx-swap="innerHTML" class="form-grid token-form">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
|
||||
<label>
|
||||
등록 토큰
|
||||
<select class="form-select" name="tokenKeyId" data-token-context-select>
|
||||
<option value="">원문 직접 입력</option>
|
||||
<option th:each="token : ${tokens}"
|
||||
th:value="${token.keyId()}"
|
||||
th:text="${token.displayLabel()}"
|
||||
th:attr="data-username=${token.username()},
|
||||
data-prefix=${token.maskedToken()},
|
||||
data-status=${token.statusLabel()},
|
||||
data-expires-at=${token.expiresAt()},
|
||||
data-description=${token.description()},
|
||||
data-direct-roles=${#strings.listJoin(token.directRoles(), '|')},
|
||||
data-groups=${#strings.listJoin(token.groups(), '|')},
|
||||
data-inherited-roles=${#strings.listJoin(token.inheritedRoles(), '|')}"></option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Bearer Token 원문
|
||||
<input class="form-control" name="bearerToken" type="password" autocomplete="off" required>
|
||||
<span class="form-hint">등록 토큰을 선택해도 원문은 저장되지 않아 실제 호출 시 필요합니다.</span>
|
||||
</label>
|
||||
<aside class="token-context-preview effective-preview span-2" data-token-context-preview>
|
||||
<div class="section-heading compact-heading">
|
||||
<h3>선택 토큰 컨텍스트</h3>
|
||||
<span class="badge text-bg-secondary" data-token-preview="status">미선택</span>
|
||||
</div>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>사용자</dt>
|
||||
<dd data-token-preview="username">원문 직접 입력</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Prefix</dt>
|
||||
<dd><code data-token-preview="prefix">-</code></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>만료</dt>
|
||||
<dd data-token-preview="expiresAt">-</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>직접 역할</dt>
|
||||
<dd data-token-preview="directRoles">-</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>그룹</dt>
|
||||
<dd data-token-preview="groups">-</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>그룹 상속 역할</dt>
|
||||
<dd data-token-preview="inheritedRoles">-</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<p class="form-hint" data-token-preview="description">토큰을 선택하면 등록된 사용자/역할 컨텍스트를 먼저 확인할 수 있습니다.</p>
|
||||
</aside>
|
||||
<label>
|
||||
ORDS 트랙
|
||||
<select class="form-select" name="objectId" required>
|
||||
|
||||
Reference in New Issue
Block a user