tokens.vue 29.2 KB
Newer Older
janis.streib's avatar
janis.streib committed
1
2
<template>
    <div class="main">
3
        <h1>Accounts & Tokens</h1>
4
5
        <b-modal id="modal-create-account" size="lg" title="Subaccount erstellen"
                 @hidden="resetAccountData">
6
7
8
9
10
            <b-form @submit="createAccount">
                <b-form-group label="Beschreibung:" label-for="input-account-create-description">
                    <b-form-textarea
                            id="input-account-create-description"
                            v-model.trim="new_account.description"
11
                            placeholder="Beschreibung eingeben"
12
                    />
13
                </b-form-group>
14
15
16
17
18
19
20
                <b-form-group label="Rolle:" label-for="input-account-create-role">
                    <b-form-select
                            id="input-account-create-role"
                            v-model="test_role"
                            :options="test_roles"
                    />
                </b-form-group>
21
            </b-form>
22
23
            <template v-slot:modal-footer="{cancel}">
                <b-button variant="outline-secondary" @click="cancel">
24
25
                    Abbrechen
                </b-button>
26
                <b-button type="submit" variant="success" @click="createAccount">
27
28
29
30
31
32
33
34
35
36
37
38
39
                    Erstellen
                </b-button>
            </template>
        </b-modal>
        <b-row>
            <b-col lg="9">
                <b-input-group>
                    <b-input-group-prepend>
                        <b-input-group-text>
                            <font-awesome-icon :icon="['fas', 'filter']"/>
                        </b-input-group-text>
                    </b-input-group-prepend>
                    <b-form-input id="filter-input" v-model.trim="filter_text" placeholder="Filter" autofocus
40
                                  debounce="300"/>
41
42
43
44
45
46
47
48
                </b-input-group>
            </b-col>
            <b-col lg="3" sm="*">
                <b-button block variant="outline-success" v-b-modal.modal-create-account>
                    Subaccount erstellen
                </b-button>
            </b-col>
        </b-row>
49
50
        <b-modal id="modal-edit-account" size="lg" title="Account bearbeiten"
                 @hidden="resetAccountData">
51
52
53
54
55
56
57
58
            <b-form>
                <b-form-group label="Beschreibung:" label-for="input-account-edit-description">
                    <b-form-textarea
                            id="input-account-edit-description"
                            v-model.trim="new_account.description"
                            placeholder="Beschreibung eingeben"
                    />
                </b-form-group>
59
60
61
62
63
64
65
                <b-form-group label="Rolle:" label-for="input-account-edit-role">
                    <b-form-select
                            id="input-account-edit-role"
                            v-model="test_role"
                            :options="test_roles"
                    />
                </b-form-group>
66
            </b-form>
67
68
69
70
71
72
            <template v-slot:modal-footer="{cancel}">
                <b-alert id="alert-edit-account" v-model="show_modal_alert"
                         variant="danger" dismissible fade class="mb-0 flex-grow">
                    {{modal_alert_content}}
                </b-alert>
                <b-button variant="outline-secondary" @click="cancel">
73
74
                    Abbrechen
                </b-button>
75
                <b-button id="button-delete-account" variant="danger">
76
77
                    Löschen
                </b-button>
78
79
                <b-popover ref="popoverAccountDelete" target="button-delete-account" triggers="click"
                           placement="bottom">
80
81
82
83
84
85
86
87
                    <template v-slot:title>Account wirklich löschen?</template>
                    <b-button variant="danger" @click="deleteAccount">
                        Löschen
                    </b-button>
                    <b-button variant="outline-secondary" @click="$refs.popoverAccountDelete.$emit('close')">
                        Abbrechen
                    </b-button>
                </b-popover>
88
                <b-button variant="primary" @click="editAccount">
89
90
91
92
                    Änderungen übernehmen
                </b-button>
            </template>
        </b-modal>
93
94
        <b-modal id="modal-create-token" size=lg title="Token erstellen"
                 @hidden="resetTokenData">
95
96
97
98
            <b-form>
                <b-form-group label="Beschreibung:" label-for="input-token-create-description">
                    <b-form-textarea
                            id="input-token-create-description"
99
                            v-model.trim="new_token.description"
100
                            required
101
102
                            placeholder="Beschreibung"
                    />
103
                </b-form-group>
