Skip to content

Commit 01e4929

Browse files
committed
Add initial permissions system
1 parent 7813b76 commit 01e4929

23 files changed

Lines changed: 737 additions & 81 deletions

File tree

components/ProjectGroupPicker.vue

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,50 @@
44
class="project-group-picker form-select"
55
aria-label="Project Group Selection"
66
>
7-
<option v-for="pg in projectGroups" :key="pg.id" :value="pg.id">
7+
<option
8+
v-for="pg in projectGroups"
9+
:key="pg.tdei_project_group_id"
10+
:value="pg.tdei_project_group_id"
11+
>
812
{{ pg.name }}
913
</option>
1014
</select>
1115
</template>
1216

1317
<script setup lang="ts">
14-
import { tdeiUserClient } from '~/services/index'
18+
import { tdeiUserClient } from '~/services/index';
19+
import type { TdeiProjectGroupItem } from '~/types/tdei';
20+
import { compareStringAsc } from '~/util/compare';
1521
16-
const model = defineModel({ required: true });
17-
const projectGroups = (await tdeiUserClient.getMyProjectGroups())
18-
.sort((a, b) => a.name.localeCompare(b.name));
22+
const model = defineModel<string | undefined>({ required: true });
23+
const props = defineProps<{ options?: TdeiProjectGroupItem[] }>();
1924
20-
if (projectGroups.length > 0) {
21-
if (!model.value || !projectGroups.some(pg => pg.id === model.value)) {
22-
model.value = projectGroups[0].id;
25+
const fetchedGroups = ref<TdeiProjectGroupItem[]>([]);
26+
27+
if (!props.options) {
28+
try {
29+
fetchedGroups.value = (await tdeiUserClient.getMyProjectGroups()).sort(
30+
compareStringAsc(g => g.name),
31+
);
32+
}
33+
catch {
34+
fetchedGroups.value = [];
2335
}
2436
}
37+
38+
const projectGroups = computed(() => props.options ?? fetchedGroups.value);
39+
40+
watch(
41+
projectGroups,
42+
(groups) => {
43+
if (groups.length > 0) {
44+
const pgId = model.value;
45+
46+
if (!pgId || !groups.some(pg => pg.tdei_project_group_id === pgId)) {
47+
model.value = groups[0]?.tdei_project_group_id;
48+
}
49+
}
50+
},
51+
{ immediate: true },
52+
);
2553
</script>

components/dashboard/DetailsTable.vue

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
2-
<div class="table-responsive border-top">
3-
<table class="table table-striped mb-0">
2+
<div class="table-responsive border-top mb-0">
3+
<table class="table table-striped">
44
<tbody>
55
<tr>
66
<th><app-icon variant="schedule" />Created At</th>
@@ -10,6 +10,41 @@
1010
<th><app-icon variant="person_outline" />Created By</th>
1111
<td>{{ workspace.createdByName }}</td>
1212
</tr>
13+
<tr>
14+
<th><app-icon variant="badge" />My Role</th>
15+
<td>
16+
<span
17+
v-if="workspace.role === 'lead'"
18+
class="badge bg-dark text-uppercase"
19+
>
20+
<app-icon variant="star" /> Owner
21+
</span>
22+
<span
23+
v-else-if="workspace.role === 'validator'"
24+
class="badge bg-dark text-uppercase"
25+
>
26+
<app-icon variant="task_alt" /> Validator
27+
</span>
28+
<span
29+
v-else
30+
class="badge bg-secondary text-uppercase"
31+
>
32+
<app-icon variant="person" /> Member
33+
</span>
34+
<span
35+
v-if="isPoc"
36+
class="badge bg-warning text-dark text-uppercase ms-1"
37+
>
38+
<app-icon variant="local_police" /> POC
39+
</span>
40+
<span
41+
v-else-if="isDataGenerator"
42+
class="badge bg-warning text-dark text-uppercase ms-1"
43+
>
44+
<app-icon variant="offline_bolt" /> Data Generator
45+
</span>
46+
</td>
47+
</tr>
1348
<tr>
1449
<th><app-icon variant="phonelink_setup" />App Access</th>
1550
<td>
@@ -42,12 +77,19 @@
4277
</template>
4378

4479
<script setup lang="ts">
45-
const props = defineProps({
46-
workspace: {
47-
type: Object,
48-
required: true
49-
}
50-
});
80+
import type { Workspace } from '~/types/workspaces';
81+
82+
interface Props {
83+
workspace: Workspace;
84+
myTdeiRoles: string[];
85+
}
86+
87+
const props = defineProps<Props>();
88+
89+
const isPoc = computed(() => props.myTdeiRoles.includes('poc'));
90+
const isDataGenerator = computed(() =>
91+
props.myTdeiRoles.includes(`${props.workspace.type}_data_generator`),
92+
);
5193
</script>
5294

5395
<style lang="scss">

components/dashboard/Toolbar.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
<app-icon variant="settings" size="24" no-margin />
3333
<span class="d-none d-sm-inline ms-2">Settings</span>
3434
</nuxt-link>
35-
</div>
36-
</div>
35+
</div><!-- .btn-group -->
36+
</div><!-- .btn-toolbar -->
3737
</template>
3838

3939
<script setup lang="ts">

components/dashboard/WorkspaceItem.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,25 @@
99
<app-icon v-else variant="lock" />
1010
App
1111
</span>
12+
13+
<span
14+
v-if="workspace.role === 'lead'"
15+
class="badge bg-dark ms-2"
16+
>
17+
<app-icon variant="star" /> {{ ROLE_LABELS.lead }}
18+
</span>
19+
<span
20+
v-else-if="workspace.role === 'validator'"
21+
class="badge bg-dark ms-2"
22+
>
23+
<app-icon variant="task_alt" /> {{ ROLE_LABELS.validator }}
24+
</span>
1225
</button>
1326
</template>
1427

1528
<script setup lang="ts">
29+
import { ROLE_LABELS } from '~/util/roles';
30+
1631
const props = defineProps({
1732
workspace: {
1833
type: Object,

components/review/Toolbar.vue

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,27 @@
4545
{{ props.item.commentCount }}
4646
</span>
4747
</button>
48-
<button
49-
v-show="props.item.isFeedback"
50-
class="btn btn-sm btn-success ms-2"
48+
<BPopover
49+
content="Only validators and owners can resolve feedback"
50+
placement="bottom"
51+
:manual="isValidator"
5152
>
52-
<app-icon
53-
variant="check"
54-
no-margin
55-
/>
56-
<span class="d-none d-sm-inline ms-2">Mark as Resolved</span>
57-
</button>
53+
<template #target>
54+
<div class="d-inline-block ms-2">
55+
<button
56+
v-show="props.item.isFeedback && !props.item.isResolved"
57+
class="btn btn-sm btn-success"
58+
:disabled="!isValidator"
59+
>
60+
<app-icon
61+
variant="check"
62+
no-margin
63+
/>
64+
<span class="d-none d-sm-inline ms-2">Mark as Resolved</span>
65+
</button>
66+
</div>
67+
</template>
68+
</BPopover>
5869
</nav>
5970
</template>
6071

@@ -66,6 +77,7 @@ interface Props {
6677
}
6778
6879
const props = defineProps<Props>();
80+
const { isValidator } = useWorkspaceRole();
6981
7082
const emit = defineEmits(['edit']);
7183
const showDetails = defineModel<boolean>('showDetails');

components/settings/Nav.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@
66
>
77
General
88
</settings-nav-link>
9+
<settings-nav-link
10+
to="/members"
11+
icon="admin_panel_settings"
12+
>
13+
Members
14+
</settings-nav-link>
915
<settings-nav-link
1016
to="/teams"
11-
icon="group"
17+
icon="diversity_3"
1218
>
1319
Teams
1420
</settings-nav-link>

components/settings/panel/Apps.vue

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
External Apps
99
</h3>
1010

11+
<b-alert
12+
v-if="!isLead"
13+
variant="info"
14+
show
15+
class="mb-3"
16+
>
17+
<app-icon variant="info" />
18+
Only workspace owners can change external app settings.
19+
</b-alert>
20+
1121
<div class="form-check form-switch">
1222
<label class="form-check-label">
1323
<input
@@ -16,6 +26,7 @@
1626
class="form-check-input"
1727
:true-value="1"
1828
:false-value="0"
29+
:disabled="!isLead"
1930
>
2031
Publish this workspace for external apps
2132
</label>
@@ -36,6 +47,7 @@
3647
type="radio"
3748
name="longFormQuestType"
3849
value="JSON"
50+
:disabled="!isLead"
3951
>
4052
</label>
4153
</div>
@@ -48,6 +60,7 @@
4860
type="radio"
4961
name="longFormQuestType"
5062
value="URL"
63+
:disabled="!isLead"
5164
>
5265
</label>
5366
</div>
@@ -61,6 +74,7 @@
6174
:class="{ 'drag-over': isDraggingQuest }"
6275
rows="5"
6376
placeholder="Optional"
77+
:disabled="!isLead"
6478
@dragover.prevent="isDraggingQuest = true"
6579
@dragleave.prevent="isDraggingQuest = false"
6680
@drop.prevent="onQuestFileDrop"
@@ -96,6 +110,7 @@
96110
type="text"
97111
class="form-control"
98112
placeholder="https://..."
113+
:disabled="!isLead"
99114
>
100115
</label>
101116
<div
@@ -131,6 +146,7 @@
131146
<button
132147
type="submit"
133148
class="btn btn-primary"
149+
:disabled="!isLead"
134150
>
135151
Save
136152
</button>
@@ -157,6 +173,7 @@ const longFormQuestSchemaUrl = import.meta.env.VITE_LONG_FORM_QUEST_SCHEMA;
157173
const longFormQuestExampleUrl = import.meta.env.VITE_LONG_FORM_QUEST_EXAMPLE_URL;
158174
159175
const workspace = inject<Workspace>('workspace')!;
176+
const { isLead } = useWorkspaceRole();
160177
161178
const [longFormQuestSettings] = await Promise.all([
162179
workspacesClient.getLongFormQuestSettings(workspace.id),

components/settings/panel/Delete.vue

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,24 @@
55
Delete Workspace
66
</h3>
77

8+
<b-alert
9+
v-if="!isLead"
10+
variant="info"
11+
show
12+
class="mb-3"
13+
>
14+
<app-icon variant="info" />
15+
Only workspace owners can delete the workspace.
16+
</b-alert>
17+
818
<p>
919
Deleting a workspace is permanent. This action will not remove any
1020
TDEI datasets outside of Workspaces.
1121
</p>
1222

1323
<button
1424
class="btn btn-outline-danger mb-3"
15-
:disabled="accepted"
25+
:disabled="!isLead || accepted"
1626
@click="acceptDelete"
1727
>
1828
I understand, and I want to delete this workspace
@@ -30,7 +40,7 @@
3040

3141
<button
3242
class="btn btn-danger"
33-
:disabled="attestation !== 'delete'"
43+
:disabled="!isLead || attestation !== 'delete'"
3444
@click="submitDelete"
3545
>
3646
Delete this workspace
@@ -48,18 +58,27 @@ import { workspacesClient } from '~/services/index';
4858
import type { Workspace } from '~/types/workspaces';
4959
5060
const workspace = inject<Workspace>('workspace')!;
61+
const { isLead } = useWorkspaceRole();
5162
5263
const accepted = ref(false);
5364
const attestation = ref('');
5465
const input = useTemplateRef<HTMLInputElement>('input');
5566
5667
async function acceptDelete() {
68+
if (!isLead.value) {
69+
return;
70+
}
71+
5772
accepted.value = true;
5873
await nextTick();
5974
input.value!.focus();
6075
}
6176
6277
async function submitDelete() {
78+
if (!isLead.value) {
79+
return;
80+
}
81+
6382
await workspacesClient.deleteWorkspace(workspace.id);
6483
navigateTo('/dashboard');
6584
}

0 commit comments

Comments
 (0)