Skip to content

Commit fc56fd0

Browse files
guguclaude
andauthored
feat(frontend): allow editing public permissions from the permissions… (#1863)
* feat(frontend): allow editing public permissions from the permissions page The backend already supported public (unauthenticated) access via GET/PUT /connection/public-permissions/:connectionId, but nothing in the UI reached those endpoints — public access could only be configured by calling the API by hand. Add a "Public access" expansion panel to /permissions/:connection-id: - status badge showing Disabled or the number of exposed tables - warning that listed tables are readable by anyone without signing in, and that access is read-only - checkbox per table plus a multi-select column whitelist (empty = all columns), with columns fetched lazily per table - Save, plus Disable public access which saves an empty table list "Enabled" stays a derived value rather than a stored flag, mirroring the backend, which computes it from whether the generated policy is non-empty. The public-permissions GET is connection:edit guarded while setActiveConnection runs for every URL carrying a connection id, so the resource is gated on an opt-in that names its connection. Without that, the request would fire on every page and 403 for non-admins. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(frontend): address review findings on the public access panel - Preload columns for every already-public table, not only the ones carrying a readableColumns whitelist. An unrestricted table still renders the column picker, so it previously showed an empty select with no options until the user unchecked and rechecked the table. - Clear tablesLoading when fetchTables fails. TablesService.fetchTables does not swallow errors, so a failure hit an absent error handler and pinned the panel body on the content loader indefinitely. - Disable the table checkboxes and column selects while a save is in flight. Editing during that window was the only case where the post-reload re-seed could discard an unsaved edit; a one-shot seeding guard was avoided because it would latch onto the pre-load empty state and would also suppress legitimate re-sync when public access changes elsewhere. - Type the mocked publicPermissions signals in users.component.spec.ts instead of casting them to any. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 2096f0c commit fc56fd0

10 files changed

Lines changed: 769 additions & 1 deletion
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
.public-access-panel {
2+
margin-bottom: 20px;
3+
}
4+
5+
.mat-expansion-panel-header-title {
6+
align-items: center;
7+
row-gap: 0;
8+
line-height: 1.3;
9+
}
10+
11+
.public-access-icon {
12+
margin-right: 8px;
13+
flex-shrink: 0;
14+
opacity: 0.7;
15+
}
16+
17+
/* Mirrors .group-system-badge on the parent permissions page. */
18+
.public-access-badge {
19+
font-size: 10px;
20+
font-weight: 600;
21+
text-transform: uppercase;
22+
letter-spacing: 0.5px;
23+
padding: 2px 6px;
24+
border-radius: 10px;
25+
background: color-mix(in srgb, var(--color-alternativePalette-500), transparent 88%);
26+
color: var(--color-alternativePalette-500);
27+
margin-left: 8px;
28+
white-space: nowrap;
29+
}
30+
31+
.public-access-badge_on {
32+
background: color-mix(in srgb, var(--color-warningPalette-500), transparent 88%);
33+
color: var(--color-warningPalette-500);
34+
}
35+
36+
.public-access-warning {
37+
display: flex;
38+
align-items: flex-start;
39+
gap: 16px;
40+
padding: 12px 16px;
41+
border: 1px solid var(--color-warningPalette-500);
42+
border-radius: 4px;
43+
background: var(--warning-background-color);
44+
color: var(--color-warningPalette-500);
45+
font-size: 13px;
46+
margin-bottom: 16px;
47+
}
48+
49+
.public-access-warning mat-icon {
50+
flex-shrink: 0;
51+
}
52+
53+
.public-access-tables {
54+
list-style: none;
55+
margin: 0;
56+
padding: 0;
57+
}
58+
59+
.public-access-table {
60+
display: flex;
61+
align-items: center;
62+
flex-wrap: wrap;
63+
gap: 12px;
64+
padding: 6px 0;
65+
}
66+
67+
.public-access-table_selected {
68+
padding-bottom: 12px;
69+
}
70+
71+
.public-access-columns {
72+
flex: 1 1 16em;
73+
min-width: 12em;
74+
}
75+
76+
.public-access-columns-hint {
77+
font-size: 12px;
78+
opacity: 0.6;
79+
}
80+
81+
.public-access-actions {
82+
display: flex;
83+
align-items: center;
84+
margin-top: 16px;
85+
}
86+
87+
.public-access-actions-spacer {
88+
flex: 1;
89+
}
90+
91+
@media (width <= 600px) {
92+
.public-access-table {
93+
align-items: flex-start;
94+
flex-direction: column;
95+
gap: 4px;
96+
}
97+
98+
.public-access-columns {
99+
width: 100%;
100+
}
101+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<mat-expansion-panel class="public-access-panel">
2+
<mat-expansion-panel-header>
3+
<mat-panel-title>
4+
<mat-icon fontSet="material-symbols-outlined" class="public-access-icon">public</mat-icon>
5+
<span>Public access</span>
6+
<span class="public-access-badge"
7+
[class.public-access-badge_on]="selectedCount() > 0">
8+
{{ statusLabel() }}
9+
</span>
10+
</mat-panel-title>
11+
</mat-expansion-panel-header>
12+
13+
<ng-template matExpansionPanelContent>
14+
<div class="public-access-warning">
15+
<mat-icon>warning</mat-icon>
16+
<span>
17+
Anyone on the internet can read the tables listed below without signing in.
18+
Public access is read-only — rows can never be added, edited or deleted.
19+
</span>
20+
</div>
21+
22+
@if (tablesLoading() || loadingPermissions()) {
23+
<app-content-loader></app-content-loader>
24+
} @else if (tables().length === 0) {
25+
<p class="body-2">No tables in this connection.</p>
26+
} @else {
27+
<ul class="public-access-tables">
28+
@for (table of tables(); track table.tableName) {
29+
@let selected = isSelected(table.tableName);
30+
<li class="public-access-table" [class.public-access-table_selected]="selected">
31+
<mat-checkbox
32+
[checked]="selected"
33+
[disabled]="submitting()"
34+
(change)="toggleTable(table.tableName, $event.checked)">
35+
{{ table.displayName }}
36+
</mat-checkbox>
37+
38+
@if (selected) {
39+
@if (isLoadingColumns(table.tableName)) {
40+
<span class="public-access-columns-hint">Loading columns…</span>
41+
} @else {
42+
<mat-form-field class="public-access-columns" subscriptSizing="dynamic">
43+
<mat-label>Readable columns</mat-label>
44+
<mat-select multiple
45+
[value]="selectedColumns(table.tableName)"
46+
[disabled]="submitting()"
47+
(valueChange)="setColumns(table.tableName, $event)"
48+
placeholder="All columns">
49+
@for (column of availableColumns(table.tableName); track column) {
50+
<mat-option [value]="column">{{ column }}</mat-option>
51+
}
52+
</mat-select>
53+
<mat-hint>Leave empty to expose all columns</mat-hint>
54+
</mat-form-field>
55+
}
56+
}
57+
</li>
58+
}
59+
</ul>
60+
61+
<div class="public-access-actions">
62+
@if (canDisable()) {
63+
<button type="button" mat-button
64+
matTooltip="Remove public access to every table"
65+
[disabled]="submitting()"
66+
(click)="disablePublicAccess(); posthog.capture('Users access: public access disabled')">
67+
Disable public access
68+
</button>
69+
}
70+
<span class="public-access-actions-spacer"></span>
71+
<button type="button" mat-flat-button color="primary"
72+
[disabled]="submitting()"
73+
(click)="save(); posthog.capture('Users access: public access saved')">
74+
Save
75+
</button>
76+
</div>
77+
}
78+
</ng-template>
79+
</mat-expansion-panel>

0 commit comments

Comments
 (0)