Robert-K's avatar
Robert-K committed
104
105
                <div v-if="new_token.expiration_date != null">
                    <b-form-group label="Ablaufdatum:" label-for="input-token-create-expiration_date" class="mb-0">
106
107
108
109
110
111
112
113
114
                        <VueCtkDateTimePicker
                                :inline="!isMobile()"
                                format="DD-MM-YYYY HH:mm"
                                locale="de"
                                noButtonNow
                                minute-interval="5"
                                noLabel
                                no-keyboard
                                noClearButton
Robert-K's avatar
Robert-K committed
115
                                id="input-token-create-expiration_date"
116
117
                                color="#007BFF"
                                button-color="#007BFF"
Robert-K's avatar
Robert-K committed
118
                                v-model="new_token.expiration_date"/>
119
                    </b-form-group>
Robert-K's avatar
Robert-K committed
120
                    <b-button block variant="outline-secondary" @click="new_token.expiration_date = null">
121
122
123
124
                        Ablaufdatum entfernen
                    </b-button>
                </div>
                <b-button block variant="outline-secondary" v-else
Robert-K's avatar
Robert-K committed
125
                          @click="new_token.expiration_date = formatDate(getDate30DaysAhead())">
126
127
                    Ablaufdatum hinzufügen
                </b-button>
128
            </b-form>
129
130
131
132
133
134
            <template v-slot:modal-footer="{cancel}">
                <b-alert id="alert-create-token" v-model="show_modal_alert"
                         variant="danger" dismissible fade class="mb-0 flex-grow">
                    {{modal_alert_content}}
                </b-alert>
                <b-button variant="outline-secondary" @click="cancel">
135
136
                    Abbrechen
                </b-button>
137
                <b-button type="submit" variant="success" @click="createToken">
138
139
140
                    Erstellen
                </b-button>
            </template>
141
        </b-modal>
142
143
144
        <b-modal id="modal-token" size=lg title="Token erstellt">
            <b-input-group>
                <b-form-input id="input-token" v-model="token" readonly/>
145
146
                <b-tooltip target="input-token" :show.sync="token_copied" :disabled="!token_copied" placement="bottom"
                           variant="primary">
147
148
                    Kopiert.
                </b-tooltip>
149
                <b-input-group-append>
150
                    <b-button variant="primary" @click="copyToken">
151
152
153
154
                        <font-awesome-icon :icon="['fas', 'copy']"/>
                    </b-button>
                </b-input-group-append>
            </b-input-group>
155
156
157
            <b-alert show variant="warning" class="mb-0 mt-3">Bewahren Sie das Token gut auf. Hier werden Sie es nicht
                mehr einsehen können!
            </b-alert>
158
159
160
161
162
163
            <template v-slot:modal-footer="{ok}">
                <b-button variant="success" @click="ok">
                    Ok
                </b-button>
            </template>
        </b-modal>
164
        <b-modal id="modal-edit-token" size=lg title="Token bearbeiten">
165
166
167
168
            <b-form>
                <b-form-group label="Beschreibung:" label-for="input-token-edit-description">
                    <b-form-textarea
                            id="input-token-edit-description"
169
                            v-model.trim="new_token.description"
170
171
172
173
174
                            required
                            placeholder="Beschreibung"
                    />
                </b-form-group>
            </b-form>
Robert-K's avatar
Robert-K committed
175
176
            <div v-if="new_token.expiration_date != null">
                <b-form-group label="Ablaufdatum:" label-for="input-token-edit-expiration_date" class="mb-0">
177
178
179
180
181
182
183
184
185
                    <VueCtkDateTimePicker
                            :inline="!isMobile()"
                            format="DD-MM-YYYY HH:mm"
                            locale="de"
                            noButtonNow
                            minute-interval="5"
                            noLabel
                            no-keyboard
                            noClearButton
Robert-K's avatar
Robert-K committed
186
                            id="input-token-edit-expiration_date"
187
188
                            color="#007BFF"
                            button-color="#007BFF"
Robert-K's avatar
Robert-K committed
189
                            v-model="new_token.expiration_date"/>
190
                </b-form-group>
Robert-K's avatar
Robert-K committed
191
                <b-button block variant="outline-secondary" @click="new_token.expiration_date = null">
192
193
194
195
                    Ablaufdatum entfernen
                </b-button>
            </div>
            <b-button block variant="outline-secondary" v-else
Robert-K's avatar
Robert-K committed
196
                      @click="new_token.expiration_date = formatDate(getDate30DaysAhead())">
