|
10 | 10 | return s.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); |
11 | 11 | } |
12 | 12 |
|
| 13 | + /** Handle v6.0.0 views_available using view_id instead of id */ |
| 14 | + function viewId_(view: any): string { |
| 15 | + return view.id || view.view_id || ""; |
| 16 | + } |
| 17 | +
|
13 | 18 | let userId = $derived(data.userId || ""); |
14 | 19 | let copied = $state(false); |
15 | 20 |
|
|
124 | 129 |
|
125 | 130 | const settled = await Promise.allSettled( |
126 | 131 | views.map(async (view) => { |
| 132 | + const vid = viewId_(view); |
127 | 133 | const res = await trackedFetch( |
128 | | - `/api/obp/banks/${encodeURIComponent(bankId)}/accounts/${encodeURIComponent(accountId)}/views/${encodeURIComponent(view.id)}/users-with-access` |
| 134 | + `/api/obp/banks/${encodeURIComponent(bankId)}/accounts/${encodeURIComponent(accountId)}/views/${encodeURIComponent(vid)}/users-with-access` |
129 | 135 | ); |
130 | 136 | if (!res.ok) { |
131 | 137 | const data = await res.json().catch(() => ({})); |
132 | | - throw new Error(data.error || `Failed to fetch users with access for view ${view.id}`); |
| 138 | + throw new Error(data.error || `Failed to fetch users with access for view ${vid}`); |
133 | 139 | } |
134 | 140 | const data = await res.json(); |
135 | | - return { viewId: view.id, users: data.users || [] }; |
| 141 | + return { viewId: vid, users: data.users || [] }; |
136 | 142 | }) |
137 | 143 | ); |
138 | 144 |
|
|
142 | 148 |
|
143 | 149 | for (let i = 0; i < settled.length; i++) { |
144 | 150 | const result = settled[i]; |
145 | | - const viewId = views[i].id; |
| 151 | + const viewId = viewId_(views[i]); |
146 | 152 | if (result.status === "fulfilled") { |
147 | 153 | anySuccess = true; |
148 | 154 | const entry = { direct: [] as string[], abac: [] as string[] }; |
|
176 | 182 | usersWithAccess = null; |
177 | 183 | usersByView = new Map(); |
178 | 184 | viewErrors = new Map(); |
179 | | - await checkAccountAccess(bankId, accountId, viewId); |
180 | | - if (hasAccountAccess) { |
181 | | - await fetchAccount(bankId, accountId, viewId); |
182 | | - if (account?.views_available?.length) { |
183 | | - await fetchUsersWithAccess(bankId, accountId, account.views_available); |
184 | | - } |
| 185 | + // Run access check and account fetch in parallel — access check is informational only |
| 186 | + await Promise.all([ |
| 187 | + checkAccountAccess(bankId, accountId, viewId), |
| 188 | + fetchAccount(bankId, accountId, viewId), |
| 189 | + ]); |
| 190 | + if (account?.views_available?.length) { |
| 191 | + await fetchUsersWithAccess(bankId, accountId, account.views_available); |
185 | 192 | } |
186 | 193 | } |
187 | 194 |
|
|
204 | 211 | <span class="breadcrumb-current">{account?.label || accountId}</span> |
205 | 212 | </nav> |
206 | 213 |
|
207 | | - {#if !accessCheckDone} |
208 | | - <div class="loading-state"> |
209 | | - <Loader2 size={32} class="spinner-icon" /> |
210 | | - <p>Checking account access...</p> |
211 | | - </div> |
212 | | - {:else if hasAccountAccess === false} |
213 | | - <div class="access-warning"> |
214 | | - You may need further ABAC Access in order to access this account. |
215 | | - </div> |
216 | | - <details class="debug-panel"> |
217 | | - <summary class="debug-summary"> |
218 | | - Debug Status |
219 | | - <button class="copy-btn" onclick={(e) => { e.stopPropagation(); copyDebugInfo(); }} title="Copy all debug info"> |
220 | | - {#if copied}<Check size={14} />{:else}<Copy size={14} />{/if} |
221 | | - </button> |
222 | | - </summary> |
223 | | - <div class="debug-content"> |
224 | | - <div class="debug-grid"> |
225 | | - <span class="debug-label">User ID</span> |
226 | | - <span class="debug-value">{userId || "—"}</span> |
227 | | - <span class="debug-label">Bank ID</span> |
228 | | - <span class="debug-value">{bankId || "—"}</span> |
229 | | - <span class="debug-label">Account ID</span> |
230 | | - <span class="debug-value">{accountId || "—"}</span> |
231 | | - <span class="debug-label">View ID</span> |
232 | | - <span class="debug-value">{viewId || "—"}</span> |
233 | | - <span class="debug-label">Access Check Done</span> |
234 | | - <span class="debug-value">{accessCheckDone}</span> |
235 | | - <span class="debug-label">Has Account Access</span> |
236 | | - <span class="debug-value debug-false">{String(hasAccountAccess)}</span> |
237 | | - <span class="debug-label">Access Source</span> |
238 | | - <span class="debug-value">{accessSource || "—"}</span> |
239 | | - <span class="debug-label">ABAC Rule ID</span> |
240 | | - <span class="debug-value">{abacRuleId || "—"}</span> |
241 | | - <span class="debug-label">Has CanExecuteAbacRule</span> |
242 | | - <span class="debug-value" class:debug-true={hasAbacRole} class:debug-false={!hasAbacRole}>{hasAbacRole}</span> |
243 | | - <span class="debug-label">Account Loading</span> |
244 | | - <span class="debug-value">{loading}</span> |
245 | | - <span class="debug-label">Account Loaded</span> |
246 | | - <span class="debug-value" class:debug-true={!!account} class:debug-false={!account && accessCheckDone && hasAccountAccess}>{!!account}</span> |
247 | | - <span class="debug-label">Error</span> |
248 | | - <span class="debug-value" class:debug-false={!!error}>{error || "none"}</span> |
249 | | - <span class="debug-label">User Entitlements</span> |
250 | | - <span class="debug-value">{userEntitlements.length} entitlement{userEntitlements.length !== 1 ? "s" : ""}</span> |
251 | | - </div> |
252 | | - </div> |
253 | | - </details> |
254 | | - {#if !hasAbacRole} |
255 | | - <MissingRoleAlert |
256 | | - roles={["CanExecuteAbacRule"]} |
257 | | - message="You may need this role to gain ABAC access to accounts." |
258 | | - /> |
259 | | - {/if} |
260 | | - {:else if loading} |
| 214 | + {#if loading} |
261 | 215 | <div class="loading-state"> |
262 | 216 | <Loader2 size={32} class="spinner-icon" /> |
263 | 217 | <p>Loading account...</p> |
|
297 | 251 |
|
298 | 252 | <!-- Content --> |
299 | 253 | <div class="panel-content"> |
| 254 | + {#if accessCheckDone && hasAccountAccess === false} |
| 255 | + <div class="access-warning"> |
| 256 | + You may need further ABAC Access in order to access this account. |
| 257 | + </div> |
| 258 | + {/if} |
300 | 259 | <!-- Account Info + Owners row --> |
301 | 260 | <div class="info-owners-row"> |
302 | 261 | <!-- Basic Info --> |
|
444 | 403 | <div class="views-table"> |
445 | 404 | <div class="views-table-header"> |
446 | 405 | <div class="views-col-name">View</div> |
| 406 | + <div class="views-col-link">Transactions</div> |
| 407 | + <div class="views-col-link">Counterparties</div> |
447 | 408 | <div class="views-col-users">Direct Access</div> |
448 | 409 | <div class="views-col-users">ABAC Access</div> |
449 | 410 | </div> |
450 | 411 | {#each account.views_available as view} |
451 | | - {@const viewUsers = usersByView.get(view.id)} |
452 | | - {@const viewError = viewErrors.get(view.id)} |
| 412 | + {@const vid = viewId_(view)} |
| 413 | + {@const viewUsers = usersByView.get(vid)} |
| 414 | + {@const viewError = viewErrors.get(vid)} |
453 | 415 | <div class="views-table-row"> |
454 | 416 | <div class="views-col-name"> |
455 | | - <a href="/account-access/accounts/{encodeURIComponent(bankId)}/{encodeURIComponent(accountId)}/{encodeURIComponent(view.id)}/transactions" class="view-name-link">{toTitleCase(view.id)}</a> |
| 417 | + <a href="/account-access/{view.is_system ? 'system-views' : 'custom-views'}/{encodeURIComponent(vid)}" class="view-name-link">{toTitleCase(vid)}</a> |
456 | 418 | {#if view.is_public} |
457 | 419 | <span class="view-badge public">PUBLIC</span> |
458 | 420 | {/if} |
459 | 421 | </div> |
| 422 | + <div class="views-col-link"> |
| 423 | + <a href="/account-access/accounts/{encodeURIComponent(bankId)}/{encodeURIComponent(accountId)}/{encodeURIComponent(vid)}/transactions" class="view-name-link">Transactions</a> |
| 424 | + </div> |
| 425 | + <div class="views-col-link"> |
| 426 | + <a href="/account-access/accounts/{encodeURIComponent(bankId)}/{encodeURIComponent(accountId)}/{encodeURIComponent(vid)}/counterparties" class="view-name-link">Counterparties</a> |
| 427 | + </div> |
460 | 428 | {#if viewError} |
461 | 429 | <div class="views-col-users view-error" style="grid-column: span 2;"> |
462 | 430 | {viewError} |
|
1086 | 1054 |
|
1087 | 1055 | .views-table-header { |
1088 | 1056 | display: grid; |
1089 | | - grid-template-columns: 1fr 1fr 1fr; |
| 1057 | + grid-template-columns: 1fr auto auto 1fr 1fr; |
1090 | 1058 | gap: 0; |
1091 | 1059 | background: #f3f4f6; |
1092 | 1060 | border-bottom: 1px solid #e5e7eb; |
|
1109 | 1077 |
|
1110 | 1078 | .views-table-row { |
1111 | 1079 | display: grid; |
1112 | | - grid-template-columns: 1fr 1fr 1fr; |
| 1080 | + grid-template-columns: 1fr auto auto 1fr 1fr; |
1113 | 1081 | gap: 0; |
1114 | 1082 | border-bottom: 1px solid #e5e7eb; |
1115 | 1083 | } |
|
1130 | 1098 | flex-wrap: wrap; |
1131 | 1099 | } |
1132 | 1100 |
|
| 1101 | + .views-col-link { |
| 1102 | + padding: 0.5rem 0.75rem; |
| 1103 | + display: flex; |
| 1104 | + align-items: center; |
| 1105 | + } |
| 1106 | +
|
1133 | 1107 | .views-col-users { |
1134 | 1108 | padding: 0.5rem 0.75rem; |
1135 | 1109 | display: flex; |
|
1147 | 1121 | color: var(--color-surface-500); |
1148 | 1122 | } |
1149 | 1123 |
|
| 1124 | + .view-name { |
| 1125 | + font-size: 0.875rem; |
| 1126 | + font-weight: 600; |
| 1127 | + color: #111827; |
| 1128 | + } |
| 1129 | +
|
| 1130 | + :global([data-mode="dark"]) .view-name { |
| 1131 | + color: var(--color-surface-100); |
| 1132 | + } |
| 1133 | +
|
1150 | 1134 | .view-name-link { |
1151 | 1135 | font-size: 0.875rem; |
1152 | 1136 | font-weight: 600; |
|
0 commit comments