@windward/integrations 0.0.12 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Content/Blocks/ExternalIntegration/LtiConsumer.vue +136 -20
- package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumer.vue +7 -5
- package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumers.vue +3 -2
- package/components/ExternalIntegration/Driver/Lti1p3/ManageConsumer.vue +284 -0
- package/components/ExternalIntegration/Driver/Lti1p3/ManageConsumers.vue +229 -0
- package/components/ExternalIntegration/Driver/Lti1p3/ManageProvider.vue +14 -0
- package/components/ExternalIntegration/Driver/Lti1p3/ViewConsumer.vue +224 -0
- package/components/ExternalIntegration/Driver/ManageLti1p3.vue +3 -3
- package/components/SecretField.vue +16 -1
- package/components/Settings/ExternalIntegration/LtiConsumerSettings.vue +72 -30
- package/i18n/en-US/components/content/blocks/external_integration/lti_consumer.ts +7 -0
- package/i18n/en-US/components/external_integration/driver/lti1p3.ts +4 -0
- package/i18n/en-US/components/settings/external_integration/lti_consumer.ts +3 -0
- package/models/ExternalIntegration/{Lti1p1Consumer.ts → LtiConsumer.ts} +1 -1
- package/package.json +1 -1
|
@@ -1,23 +1,52 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
3
|
<div v-if="!missing && consumer">
|
|
4
|
-
<
|
|
5
|
-
<TextViewer v-model="consumer.description"></TextViewer>
|
|
4
|
+
<h3>{{ block.metadata.config.title }}</h3>
|
|
6
5
|
|
|
7
|
-
<
|
|
8
|
-
v-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
<TextViewer
|
|
7
|
+
v-model="block.metadata.config.instructions"
|
|
8
|
+
></TextViewer>
|
|
9
|
+
|
|
10
|
+
<v-row
|
|
11
|
+
v-if="
|
|
12
|
+
consumer.enabled &&
|
|
13
|
+
block.metadata.config.launch_type !== 'inline'
|
|
14
|
+
"
|
|
15
|
+
class="d-flex justify-center"
|
|
12
16
|
>
|
|
13
|
-
<v-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
<v-col class="d-flex justify-center col-md-4">
|
|
18
|
+
<v-btn
|
|
19
|
+
v-if="
|
|
20
|
+
block.metadata.config.launch_type === 'new_window'
|
|
21
|
+
"
|
|
22
|
+
color="primary"
|
|
23
|
+
block
|
|
24
|
+
@click="onLaunch"
|
|
25
|
+
>
|
|
26
|
+
{{
|
|
27
|
+
$t(
|
|
28
|
+
'windward.integrations.components.content.blocks.external_integration.lti_consumer.launch_in_' +
|
|
29
|
+
block.metadata.config.launch_type,
|
|
30
|
+
[consumer.name]
|
|
31
|
+
)
|
|
32
|
+
}}
|
|
33
|
+
</v-btn>
|
|
34
|
+
<v-btn
|
|
35
|
+
v-else
|
|
36
|
+
color="primary"
|
|
37
|
+
block
|
|
38
|
+
@click="setLaunchTimer(100)"
|
|
39
|
+
>
|
|
40
|
+
{{
|
|
41
|
+
$t(
|
|
42
|
+
'windward.integrations.components.content.blocks.external_integration.lti_consumer.launch_in_' +
|
|
43
|
+
block.metadata.config.launch_type,
|
|
44
|
+
[consumer.name]
|
|
45
|
+
)
|
|
46
|
+
}}
|
|
47
|
+
</v-btn>
|
|
48
|
+
</v-col>
|
|
49
|
+
</v-row>
|
|
21
50
|
|
|
22
51
|
<v-alert v-if="!consumer.enabled" type="error">
|
|
23
52
|
{{
|
|
@@ -33,6 +62,7 @@
|
|
|
33
62
|
:target="target"
|
|
34
63
|
:action="launchData.target || ''"
|
|
35
64
|
:method="launchData.method || 'POST'"
|
|
65
|
+
@submit.prevent
|
|
36
66
|
>
|
|
37
67
|
<input
|
|
38
68
|
v-for="(value, key) of launchData.payload"
|
|
@@ -50,6 +80,35 @@
|
|
|
50
80
|
:name="frameId"
|
|
51
81
|
class="launch-frame"
|
|
52
82
|
></iframe>
|
|
83
|
+
<Dialog
|
|
84
|
+
v-if="block.metadata.config.launch_type === 'modal'"
|
|
85
|
+
v-model="openModal"
|
|
86
|
+
:trigger="false"
|
|
87
|
+
persistent
|
|
88
|
+
max-width="1350px"
|
|
89
|
+
@click:close="closeModal"
|
|
90
|
+
>
|
|
91
|
+
<template #title>{{ block.metadata.config.title }}</template>
|
|
92
|
+
<template #trigger>
|
|
93
|
+
<v-icon small>mdi-pencil</v-icon>
|
|
94
|
+
<span class="sr-only">{{
|
|
95
|
+
$t(
|
|
96
|
+
'windward.integrations.components.external_integration.driver.lti1p1.edit'
|
|
97
|
+
)
|
|
98
|
+
}}</span>
|
|
99
|
+
</template>
|
|
100
|
+
<template #form="{ on, attrs }">
|
|
101
|
+
<iframe
|
|
102
|
+
v-if="launched"
|
|
103
|
+
v-bind="attrs"
|
|
104
|
+
:name="frameId"
|
|
105
|
+
class="launch-frame"
|
|
106
|
+
v-on="on"
|
|
107
|
+
></iframe>
|
|
108
|
+
|
|
109
|
+
<div v-else></div>
|
|
110
|
+
</template>
|
|
111
|
+
</Dialog>
|
|
53
112
|
</div>
|
|
54
113
|
|
|
55
114
|
<v-alert v-if="missing" type="error">
|
|
@@ -88,17 +147,18 @@
|
|
|
88
147
|
<script>
|
|
89
148
|
import _ from 'lodash'
|
|
90
149
|
import { mapGetters } from 'vuex'
|
|
91
|
-
import
|
|
150
|
+
import LtiConsumer from '../../../../models/ExternalIntegration/LtiConsumer'
|
|
92
151
|
import Crypto from '~/helpers/Crypto'
|
|
93
152
|
import TextViewer from '~/components/Text/TextViewer.vue'
|
|
94
153
|
import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
|
|
95
154
|
import Course from '~/models/Course'
|
|
96
155
|
import Organization from '~/models/Organization'
|
|
97
156
|
import Enrollment from '~/models/Enrollment'
|
|
157
|
+
import Dialog from '~/components/Dialog.vue'
|
|
98
158
|
|
|
99
159
|
export default {
|
|
100
160
|
name: 'ContentBlockExternalIntegrationLti1p1Consumer',
|
|
101
|
-
components: { TextViewer },
|
|
161
|
+
components: { TextViewer, Dialog },
|
|
102
162
|
extends: BaseContentBlock,
|
|
103
163
|
data() {
|
|
104
164
|
return {
|
|
@@ -109,6 +169,8 @@ export default {
|
|
|
109
169
|
frameId: Crypto.id() + '_frame',
|
|
110
170
|
missing: false,
|
|
111
171
|
launched: false,
|
|
172
|
+
launchTimeout: {},
|
|
173
|
+
openModal: false,
|
|
112
174
|
}
|
|
113
175
|
},
|
|
114
176
|
async fetch() {
|
|
@@ -123,12 +185,27 @@ export default {
|
|
|
123
185
|
}),
|
|
124
186
|
},
|
|
125
187
|
watch: {
|
|
188
|
+
'block.metadata.config.launch_type': {
|
|
189
|
+
deep: true,
|
|
190
|
+
handler(newVal, oldVal) {
|
|
191
|
+
// We changed the consumer tool id, reload
|
|
192
|
+
if (
|
|
193
|
+
newVal === 'inline' &&
|
|
194
|
+
this.block.metadata.config.tool_id !== null
|
|
195
|
+
) {
|
|
196
|
+
this.setLaunchTimer()
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
},
|
|
126
200
|
'block.metadata.config.tool_id': {
|
|
127
201
|
deep: true,
|
|
128
202
|
handler(newVal, oldVal) {
|
|
129
203
|
// We changed the consumer tool id, reload
|
|
130
204
|
if (newVal !== oldVal) {
|
|
131
205
|
this.loadConsumer()
|
|
206
|
+
if (this.block.metadata.config.launch_type === 'inline') {
|
|
207
|
+
this.setLaunchTimer()
|
|
208
|
+
}
|
|
132
209
|
}
|
|
133
210
|
},
|
|
134
211
|
},
|
|
@@ -140,13 +217,45 @@ export default {
|
|
|
140
217
|
if (_.isEmpty(this.block.metadata.config.tool_id)) {
|
|
141
218
|
this.block.metadata.config.tool_id = null
|
|
142
219
|
}
|
|
220
|
+
if (_.isEmpty(this.block.metadata.config.title)) {
|
|
221
|
+
this.block.metadata.config.title = this.$t(
|
|
222
|
+
'windward.integrations.components.content.blocks.external_integration.lti_consumer.title'
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
if (_.isEmpty(this.block.metadata.config.instructions)) {
|
|
226
|
+
this.block.metadata.config.instructions = this.$t(
|
|
227
|
+
'windward.integrations.components.content.blocks.external_integration.lti_consumer.instructions'
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
mounted() {
|
|
232
|
+
if (
|
|
233
|
+
this.block.metadata.config.tool_id !== null &&
|
|
234
|
+
this.block.metadata.config.launch_type === 'inline'
|
|
235
|
+
) {
|
|
236
|
+
this.setLaunchTimer()
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
destroyed() {
|
|
241
|
+
clearTimeout(this.launchTimeout)
|
|
143
242
|
},
|
|
144
|
-
mounted() {},
|
|
145
243
|
methods: {
|
|
244
|
+
setLaunchTimer(timeout = 1000) {
|
|
245
|
+
clearTimeout(this.launchTimeout)
|
|
246
|
+
this.launchTimeout = setTimeout(this.onLaunch, timeout)
|
|
247
|
+
this.launched = true
|
|
248
|
+
},
|
|
249
|
+
closeModal() {
|
|
250
|
+
this.launchData = {}
|
|
251
|
+
this.target = ''
|
|
252
|
+
this.openModal = false
|
|
253
|
+
this.launched = false
|
|
254
|
+
},
|
|
146
255
|
async loadConsumer() {
|
|
147
256
|
try {
|
|
148
257
|
if (!_.isEmpty(this.block.metadata.config.tool_id)) {
|
|
149
|
-
this.consumer = await new
|
|
258
|
+
this.consumer = await new LtiConsumer()
|
|
150
259
|
.for(
|
|
151
260
|
new Organization({ id: this.organization.id }),
|
|
152
261
|
new Course({ id: this.course.id })
|
|
@@ -168,8 +277,11 @@ export default {
|
|
|
168
277
|
// Clear the launch data in-case we're in inline mode
|
|
169
278
|
// If the iframe isn't destroyed subsequent launches will be new windows instead of the target iframe
|
|
170
279
|
this.launchData = {}
|
|
280
|
+
if (this.block.metadata.config.launch_type === 'modal') {
|
|
281
|
+
this.openModal = true
|
|
282
|
+
}
|
|
171
283
|
|
|
172
|
-
this.launchData = await
|
|
284
|
+
this.launchData = await LtiConsumer.custom(
|
|
173
285
|
new Enrollment({ id: this.enrollment.id }),
|
|
174
286
|
this.consumer,
|
|
175
287
|
'/launch'
|
|
@@ -182,6 +294,9 @@ export default {
|
|
|
182
294
|
case 'inline':
|
|
183
295
|
this.target = this.frameId
|
|
184
296
|
break
|
|
297
|
+
case 'modal':
|
|
298
|
+
this.target = this.frameId
|
|
299
|
+
break
|
|
185
300
|
default:
|
|
186
301
|
this.target = '_blank'
|
|
187
302
|
}
|
|
@@ -216,5 +331,6 @@ export default {
|
|
|
216
331
|
.launch-frame {
|
|
217
332
|
aspect-ratio: 16/9;
|
|
218
333
|
width: 100%;
|
|
334
|
+
border-style: none;
|
|
219
335
|
}
|
|
220
336
|
</style>
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
<TextEditor
|
|
34
34
|
id="description"
|
|
35
35
|
v-model="consumer.description"
|
|
36
|
+
menubar="edit"
|
|
37
|
+
toolbar="undo redo | bold italic underline strikethrough "
|
|
36
38
|
:height="200"
|
|
37
39
|
></TextEditor>
|
|
38
40
|
|
|
@@ -148,7 +150,7 @@
|
|
|
148
150
|
<script>
|
|
149
151
|
import _ from 'lodash'
|
|
150
152
|
import { mapGetters } from 'vuex'
|
|
151
|
-
import
|
|
153
|
+
import LtiConsumer from '../../../../models/ExternalIntegration/LtiConsumer'
|
|
152
154
|
import Organization from '~/models/Organization'
|
|
153
155
|
import Course from '~/models/Course'
|
|
154
156
|
import TextEditor from '~/components/Text/TextEditor.vue'
|
|
@@ -160,7 +162,7 @@ export default {
|
|
|
160
162
|
extends: FormVue,
|
|
161
163
|
props: {
|
|
162
164
|
value: {
|
|
163
|
-
type: [
|
|
165
|
+
type: [LtiConsumer, null],
|
|
164
166
|
required: false,
|
|
165
167
|
default: null,
|
|
166
168
|
},
|
|
@@ -220,9 +222,9 @@ export default {
|
|
|
220
222
|
},
|
|
221
223
|
created() {
|
|
222
224
|
if (_.isEmpty(this.value)) {
|
|
223
|
-
this.consumer = new
|
|
225
|
+
this.consumer = new LtiConsumer(this.consumer)
|
|
224
226
|
} else {
|
|
225
|
-
this.consumer = new
|
|
227
|
+
this.consumer = new LtiConsumer(_.cloneDeep(this.value))
|
|
226
228
|
}
|
|
227
229
|
},
|
|
228
230
|
mounted() {
|
|
@@ -260,7 +262,7 @@ export default {
|
|
|
260
262
|
this.consumer.metadata.custom.splice(index, 1)
|
|
261
263
|
},
|
|
262
264
|
async save() {
|
|
263
|
-
let consumer = new
|
|
265
|
+
let consumer = new LtiConsumer(this.consumer).for(
|
|
264
266
|
new Organization({ id: this.organization.id }),
|
|
265
267
|
new Course({ id: this.course.id })
|
|
266
268
|
)
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
<script>
|
|
80
80
|
import _ from 'lodash'
|
|
81
81
|
import { mapGetters } from 'vuex'
|
|
82
|
-
import
|
|
82
|
+
import LtiConsumer from '../../../../models/ExternalIntegration/LtiConsumer'
|
|
83
83
|
import ManageConsumer from './ManageConsumer.vue'
|
|
84
84
|
import Course from '~/models/Course'
|
|
85
85
|
import Organization from '~/models/Organization'
|
|
@@ -168,11 +168,12 @@ export default {
|
|
|
168
168
|
this.loadConsumers()
|
|
169
169
|
},
|
|
170
170
|
async loadConsumers() {
|
|
171
|
-
this.consumers = await new
|
|
171
|
+
this.consumers = await new LtiConsumer()
|
|
172
172
|
.for(
|
|
173
173
|
new Organization({ id: this.organization.id }),
|
|
174
174
|
new Course({ id: this.course.id })
|
|
175
175
|
)
|
|
176
|
+
.where('version', '1.1')
|
|
176
177
|
.get()
|
|
177
178
|
},
|
|
178
179
|
onConfirmDelete(consumer) {
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div v-if="!render" class="integration-loading">
|
|
4
|
+
<v-progress-circular size="128" indeterminate />
|
|
5
|
+
</div>
|
|
6
|
+
<div v-if="render">
|
|
7
|
+
<v-form v-model="formValid" @submit.prevent>
|
|
8
|
+
<v-row justify="center" align="center" class="mt-5">
|
|
9
|
+
<v-col cols="12">
|
|
10
|
+
<v-text-field
|
|
11
|
+
v-model="consumer.target"
|
|
12
|
+
:label="
|
|
13
|
+
$t(
|
|
14
|
+
'windward.integrations.components.external_integration.target_url'
|
|
15
|
+
)
|
|
16
|
+
"
|
|
17
|
+
:hint="
|
|
18
|
+
$t(
|
|
19
|
+
'windward.integrations.components.external_integration.target_url'
|
|
20
|
+
)
|
|
21
|
+
"
|
|
22
|
+
></v-text-field>
|
|
23
|
+
<v-text-field
|
|
24
|
+
v-model="consumer.name"
|
|
25
|
+
:placeholder="$t('shared.forms.name')"
|
|
26
|
+
:label="$t('shared.forms.name')"
|
|
27
|
+
:hint="$t('shared.forms.name')"
|
|
28
|
+
></v-text-field>
|
|
29
|
+
|
|
30
|
+
<label for="description">{{
|
|
31
|
+
$t('shared.forms.description')
|
|
32
|
+
}}</label>
|
|
33
|
+
<TextEditor
|
|
34
|
+
id="description"
|
|
35
|
+
v-model="consumer.description"
|
|
36
|
+
menubar="edit"
|
|
37
|
+
toolbar="undo redo | bold italic underline strikethrough "
|
|
38
|
+
:height="200"
|
|
39
|
+
></TextEditor>
|
|
40
|
+
|
|
41
|
+
<v-switch
|
|
42
|
+
v-model="consumer.enabled"
|
|
43
|
+
:label="$t('shared.forms.enabled')"
|
|
44
|
+
/>
|
|
45
|
+
<v-text-field
|
|
46
|
+
v-model="consumer.metadata.tool_oidc_auth_endpoint"
|
|
47
|
+
:placeholder="
|
|
48
|
+
$t(
|
|
49
|
+
'windward.integrations.components.external_integration.driver.lti1p3.tool_oidc_auth_endpoint'
|
|
50
|
+
)
|
|
51
|
+
"
|
|
52
|
+
:label="
|
|
53
|
+
$t(
|
|
54
|
+
'windward.integrations.components.external_integration.driver.lti1p3.tool_oidc_auth_endpoint'
|
|
55
|
+
)
|
|
56
|
+
"
|
|
57
|
+
></v-text-field>
|
|
58
|
+
<v-text-field
|
|
59
|
+
v-model="consumer.metadata.tool_public_keyset_url"
|
|
60
|
+
:placeholder="
|
|
61
|
+
$t(
|
|
62
|
+
'windward.integrations.components.external_integration.driver.lti1p3.tool_public_keyset_url'
|
|
63
|
+
)
|
|
64
|
+
"
|
|
65
|
+
:label="
|
|
66
|
+
$t(
|
|
67
|
+
'windward.integrations.components.external_integration.driver.lti1p3.tool_public_keyset_url'
|
|
68
|
+
)
|
|
69
|
+
"
|
|
70
|
+
></v-text-field>
|
|
71
|
+
|
|
72
|
+
<v-data-table
|
|
73
|
+
:headers="customParameterHeaders"
|
|
74
|
+
:items="consumer.metadata.custom"
|
|
75
|
+
hide-default-footer
|
|
76
|
+
class="elevation-1"
|
|
77
|
+
>
|
|
78
|
+
<template #[`item.key`]="{ index }">
|
|
79
|
+
<v-text-field
|
|
80
|
+
v-model="
|
|
81
|
+
consumer.metadata.custom[index].key
|
|
82
|
+
"
|
|
83
|
+
:label="
|
|
84
|
+
$t(
|
|
85
|
+
'windward.integrations.components.external_integration.driver.lti1p1.parameter_name'
|
|
86
|
+
)
|
|
87
|
+
"
|
|
88
|
+
/>
|
|
89
|
+
</template>
|
|
90
|
+
<template #[`item.value`]="{ index }">
|
|
91
|
+
<v-text-field
|
|
92
|
+
v-model="
|
|
93
|
+
consumer.metadata.custom[index].value
|
|
94
|
+
"
|
|
95
|
+
:label="
|
|
96
|
+
$t(
|
|
97
|
+
'windward.integrations.components.external_integration.driver.lti1p1.value'
|
|
98
|
+
)
|
|
99
|
+
"
|
|
100
|
+
/>
|
|
101
|
+
</template>
|
|
102
|
+
<template #[`item.actions`]="{ index }">
|
|
103
|
+
<v-btn
|
|
104
|
+
text
|
|
105
|
+
color="primary"
|
|
106
|
+
@click="deleteCustomParameter(index)"
|
|
107
|
+
>
|
|
108
|
+
<v-icon small> mdi-delete </v-icon>
|
|
109
|
+
<span class="sr-only">{{
|
|
110
|
+
$t('shared.forms.delete')
|
|
111
|
+
}}</span>
|
|
112
|
+
</v-btn>
|
|
113
|
+
</template>
|
|
114
|
+
<template #footer>
|
|
115
|
+
<div class="text-center">
|
|
116
|
+
<v-btn
|
|
117
|
+
color="primary"
|
|
118
|
+
class="mb-3"
|
|
119
|
+
@click="addCustomParameter"
|
|
120
|
+
>{{ $t('shared.forms.add') }}</v-btn
|
|
121
|
+
>
|
|
122
|
+
</div>
|
|
123
|
+
</template>
|
|
124
|
+
</v-data-table>
|
|
125
|
+
<br />
|
|
126
|
+
<v-select
|
|
127
|
+
v-model="consumer.metadata.security_level"
|
|
128
|
+
:items="securityLevels"
|
|
129
|
+
item-text="name"
|
|
130
|
+
item-value="value"
|
|
131
|
+
label="Security Level"
|
|
132
|
+
outlined
|
|
133
|
+
></v-select>
|
|
134
|
+
</v-col>
|
|
135
|
+
</v-row>
|
|
136
|
+
</v-form>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</template>
|
|
140
|
+
|
|
141
|
+
<script>
|
|
142
|
+
import _ from 'lodash'
|
|
143
|
+
import { mapGetters } from 'vuex'
|
|
144
|
+
import LtiConsumer from '../../../../models/ExternalIntegration/LtiConsumer'
|
|
145
|
+
import Organization from '~/models/Organization'
|
|
146
|
+
import Course from '~/models/Course'
|
|
147
|
+
import TextEditor from '~/components/Text/TextEditor.vue'
|
|
148
|
+
import FormVue from '~/components/Form'
|
|
149
|
+
|
|
150
|
+
export default {
|
|
151
|
+
name: 'ManageLti1p3ConsumerDriver',
|
|
152
|
+
components: { TextEditor },
|
|
153
|
+
extends: FormVue,
|
|
154
|
+
props: {
|
|
155
|
+
value: {
|
|
156
|
+
type: [LtiConsumer, null],
|
|
157
|
+
required: false,
|
|
158
|
+
default: null,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
emits: ['update:consumer'],
|
|
162
|
+
meta: {
|
|
163
|
+
privilege: {
|
|
164
|
+
'': {
|
|
165
|
+
writable: true,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
data() {
|
|
170
|
+
return {
|
|
171
|
+
render: false,
|
|
172
|
+
consumer: {
|
|
173
|
+
version: '1.3',
|
|
174
|
+
metadata: {
|
|
175
|
+
custom: [],
|
|
176
|
+
security_level: '',
|
|
177
|
+
security: [],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
customParameterHeaders: [
|
|
181
|
+
{
|
|
182
|
+
text: this.$t(
|
|
183
|
+
'windward.integrations.components.external_integration.driver.lti1p1.parameter_name'
|
|
184
|
+
),
|
|
185
|
+
value: 'key',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
text: this.$t(
|
|
189
|
+
'windward.integrations.components.external_integration.driver.lti1p1.value'
|
|
190
|
+
),
|
|
191
|
+
value: 'value',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
text: this.$t('shared.forms.actions'),
|
|
195
|
+
value: 'actions',
|
|
196
|
+
sortable: false,
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
securityLevels: [
|
|
200
|
+
{ name: 'Full Access', value: 'full' },
|
|
201
|
+
{ name: 'Email Only', value: 'email' },
|
|
202
|
+
{ name: 'Name Only', value: 'name' },
|
|
203
|
+
{ name: 'Anonymous', value: 'anonymous' },
|
|
204
|
+
// { name: 'Custom', value: 'custom' }, // TODO: When this is selected provide direct access to check off LTI fields
|
|
205
|
+
],
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
computed: {
|
|
209
|
+
...mapGetters({
|
|
210
|
+
organization: 'organization/get',
|
|
211
|
+
course: 'course/get',
|
|
212
|
+
}),
|
|
213
|
+
},
|
|
214
|
+
created() {
|
|
215
|
+
if (_.isEmpty(this.value)) {
|
|
216
|
+
this.consumer = new LtiConsumer(this.consumer)
|
|
217
|
+
} else {
|
|
218
|
+
this.consumer = new LtiConsumer(_.cloneDeep(this.value))
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
mounted() {
|
|
222
|
+
if (
|
|
223
|
+
!this.$PermissionService.userHasAccessTo(
|
|
224
|
+
'plugin.windward.integrations.course.externalIntegration',
|
|
225
|
+
'writable'
|
|
226
|
+
)
|
|
227
|
+
) {
|
|
228
|
+
// Display an angry error that they can't view this driver
|
|
229
|
+
this.$dialog.error(this.$t('shared.error.description_401'), {
|
|
230
|
+
duration: null,
|
|
231
|
+
action: {
|
|
232
|
+
text: this.$t('shared.forms.close'),
|
|
233
|
+
onClick: (_e, toastObject) => {
|
|
234
|
+
toastObject.goAway(0)
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// eslint-disable-next-line no-console
|
|
240
|
+
console.error('You do not have access to this consumer!')
|
|
241
|
+
|
|
242
|
+
// Return so we don't even attempt loading
|
|
243
|
+
return false
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.render = true
|
|
247
|
+
},
|
|
248
|
+
methods: {
|
|
249
|
+
addCustomParameter() {
|
|
250
|
+
this.consumer.metadata.custom.push({ key: '', value: '' })
|
|
251
|
+
},
|
|
252
|
+
deleteCustomParameter(index) {
|
|
253
|
+
this.consumer.metadata.custom.splice(index, 1)
|
|
254
|
+
},
|
|
255
|
+
async save() {
|
|
256
|
+
let consumer = new LtiConsumer(this.consumer).for(
|
|
257
|
+
new Organization({ id: this.organization.id }),
|
|
258
|
+
new Course({ id: this.course.id })
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
consumer = await consumer.save()
|
|
263
|
+
this.consumer = consumer
|
|
264
|
+
|
|
265
|
+
// Clone and delete the metadata since we don't need to share that around
|
|
266
|
+
// Also include the vendor going back for easier mapping
|
|
267
|
+
const consumerEvent = _.cloneDeep(consumer)
|
|
268
|
+
|
|
269
|
+
this.$dialog.success(this.$t('shared.forms.saved'))
|
|
270
|
+
this.$emit('update:consumer', consumerEvent)
|
|
271
|
+
} catch (e) {
|
|
272
|
+
this.$dialog.error(
|
|
273
|
+
this.$t('windward.integrations.shared.error.save_failed')
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
async onSave() {
|
|
278
|
+
if (this.formValid) {
|
|
279
|
+
await this.save()
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
</script>
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<Dialog
|
|
4
|
+
color="primary"
|
|
5
|
+
action-save
|
|
6
|
+
action-save-new
|
|
7
|
+
@click:save="onSaved"
|
|
8
|
+
>
|
|
9
|
+
<template #title>{{
|
|
10
|
+
$t(
|
|
11
|
+
'windward.integrations.components.external_integration.driver.lti1p1.new'
|
|
12
|
+
)
|
|
13
|
+
}}</template>
|
|
14
|
+
<template #trigger>{{
|
|
15
|
+
$t(
|
|
16
|
+
'windward.integrations.components.external_integration.driver.lti1p1.new'
|
|
17
|
+
)
|
|
18
|
+
}}</template>
|
|
19
|
+
<template #form="{ on, attrs }"
|
|
20
|
+
><ManageConsumer v-bind="attrs" v-on="on"></ManageConsumer
|
|
21
|
+
></template>
|
|
22
|
+
</Dialog>
|
|
23
|
+
|
|
24
|
+
<v-data-table
|
|
25
|
+
:headers="headers"
|
|
26
|
+
:items="consumers"
|
|
27
|
+
:items-per-page="10"
|
|
28
|
+
class="elevation-1"
|
|
29
|
+
>
|
|
30
|
+
<template #[`item.description`]="{ item }">
|
|
31
|
+
{{ item.description.replace(/(<([^>]+)>)/gi, '').trim() }}
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<template #[`item.enabled`]="{ item }">
|
|
35
|
+
<v-icon :color="item.enabled ? 'success' : 'error'"
|
|
36
|
+
>{{ item.enabled ? 'mdi-check' : 'mdi-close' }}
|
|
37
|
+
</v-icon>
|
|
38
|
+
<span v-if="!item.enabled" class="sr-only">{{
|
|
39
|
+
$t('shared.forms.enabled')
|
|
40
|
+
}}</span>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<template #[`item.created_at`]="{ item }">
|
|
44
|
+
{{ $d(new Date(item.created_at), 'long') }}
|
|
45
|
+
</template>
|
|
46
|
+
<template #[`item.actions`]="{ index, item }">
|
|
47
|
+
<Dialog color="primary">
|
|
48
|
+
<template #title>{{
|
|
49
|
+
$t(
|
|
50
|
+
'windward.integrations.components.external_integration.driver.lti1p3.view'
|
|
51
|
+
)
|
|
52
|
+
}}</template>
|
|
53
|
+
<template #trigger>
|
|
54
|
+
<v-icon small>mdi-eye-outline</v-icon>
|
|
55
|
+
<span class="sr-only">{{
|
|
56
|
+
$t(
|
|
57
|
+
'windward.integrations.components.external_integration.driver.lti1p3.view'
|
|
58
|
+
)
|
|
59
|
+
}}</span>
|
|
60
|
+
</template>
|
|
61
|
+
<template #form="{ on, attrs }"
|
|
62
|
+
><ViewConsumer
|
|
63
|
+
v-model="consumers[index]"
|
|
64
|
+
v-bind="attrs"
|
|
65
|
+
v-on="on"
|
|
66
|
+
></ViewConsumer
|
|
67
|
+
></template>
|
|
68
|
+
</Dialog>
|
|
69
|
+
<Dialog color="primary" action-save @click:save="onSaved">
|
|
70
|
+
<template #title>{{
|
|
71
|
+
$t(
|
|
72
|
+
'windward.integrations.components.external_integration.driver.lti1p3.edit'
|
|
73
|
+
)
|
|
74
|
+
}}</template>
|
|
75
|
+
<template #trigger>
|
|
76
|
+
<v-icon small>mdi-pencil</v-icon>
|
|
77
|
+
<span class="sr-only">{{
|
|
78
|
+
$t(
|
|
79
|
+
'windward.integrations.components.external_integration.driver.lti1p3.edit'
|
|
80
|
+
)
|
|
81
|
+
}}</span>
|
|
82
|
+
</template>
|
|
83
|
+
<template #form="{ on, attrs }"
|
|
84
|
+
><ManageConsumer
|
|
85
|
+
v-model="consumers[index]"
|
|
86
|
+
v-bind="attrs"
|
|
87
|
+
v-on="on"
|
|
88
|
+
></ManageConsumer
|
|
89
|
+
></template>
|
|
90
|
+
</Dialog>
|
|
91
|
+
|
|
92
|
+
<v-btn icon>
|
|
93
|
+
<v-icon @click="onConfirmDelete(item)"> mdi-delete </v-icon>
|
|
94
|
+
<span class="sr-only">{{ $t('shared.forms.delete') }}</span>
|
|
95
|
+
</v-btn>
|
|
96
|
+
</template>
|
|
97
|
+
</v-data-table>
|
|
98
|
+
</div>
|
|
99
|
+
</template>
|
|
100
|
+
|
|
101
|
+
<script>
|
|
102
|
+
import _ from 'lodash'
|
|
103
|
+
import { mapGetters } from 'vuex'
|
|
104
|
+
import LtiConsumer from '../../../../models/ExternalIntegration/LtiConsumer'
|
|
105
|
+
import ManageConsumer from './ManageConsumer.vue'
|
|
106
|
+
import ViewConsumer from './ViewConsumer.vue'
|
|
107
|
+
import Course from '~/models/Course'
|
|
108
|
+
import Organization from '~/models/Organization'
|
|
109
|
+
import Dialog from '~/components/Dialog.vue'
|
|
110
|
+
|
|
111
|
+
export default {
|
|
112
|
+
name: 'ManageLti1p1ConsumersDriver',
|
|
113
|
+
components: { Dialog, ManageConsumer, ViewConsumer },
|
|
114
|
+
data() {
|
|
115
|
+
return {
|
|
116
|
+
consumers: [],
|
|
117
|
+
headers: [
|
|
118
|
+
{
|
|
119
|
+
text: this.$t(
|
|
120
|
+
'windward.integrations.components.external_integration.target_url'
|
|
121
|
+
),
|
|
122
|
+
value: 'target',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
text: this.$t('shared.forms.name'),
|
|
126
|
+
value: 'name',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
text: this.$t('shared.forms.description'),
|
|
130
|
+
value: 'description',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
text: this.$t('shared.forms.enabled'),
|
|
134
|
+
value: 'enabled',
|
|
135
|
+
},
|
|
136
|
+
{ text: this.$t('shared.forms.created'), value: 'created_at' },
|
|
137
|
+
{
|
|
138
|
+
text: this.$t('shared.forms.actions'),
|
|
139
|
+
value: 'actions',
|
|
140
|
+
sortable: false,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
async fetch() {
|
|
147
|
+
if (
|
|
148
|
+
!this.$PermissionService.userHasAccessTo(
|
|
149
|
+
'plugin.windward.integrations.course.externalIntegration',
|
|
150
|
+
'readable'
|
|
151
|
+
) ||
|
|
152
|
+
_.isEmpty(this.organization.id) ||
|
|
153
|
+
_.isEmpty(this.course.id)
|
|
154
|
+
) {
|
|
155
|
+
// Display an angry error that they can't view this driver
|
|
156
|
+
this.$dialog.error(this.$t('shared.error.description_401'), {
|
|
157
|
+
duration: null,
|
|
158
|
+
action: {
|
|
159
|
+
text: this.$t('shared.forms.close'),
|
|
160
|
+
onClick: (_e, toastObject) => {
|
|
161
|
+
toastObject.goAway(0)
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
if (_.isEmpty(this.organization.id) || _.isEmpty(this.course.id)) {
|
|
166
|
+
// eslint-disable-next-line no-console
|
|
167
|
+
console.error(
|
|
168
|
+
'Cannot load external integrations because organization or course is not set!'
|
|
169
|
+
)
|
|
170
|
+
} else {
|
|
171
|
+
// eslint-disable-next-line no-console
|
|
172
|
+
console.error(
|
|
173
|
+
'You do not have access to this external integration!'
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Return so we don't even attempt loading
|
|
178
|
+
return false
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await this.loadConsumers()
|
|
182
|
+
},
|
|
183
|
+
computed: {
|
|
184
|
+
...mapGetters({
|
|
185
|
+
organization: 'organization/get',
|
|
186
|
+
course: 'course/get',
|
|
187
|
+
}),
|
|
188
|
+
},
|
|
189
|
+
methods: {
|
|
190
|
+
onSaved() {
|
|
191
|
+
this.loadConsumers()
|
|
192
|
+
},
|
|
193
|
+
async loadConsumers() {
|
|
194
|
+
this.consumers = await new LtiConsumer()
|
|
195
|
+
.for(
|
|
196
|
+
new Organization({ id: this.organization.id }),
|
|
197
|
+
new Course({ id: this.course.id })
|
|
198
|
+
)
|
|
199
|
+
.where('version', '1.3')
|
|
200
|
+
.get()
|
|
201
|
+
},
|
|
202
|
+
onConfirmDelete(consumer) {
|
|
203
|
+
this.$dialog.show(this.$t('shared.forms.confirm_delete_text'), {
|
|
204
|
+
icon: 'mdi-help',
|
|
205
|
+
duration: null,
|
|
206
|
+
action: [
|
|
207
|
+
{
|
|
208
|
+
text: this.$t('shared.forms.cancel'),
|
|
209
|
+
onClick: (_e, toastObject) => {
|
|
210
|
+
toastObject.goAway(0)
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
text: this.$t('shared.forms.confirm'),
|
|
215
|
+
// router navigation
|
|
216
|
+
onClick: (_e, toastObject) => {
|
|
217
|
+
this.deleteConsumer(consumer)
|
|
218
|
+
toastObject.goAway(0)
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
})
|
|
223
|
+
},
|
|
224
|
+
deleteConsumer(consumer) {
|
|
225
|
+
consumer.delete()
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
</script>
|
|
@@ -127,6 +127,20 @@
|
|
|
127
127
|
)
|
|
128
128
|
"
|
|
129
129
|
></v-text-field>
|
|
130
|
+
<v-text-field
|
|
131
|
+
id="lti-oauth2-aud"
|
|
132
|
+
v-model="provider.metadata.platform_oauth2_audience"
|
|
133
|
+
:label="
|
|
134
|
+
$t(
|
|
135
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_oauth2_audience'
|
|
136
|
+
)
|
|
137
|
+
"
|
|
138
|
+
:hint="
|
|
139
|
+
$t(
|
|
140
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_oauth2_audience'
|
|
141
|
+
)
|
|
142
|
+
"
|
|
143
|
+
></v-text-field>
|
|
130
144
|
<v-text-field
|
|
131
145
|
id="lti-client-id"
|
|
132
146
|
v-model="provider.metadata.tool_client_id"
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div v-if="!render" class="integration-loading">
|
|
4
|
+
<v-progress-circular size="128" indeterminate />
|
|
5
|
+
</div>
|
|
6
|
+
<div v-if="render">
|
|
7
|
+
<v-form v-model="formValid" @submit.prevent>
|
|
8
|
+
<v-row justify="center" align="center" class="mt-5">
|
|
9
|
+
<v-col cols="12">
|
|
10
|
+
<SecretField
|
|
11
|
+
v-model="consumer.metadata.platform_id"
|
|
12
|
+
:hidden="false"
|
|
13
|
+
:placeholder="
|
|
14
|
+
$t(
|
|
15
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_id'
|
|
16
|
+
)
|
|
17
|
+
"
|
|
18
|
+
:label="
|
|
19
|
+
$t(
|
|
20
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_id'
|
|
21
|
+
)
|
|
22
|
+
"
|
|
23
|
+
></SecretField>
|
|
24
|
+
<SecretField
|
|
25
|
+
v-model="consumer.metadata.client_id"
|
|
26
|
+
:hidden="false"
|
|
27
|
+
:placeholder="
|
|
28
|
+
$t(
|
|
29
|
+
'windward.integrations.components.external_integration.driver.lti1p3.tool_client_id'
|
|
30
|
+
)
|
|
31
|
+
"
|
|
32
|
+
:label="
|
|
33
|
+
$t(
|
|
34
|
+
'windward.integrations.components.external_integration.driver.lti1p3.tool_client_id'
|
|
35
|
+
)
|
|
36
|
+
"
|
|
37
|
+
></SecretField>
|
|
38
|
+
<SecretField
|
|
39
|
+
v-model="consumer.metadata.deployment_id"
|
|
40
|
+
:hidden="false"
|
|
41
|
+
:placeholder="
|
|
42
|
+
$t(
|
|
43
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_deployment_id'
|
|
44
|
+
)
|
|
45
|
+
"
|
|
46
|
+
:label="
|
|
47
|
+
$t(
|
|
48
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_deployment_id'
|
|
49
|
+
)
|
|
50
|
+
"
|
|
51
|
+
></SecretField>
|
|
52
|
+
<SecretField
|
|
53
|
+
v-model="
|
|
54
|
+
consumer.metadata.platform_oidc_auth_endpoint
|
|
55
|
+
"
|
|
56
|
+
:hidden="false"
|
|
57
|
+
:placeholder="
|
|
58
|
+
$t(
|
|
59
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_oidc_auth_endpoint'
|
|
60
|
+
)
|
|
61
|
+
"
|
|
62
|
+
:label="
|
|
63
|
+
$t(
|
|
64
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_oidc_auth_endpoint'
|
|
65
|
+
)
|
|
66
|
+
"
|
|
67
|
+
></SecretField>
|
|
68
|
+
<SecretField
|
|
69
|
+
v-model="
|
|
70
|
+
consumer.metadata.platform_public_keyset_url
|
|
71
|
+
"
|
|
72
|
+
:hidden="false"
|
|
73
|
+
:placeholder="
|
|
74
|
+
$t(
|
|
75
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_public_keyset_url'
|
|
76
|
+
)
|
|
77
|
+
"
|
|
78
|
+
:label="
|
|
79
|
+
$t(
|
|
80
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_public_keyset_url'
|
|
81
|
+
)
|
|
82
|
+
"
|
|
83
|
+
></SecretField>
|
|
84
|
+
<SecretField
|
|
85
|
+
v-model="
|
|
86
|
+
consumer.metadata
|
|
87
|
+
.platform_oauth2_access_token_url
|
|
88
|
+
"
|
|
89
|
+
:hidden="false"
|
|
90
|
+
:placeholder="
|
|
91
|
+
$t(
|
|
92
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_oauth2_access_token_url'
|
|
93
|
+
)
|
|
94
|
+
"
|
|
95
|
+
:label="
|
|
96
|
+
$t(
|
|
97
|
+
'windward.integrations.components.external_integration.driver.lti1p3.platform_oauth2_access_token_url'
|
|
98
|
+
)
|
|
99
|
+
"
|
|
100
|
+
></SecretField>
|
|
101
|
+
</v-col>
|
|
102
|
+
</v-row>
|
|
103
|
+
</v-form>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
108
|
+
<script>
|
|
109
|
+
import _ from 'lodash'
|
|
110
|
+
import { mapGetters } from 'vuex'
|
|
111
|
+
import LtiConsumer from '../../../../models/ExternalIntegration/LtiConsumer'
|
|
112
|
+
import SecretField from '../../../SecretField.vue'
|
|
113
|
+
import Organization from '~/models/Organization'
|
|
114
|
+
import Course from '~/models/Course'
|
|
115
|
+
import FormVue from '~/components/Form'
|
|
116
|
+
export default {
|
|
117
|
+
name: 'ViewLti1p3Consumer',
|
|
118
|
+
components: { SecretField },
|
|
119
|
+
extends: FormVue,
|
|
120
|
+
props: {
|
|
121
|
+
value: {
|
|
122
|
+
type: [LtiConsumer, null],
|
|
123
|
+
required: false,
|
|
124
|
+
default: null,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
emits: ['view:consumer'],
|
|
128
|
+
meta: {
|
|
129
|
+
privilege: {
|
|
130
|
+
'': {
|
|
131
|
+
writable: true,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
data() {
|
|
136
|
+
return {
|
|
137
|
+
render: false,
|
|
138
|
+
consumer: {
|
|
139
|
+
version: '1.3',
|
|
140
|
+
metadata: {
|
|
141
|
+
custom: [],
|
|
142
|
+
security_level: '',
|
|
143
|
+
security: [],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
computed: {
|
|
149
|
+
...mapGetters({
|
|
150
|
+
organization: 'organization/get',
|
|
151
|
+
course: 'course/get',
|
|
152
|
+
}),
|
|
153
|
+
},
|
|
154
|
+
created() {
|
|
155
|
+
if (_.isEmpty(this.value)) {
|
|
156
|
+
this.consumer = new LtiConsumer(this.consumer)
|
|
157
|
+
} else {
|
|
158
|
+
this.consumer = new LtiConsumer(_.cloneDeep(this.value))
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
mounted() {
|
|
162
|
+
if (
|
|
163
|
+
!this.$PermissionService.userHasAccessTo(
|
|
164
|
+
'plugin.windward.integrations.course.externalIntegration',
|
|
165
|
+
'writable'
|
|
166
|
+
)
|
|
167
|
+
) {
|
|
168
|
+
// Display an angry error that they can't view this driver
|
|
169
|
+
this.$dialog.error(this.$t('shared.error.description_401'), {
|
|
170
|
+
duration: null,
|
|
171
|
+
action: {
|
|
172
|
+
text: this.$t('shared.forms.close'),
|
|
173
|
+
onClick: (_e, toastObject) => {
|
|
174
|
+
toastObject.goAway(0)
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// eslint-disable-next-line no-console
|
|
180
|
+
console.error('You do not have access to this consumer!')
|
|
181
|
+
|
|
182
|
+
// Return so we don't even attempt loading
|
|
183
|
+
return false
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.render = true
|
|
187
|
+
},
|
|
188
|
+
methods: {
|
|
189
|
+
addCustomParameter() {
|
|
190
|
+
this.consumer.metadata.custom.push({ key: '', value: '' })
|
|
191
|
+
},
|
|
192
|
+
deleteCustomParameter(index) {
|
|
193
|
+
this.consumer.metadata.custom.splice(index, 1)
|
|
194
|
+
},
|
|
195
|
+
async save() {
|
|
196
|
+
let consumer = new LtiConsumer(this.consumer).for(
|
|
197
|
+
new Organization({ id: this.organization.id }),
|
|
198
|
+
new Course({ id: this.course.id })
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
consumer = await consumer.save()
|
|
203
|
+
this.consumer = consumer
|
|
204
|
+
|
|
205
|
+
// Clone and delete the metadata since we don't need to share that around
|
|
206
|
+
// Also include the vendor going back for easier mapping
|
|
207
|
+
const consumerEvent = _.cloneDeep(consumer)
|
|
208
|
+
|
|
209
|
+
this.$dialog.success(this.$t('shared.forms.saved'))
|
|
210
|
+
this.$emit('update:consumer', consumerEvent)
|
|
211
|
+
} catch (e) {
|
|
212
|
+
this.$dialog.error(
|
|
213
|
+
this.$t('windward.integrations.shared.error.save_failed')
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
async onSave() {
|
|
218
|
+
if (this.formValid) {
|
|
219
|
+
await this.save()
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
</script>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}}
|
|
23
23
|
</v-expansion-panel-header>
|
|
24
24
|
<v-expansion-panel-content>
|
|
25
|
-
|
|
25
|
+
<ManageConsumers></ManageConsumers>
|
|
26
26
|
</v-expansion-panel-content>
|
|
27
27
|
</v-expansion-panel>
|
|
28
28
|
</v-expansion-panels>
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
|
|
32
32
|
<script>
|
|
33
33
|
import ManageProviders from './Lti1p3/ManageProviders.vue'
|
|
34
|
-
|
|
34
|
+
import ManageConsumers from './Lti1p3/ManageConsumers.vue'
|
|
35
35
|
|
|
36
36
|
export default {
|
|
37
37
|
name: 'ManageLti1p3Driver',
|
|
38
|
-
components: { ManageProviders },
|
|
38
|
+
components: { ManageProviders, ManageConsumers },
|
|
39
39
|
data() {
|
|
40
40
|
return {
|
|
41
41
|
panel: 0,
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<v-text-field
|
|
2
|
+
<v-text-field
|
|
3
|
+
id="secret-field"
|
|
4
|
+
:value="value"
|
|
5
|
+
readonly
|
|
6
|
+
:type="fieldType"
|
|
7
|
+
:placeholder="placeholder"
|
|
8
|
+
:label="label"
|
|
9
|
+
>
|
|
3
10
|
<template #append>
|
|
4
11
|
<v-btn icon @click="toggleClear">
|
|
5
12
|
<v-icon>{{ showClear ? 'mdi-eye-off' : 'mdi-eye' }}</v-icon>
|
|
@@ -19,6 +26,14 @@ export default {
|
|
|
19
26
|
value: { type: String, required: false, default: '' },
|
|
20
27
|
hidden: { type: Boolean, required: false, default: true },
|
|
21
28
|
copy: { type: Boolean, required: false, default: true },
|
|
29
|
+
placeholder: {
|
|
30
|
+
type: String,
|
|
31
|
+
required: false,
|
|
32
|
+
},
|
|
33
|
+
label: {
|
|
34
|
+
type: String,
|
|
35
|
+
required: false,
|
|
36
|
+
},
|
|
22
37
|
},
|
|
23
38
|
data() {
|
|
24
39
|
return {
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-container>
|
|
3
|
+
<v-text-field
|
|
4
|
+
v-model="block.metadata.config.title"
|
|
5
|
+
outlined
|
|
6
|
+
:counter="50"
|
|
7
|
+
maxlength="50"
|
|
8
|
+
:label="
|
|
9
|
+
$t(
|
|
10
|
+
'windward.integrations.components.settings.external_integration.lti_consumer.title'
|
|
11
|
+
)
|
|
12
|
+
"
|
|
13
|
+
></v-text-field>
|
|
14
|
+
<v-textarea
|
|
15
|
+
v-model="block.metadata.config.instructions"
|
|
16
|
+
outlined
|
|
17
|
+
:counter="255"
|
|
18
|
+
maxlength="255"
|
|
19
|
+
:label="
|
|
20
|
+
$t(
|
|
21
|
+
'windward.integrations.components.settings.external_integration.lti_consumer.instructions'
|
|
22
|
+
)
|
|
23
|
+
"
|
|
24
|
+
></v-textarea>
|
|
25
|
+
<br />
|
|
26
|
+
<v-divider class="primary"></v-divider>
|
|
27
|
+
<br />
|
|
3
28
|
<v-select
|
|
4
29
|
v-model="block.metadata.config.launch_type"
|
|
5
30
|
:label="
|
|
@@ -17,40 +42,55 @@
|
|
|
17
42
|
)
|
|
18
43
|
}}
|
|
19
44
|
<v-list>
|
|
20
|
-
<v-list-item
|
|
21
|
-
v-
|
|
22
|
-
|
|
23
|
-
two-line
|
|
24
|
-
:disabled="!consumer.enabled"
|
|
25
|
-
@click="onSelectLink(consumer)"
|
|
45
|
+
<v-list-item-group
|
|
46
|
+
v-model="block.metadata.config.tool_id"
|
|
47
|
+
color="primary"
|
|
26
48
|
>
|
|
27
|
-
<v-list-item
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
</v-list-item-
|
|
39
|
-
<v-list-item-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
<v-list-item
|
|
50
|
+
v-for="consumer in consumers"
|
|
51
|
+
:key="consumer.id"
|
|
52
|
+
two-line
|
|
53
|
+
:disabled="!consumer.enabled"
|
|
54
|
+
:value="consumer.id"
|
|
55
|
+
>
|
|
56
|
+
<v-list-item-avatar
|
|
57
|
+
:color="!consumer.enabled ? 'error' : ''"
|
|
58
|
+
>
|
|
59
|
+
<v-icon> mdi-shield-link-variant-outline</v-icon>
|
|
60
|
+
</v-list-item-avatar>
|
|
61
|
+
<v-list-item-content color="secondary">
|
|
62
|
+
<v-list-item-title>{{
|
|
63
|
+
consumer.name
|
|
64
|
+
}}</v-list-item-title>
|
|
65
|
+
<v-list-item-subtitle v-if="!consumer.enabled">
|
|
66
|
+
<v-chip color="error"
|
|
67
|
+
>{{
|
|
68
|
+
$t(
|
|
69
|
+
'windward.integrations.components.settings.external_integration.lti_consumer.link_disabled'
|
|
70
|
+
)
|
|
71
|
+
}}
|
|
72
|
+
</v-chip>
|
|
73
|
+
</v-list-item-subtitle>
|
|
74
|
+
<v-list-item-subtitle
|
|
75
|
+
>{{
|
|
76
|
+
consumer.description
|
|
77
|
+
.replace(/(<([^>]+)>)/gi, '')
|
|
78
|
+
.trim()
|
|
79
|
+
}}
|
|
80
|
+
</v-list-item-subtitle>
|
|
81
|
+
</v-list-item-content>
|
|
82
|
+
</v-list-item>
|
|
83
|
+
</v-list-item-group>
|
|
44
84
|
</v-list>
|
|
45
85
|
</v-container>
|
|
46
86
|
</template>
|
|
47
87
|
|
|
48
88
|
<script>
|
|
49
89
|
import { mapGetters } from 'vuex'
|
|
90
|
+
import LtiConsumer from '../../../models/ExternalIntegration/LtiConsumer'
|
|
50
91
|
import BaseContentSettings from '~/components/Content/Tool/BaseContentSettings.js'
|
|
51
92
|
import Course from '~/models/Course'
|
|
52
93
|
import Organization from '~/models/Organization'
|
|
53
|
-
import Lti1p1Consumer from '../../../models/ExternalIntegration/Lti1p1Consumer'
|
|
54
94
|
|
|
55
95
|
export default {
|
|
56
96
|
name: 'ContentBlockExternalIntegrationLti1p1ConsumerSettings',
|
|
@@ -75,11 +115,17 @@ export default {
|
|
|
75
115
|
),
|
|
76
116
|
value: 'inline',
|
|
77
117
|
},
|
|
118
|
+
{
|
|
119
|
+
name: this.$t(
|
|
120
|
+
'windward.integrations.components.settings.external_integration.lti_consumer.launch_type_modal'
|
|
121
|
+
),
|
|
122
|
+
value: 'modal',
|
|
123
|
+
},
|
|
78
124
|
],
|
|
79
125
|
}
|
|
80
126
|
},
|
|
81
127
|
async fetch() {
|
|
82
|
-
this.consumers = await new
|
|
128
|
+
this.consumers = await new LtiConsumer()
|
|
83
129
|
.for(
|
|
84
130
|
new Organization({ id: this.organization.id }),
|
|
85
131
|
new Course({ id: this.course.id })
|
|
@@ -96,10 +142,6 @@ export default {
|
|
|
96
142
|
watch: {},
|
|
97
143
|
beforeMount() {},
|
|
98
144
|
mounted() {},
|
|
99
|
-
methods: {
|
|
100
|
-
onSelectLink(consumer) {
|
|
101
|
-
this.block.metadata.config.tool_id = consumer.id
|
|
102
|
-
},
|
|
103
|
-
},
|
|
145
|
+
methods: {},
|
|
104
146
|
}
|
|
105
147
|
</script>
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
title: 'LTI Consumer Block Title',
|
|
3
|
+
instructions:
|
|
4
|
+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam fringilla ipsum eget ante tempus blandit. Maecenas vel massa\n' +
|
|
5
|
+
'nec tellus vestibulum porttitor non a enim.',
|
|
2
6
|
launch: 'Launch',
|
|
7
|
+
goodbye: 'goodbye',
|
|
8
|
+
launch_in_new_window: 'launch {0} in new tab',
|
|
9
|
+
launch_in_modal: 'launch {0} in modal',
|
|
3
10
|
configure_warning:
|
|
4
11
|
'This block needs to be configured. Please select a LTI tool in the settings panel.',
|
|
5
12
|
no_access:
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
key: 'Key',
|
|
3
|
+
issuer: 'Issuer',
|
|
3
4
|
tool_public_keyset_url: 'Tool Keyset Url',
|
|
4
5
|
tool_oidc_auth_endpoint: 'Tool OpenID Connect Endpoint',
|
|
6
|
+
login_hint: 'login hint',
|
|
5
7
|
platform_id: 'Platform Identifier',
|
|
6
8
|
platform_name: 'Platform Name',
|
|
7
9
|
platform_public_keyset_url: 'Platform Keyset Url',
|
|
8
10
|
platform_oidc_auth_endpoint: 'Platform OpenID Connect Endpoint',
|
|
9
11
|
platform_oauth2_access_token_url: 'Platform OAuth2 Access Token URL',
|
|
12
|
+
platform_oauth2_audience: 'Platform Oauth2 audience',
|
|
10
13
|
tool_client_id: 'Tool Client ID',
|
|
11
14
|
platform_deployment_id: 'Deployment ID',
|
|
12
15
|
enable_button_feedback:
|
|
@@ -15,4 +18,5 @@ export default {
|
|
|
15
18
|
value: 'Value',
|
|
16
19
|
new: 'New LTI Link',
|
|
17
20
|
edit: 'Edit LTI Link',
|
|
21
|
+
view: 'View LTI link details',
|
|
18
22
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
title: 'title',
|
|
3
|
+
instructions: 'instructions',
|
|
2
4
|
launch_type: 'Launch Type',
|
|
3
5
|
launch_type_new_window: 'New Window',
|
|
4
6
|
launch_type_inline_frame: 'Inline Frame',
|
|
7
|
+
launch_type_modal: 'Modal',
|
|
5
8
|
link_select: 'Select a Link:',
|
|
6
9
|
link_disabled: 'Link Disabled',
|
|
7
10
|
}
|