197
198
                Ablaufdatum hinzufügen
            </b-button>
199
            <template v-slot:modal-footer="{cancel, ok}">
200
201
202
203
204
                <b-alert id="alert-edit-token" v-model="show_modal_alert"
                         variant="danger" dismissible fade class="mb-0 flex-grow">
                    {{modal_alert_content}}
                </b-alert>
                <b-button variant="outline-secondary" @click="cancel">
205
206
                    Abbrechen
                </b-button>
207
                <b-button id="button-delete-token" variant="danger">
208
209
                    Löschen
                </b-button>
210
                <b-popover ref="popoverTokenDelete" target="button-delete-token" triggers="click" placement="bottom">
211
212
213
214
215
216
217
218
                    <template v-slot:title>Token wirklich löschen?</template>
                    <b-button variant="danger" @click="deleteToken">
                        Löschen
                    </b-button>
                    <b-button variant="outline-secondary" @click="$refs.popoverTokenDelete.$emit('close')">
                        Abbrechen
                    </b-button>
                </b-popover>
219
                <b-button type="submit" variant="primary" @click="editToken">
220
221
222
223
                    Änderungen übernehmen
                </b-button>
            </template>
        </b-modal>
224
225
        <template v-for="account in filtered_accounts">
            <b-card no-body :key="'card-account-' + account.login_name" class="mb-4">
226
227
228
229
230
231
                <template v-slot:header>
                    <b-row>
                        <b-col lg="3">
                            <h4>
                                {{account.login_name}}
                                <b-badge variant="success">
232
233
234
235
                                    <template v-if="account.login_name in tokens_by_account">
                                        <template v-if="tokens_by_account[account.login_name].length === 1">
                                            {{tokens_by_account[account.login_name].length}} Token
                                        </template>
236
237
                                        <template v-else>{{tokens_by_account[account.login_name].length}} Tokens
                                        </template>
238
239
                                    </template>
                                    <template v-else>0 Tokens</template>
240
241
242
243
244
                                </b-badge>
                            </h4>
                            <p class="text-muted">Login Name</p>
                        </b-col>
                        <b-col>
245
246
247
                            <div v-if="account.parent_login_name == null">Hauptaccount; enthält nur Session-Tokens; kann
                                nicht gelöscht werden.
                            </div>
248
249
                            <div v-else-if="account.description == null">Keine Beschreibung vorhanden.</div>
                            <div v-else>{{account.description}}</div>
250
251
252
                            <p class="text-muted">Beschreibung</p>
                        </b-col>
                        <b-col>
253
                            <h5 class="mb-0">
254
255
256
257
258
259
260
261
262
263
264
265
                                <template
                                        v-for="role in roles_by_account[account.login_name].slice(0, max_role_badge_count)">
                                    <b-badge
                                            :key="'role-badge-' + role.mgr_login_name + '-' + role.role_fq_name"
                                            :id="account.login_name + '-role-badge-' + role.mgr_login_name + '-' + role.role_fq_name"
                                            :style="{background: role.role_fq_name.toHSL({lit: [30, 40]})}"
                                            class="mr-1 mb-1">{{role.system.toUpperCase()}}<br>{{role.role}}
                                    </b-badge>
                                    <b-popover
                                            :key="'role-badge-tooltip' + role.mgr_login_name + '-' + role.role_fq_name"
                                            :target="account.login_name + '-role-badge-' + role.mgr_login_name + '-' + role.role_fq_name"
                                            triggers="hover"
266
267
268
269
                                            placement="bottom">
                                        <b-table :fields="permission_list_fields"
                                                 :items="role.contained_permissions"
                                                 sticky-header/>
270
271
                                    </b-popover>
                                </template>
272
273
274
                            </h5>
                            <h6 v-if="roles_by_account[account.login_name].length > max_role_badge_count">
                                + {{roles_by_account[account.login_name].length - max_role_badge_count}} Weitere
275
                            </h6>
276
                            <p class="text-muted">Rollen</p>
277
278
                        </b-col>
                        <b-col lg="1">
279
280
281
282
283
284
285
286
287
288
289
                            <template v-if="account.parent_login_name !== null">
                                <b-button block variant="outline-primary"
                                          :id="'button-edit-account-' + account.login_name"
                                          @click="showModalEditAccount(account)">
                                    <font-awesome-icon :icon="['far', 'edit']"/>
                                </b-button>
                                <b-tooltip placement="bottom" :target="'button-edit-account-' + account.login_name"
                                           triggers="hover" variant="primary">
                                    Account bearbeiten
                                </b-tooltip>
                            </template>
290
291
292
293
                        </b-col>
                    </b-row>
                </template>
                <b-button block squared variant="info" v-b-toggle="account.login_name + '-collapse'">
294
                    <font-awesome-icon class="collapse-icon" :icon="['fas','chevron-up']"/>
295
296
                </b-button>
                <b-collapse :id="account.login_name + '-collapse'">
297
298
299
300
301
302
303
                    <b-table :items="tokens_by_account[account.login_name]"
                             :fields="token_list_fields"
                             class="m-0"
                             striped
                             responsive
                             :tbody-transition-props="leaving_transition_properties"
                             primary-key="description"> <!-- TODO: Get table animations to work -->
304
                        <template v-slot:head(buttons)>
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
                            <template v-if="account.parent_login_name !== null">
                                <b-button block variant="outline-success"
                                          :id="'button-create-token-' +  account.login_name"
                                          @click="showModalCreateToken(account.login_name)">
                                    <font-awesome-icon :icon="['fas', 'plus']"/>
                                </b-button>
                                <b-tooltip :target="'button-create-token-' +  account.login_name" triggers="hover"
                                           variant="success" placement="left">
                                    Token erstellen
                                </b-tooltip>
                            </template>
                        </template>
                        <template v-slot:cell(expiration_date)="data">
                            <!-- TODO: Expired tokens should be red; might be ugly / difficult -->
                            {{formatDate(data.item.expiration_date)}}
                            <template v-if="data.item.is_expired">
                                (Expired)
                            </template>
323
324
325
326
                        </template>
                        <template v-slot:cell(buttons)="data">
                            <b-button block variant="outline-primary"
                                      :id="'button-edit-token-' + account.login_name+ '-' + data.index"
327
                                      @click="showModalEditToken(data.item)">
328
329
330
                                <font-awesome-icon :icon="['far', 'edit']"/>
                            </b-button>
                            <b-tooltip :target="'button-edit-token-' + account.login_name+ '-' + data.index"
331
                                       triggers="hover" variant="primary" placement="left">
332
333
334
335
336
337
338
                                Token bearbeiten
                            </b-tooltip>
                        </template>
                    </b-table>
                </b-collapse>
            </b-card>
        </template>
janis.streib's avatar
janis.streib committed
339
340
341
342
    </div>
</template>

<script>
343
344
    import AccountTokenService from '@/api-services/account_token.service';
    import AccountService from '@/api-services.gen/cntl.mgr'
345
    import TokenService from '@/api-services.gen/cntl.wapi_auth'
346
    import ApiUtil from '@/util/apiutil'
347
    import '@/util/colorutil'
janis.streib's avatar
janis.streib committed
348
349

    export default {
350
        name: 'tokens',
janis.streib's avatar
janis.streib committed
351
352
        data() {
            return {
353
354
355
356
357
358
359
360
361
                // TODO: remove test/wip data aka implement role management
                test_role: 'NetVS Overlord',
                test_roles: [
                    'NetVS Overlord',
                    'Dungeon Master',
                    'HiWi-Goblin',
                    'Guest',
                    'Individuell'
                ],
362
                tokens_by_account: null,
363
                roles_by_account: null,
364
365
                accounts: null,
                new_account: {
366
                    parent_login_name: null,
367
368
                    description: '',
                    login_name: '',
Robert-K's avatar
Robert-K committed
369
                    expiration_date: null
370
371
                },
                new_token: {
372
373
                    description: '',
                    login_name: '',
Robert-K's avatar
Robert-K committed
374
                    expiration_date: null
375
                },
376
377
378
379
380
381
382
383
                filter_text: "",
                token_list_fields: [
                    {
                        key: 'description',
                        label: "Beschreibung",
                        sortable: false
                    },
                    {
Robert-K's avatar
Robert-K committed
384
                        key: 'expiration_date',
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
                        label: "Ablaufdatum",
                        sortable: true
                    },
                    {
                        key: 'last_login_date',
                        label: "Zuletzt verwendet",
                        formatter: this.formatDate,
                        sortable: true
                    },
                    {
                        key: 'last_generate_date',
                        label: "Zuletzt generiert",
                        formatter: this.formatDate,
                        sortable: true
                    },
                    {
                        key: 'buttons',
                        label: '',
                        sortable: false
                    },
405
                ],
406
407
408
409
410
411
412
413
414
415
                permission_list_fields: [
                    {
                        key: 'system',
                        label: "System"
                    },
                    {
                        key: 'permission',
                        label: 'Berechtigung'
                    },
                ],
416
417
                show_modal_alert: false,
                modal_alert_content: "",
418
                token: "",
419
420
421
                token_copied: false,
                leaving_transition_properties: { // TODO: Get table animations to work
                    name: 'flip-list'
422
                },
423
424
                max_role_badge_count: 10,
                max_permission_count: 10
janis.streib's avatar
janis.streib committed
425
426
            }
        },
427
        computed: {
428
429
430
            filtered_accounts() {
                if (this.accounts == null) {
                    return null
431
                }
432
433
434
435
436
                if (this.filter_text === '') {
                    return this.accounts
                }
                return this.accounts.filter(account => {
                    return account.login_name.toLowerCase().includes(this.filter_text.toLowerCase())
437
                        || (account.description != null && account.description.toLowerCase().includes(this.filter_text.toLowerCase()))
438
439
                })
            }
440
        },
janis.streib's avatar
janis.streib committed
441
        created() {
442
            this.fetchData()
443
444
        },
        methods: {
445
            fetchData() {
446
                AccountTokenService.list(this.$store.state.netdb_axios_config, this.$store.state.user.login_name).then((response) => {
447
                    this.tokens_by_account = ApiUtil.dict_of_lists_by_value_of_array(this.formatExpiredTokens(response.data[2].concat(response.data[3])), 'login_name')
448
                    this.roles_by_account = ApiUtil.dict_of_lists_by_value_of_array(this.formatContainedPermissions(response.data[4].concat(response.data[5])), 'mgr_login_name')
449
                    this.accounts = response.data[0].concat(response.data[1])
450
451
452
                })
            },
            showModalEditAccount(account) {
453
454
                this.new_account.description = account.description
                this.new_account.login_name = account.login_name
455
456
457
                this.new_account.parent_login_name = account.parent_login_name
                this.show_modal_alert = false
                this.$bvModal.show('modal-edit-account')
458
            },
459
            showModalEditToken(token) {
460
461
                this.new_token.description = token.description
                this.new_token.login_name = token.login_name
Robert-K's avatar
Robert-K committed
462
                this.new_token.expiration_date = token.expiration_date != null ? this.formatDate(token.expiration_date) : null
463
464
465
466
467
468
                this.new_token.pk = token.pk
                this.show_modal_alert = false
                this.$bvModal.show('modal-edit-token')
            },
            showModalCreateToken(login_name) {
                this.new_token.description = ''
Robert-K's avatar
Robert-K committed
469
                this.new_token.expiration_date = null
470
471
472
                this.new_token.login_name = login_name
                this.show_modal_alert = false
                this.$bvModal.show('modal-create-token')
473
            },
474
            resetAccountData() {
475
476
                this.new_account.description = ''
                this.new_account.login_name = ''
Robert-K's avatar
Robert-K committed
477
                this.new_account.expiration_date = null
478
            },
479
            resetTokenData() {
480
481
                this.new_token.description = ''
                this.new_token.login_name = ''
Robert-K's avatar
Robert-K committed
482
                this.new_token.expiration_date = null
483
            },
484
485
            createAccount() {
                AccountService.create(this.$store.state.netdb_axios_config, {
486
487
488
489
490
491
492
493
                    do_copy_assignments_new: true,
                    description_new: this.new_account.description,
                    allow_data_manipulation_new: true,
                    login_name_new: null,
                    do_copy_roles_new: true
                }).then(() => {
                    this.$bvModal.hide('modal-create-account')
                    this.fetchData()
gj4210's avatar
gj4210 committed
494
495
                }).catch(error => {
                    this.modal_alert_content = error.response.data.error.type.text_descr
496
497
498
499
500
501
502
503
504
505
506
507
508
                    this.show_modal_alert = true
                })
            },
            editAccount() {
                AccountService.update(this.$store.state.netdb_axios_config, {
                    description_new: this.new_account.description,
                    login_name_old: this.new_account.login_name,
                    login_name_new: this.new_account.login_name,
                    allow_data_manipulation_new: true
                }).then(() => {
                    this.$bvModal.hide('modal-edit-account')
                    this.fetchData()
                }).catch(error => {
gj4210's avatar
gj4210 committed
509
                    this.modal_alert_content = error.response.data.error.type.text_descr
510
511
512
513
514
515
                    this.show_modal_alert = true
                })
            },
            deleteAccount() {
                AccountService.delete(this.$store.state.netdb_axios_config, {
                    do_delete_references: true,
516
                    login_name_old: this.new_account.login_name
517
518
519
                }).then(() => {
                    this.$bvModal.hide('modal-edit-account')
                }).catch(error => {
gj4210's avatar
gj4210 committed
520
                    this.modal_alert_content = error.response.data.error.type.text_descr
521
522
523
524
525
526
527
                    this.show_modal_alert = true
                })
            },
            createToken() {
                TokenService.create(this.$store.state.netdb_axios_config, {
                    description_new: this.new_token.description,
                    login_name_new: this.new_token.login_name,
Robert-K's avatar
Robert-K committed
528
                    expiration_date_new: this.new_token.expiration_date
529
530
531
532
533
534
                }).then(response => {
                    this.$bvModal.hide('modal-create-token')
                    this.token = response.data[0][0].token
                    this.$bvModal.show('modal-token')
                    this.fetchData()
                }).catch(error => {
gj4210's avatar
gj4210 committed
535
                    this.modal_alert_content = error.response.data.error.type.text_descr
536
537
538
539
540
541
542
543
                    this.show_modal_alert = true
                })
            },
            editToken() {
                TokenService.update(this.$store.state.netdb_axios_config, {
                    description_new: this.new_token.description,
                    pk_old: this.new_token.pk,
                    do_refresh_token_new: true,
Robert-K's avatar
Robert-K committed
544
                    expiration_date_new: this.new_token.expiration_date
545
546
547
548
                }).then(() => {
                    this.$bvModal.hide('modal-edit-token')
                    this.fetchData()
                }).catch(error => {
gj4210's avatar
gj4210 committed
549
                    this.modal_alert_content = error.response.data.error.type.text_descr
550
551
552
553
554
555
556
557
558
559
                    this.show_modal_alert = true
                })
            },
            deleteToken() {
                TokenService.delete(this.$store.state.netdb_axios_config, {
                    pk_old: this.new_token.pk
                }).then(() => {
                    this.$bvModal.hide('modal-edit-token')
                    this.fetchData()
                }).catch(error => {
gj4210's avatar
gj4210 committed
560
                    this.modal_alert_content = error.response.data.error.type.text_descr
561
                    this.show_modal_alert = true
562
563
564
                })
            },
            formatDate(value) {
565
566
567
568
                if (value == null) {
                    return 'N/A'
                }
                return new Date(Date.parse(value)).toLocaleString('de-DE')
569
            },
570
            getDate30DaysAhead() {
571
                let d = new Date()
572
                d.setDate(d.getDate() + 29)
573
                return d
574
575
576
            },
            copyToken() {
                // https://www.w3schools.com/howto/howto_js_copy_clipboard.asp
577
578
579
580
581
                let text = document.getElementById("input-token")
                text.select()
                text.setSelectionRange(0, 99999) /*For mobile devices*/
                document.execCommand("copy")
                this.token_copied = true
582
583
584
585
586
587
588
589
590
            },
            formatExpiredTokens(tokens) {
                tokens.forEach(token => {
                        if (token.is_expired) {
                            token._rowVariant = 'danger'
                        }
                    }
                )
                return tokens
591
592
593
594
595
596
597
598
599
600
601
            },
            formatContainedPermissions(roles) {
                roles.forEach(role => {
                        let permissions = []
                        for (let [key, value] of Object.entries(role.contained_permissions)) {
                            permissions.push({system: key, permission: value})
                        }
                        role.contained_permissions = permissions
                    }
                )
                return roles
602
            }
janis.streib's avatar
janis.streib committed
603
604
605
606
607
        }
    }
</script>

<style scoped>
608
609
610
611
612
613
614
    .collapse-icon {
        transition: .2s transform ease-in-out;
    }

    .collapsed .collapse-icon {
        transform: rotate(-180deg);

615
    }
616
617
618
619

    table .flip-list-move { /* TODO: Get table animations to work */
        transition: transform 1s;
    }
620
621
622
623

    .popover{
        max-width: 100%;
    }
janis.streib's avatar
janis.streib committed
624
</style>