@windward/integrations 0.0.7 → 0.0.9
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 +15 -0
- package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumer.vue +3 -7
- package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumers.vue +10 -14
- package/components/ExternalIntegration/Driver/Lti1p1/ManageProvider.vue +28 -21
- package/components/ExternalIntegration/Driver/Lti1p1/ManageProviders.vue +25 -11
- package/components/ExternalIntegration/Driver/Lti1p3/ManageProvider.vue +445 -0
- package/components/ExternalIntegration/Driver/Lti1p3/ManageProviders.vue +259 -0
- package/components/ExternalIntegration/Driver/ManageLti1p1.vue +2 -2
- package/components/ExternalIntegration/Driver/ManageLti1p3.vue +45 -0
- package/components/ExternalIntegration/ProviderTargetPicker.vue +21 -37
- package/components/Integration/Driver/ManageAtutor.vue +15 -0
- package/components/Integration/JobLog.vue +98 -0
- package/components/Integration/JobTable.vue +15 -1
- package/i18n/en-US/components/content/blocks/external_integration/lti_consumer.ts +2 -0
- package/i18n/en-US/components/external_integration/driver/lti1p1.ts +0 -21
- package/i18n/en-US/components/external_integration/driver/lti1p3.ts +13 -0
- package/i18n/en-US/components/external_integration/index.ts +26 -1
- package/i18n/en-US/components/integration/driver.ts +1 -0
- package/i18n/en-US/components/integration/job.ts +3 -0
- package/i18n/en-US/pages/login/lti.ts +1 -1
- package/models/ExternalIntegration/{Lti1p1Provider.ts → LtiProvider.ts} +2 -2
- package/package.json +1 -1
- package/pages/admin/importCourse.vue +1 -1
- package/pages/admin/vendors.vue +3 -2
- package/pages/course/externalIntegration/index.vue +4 -3
- package/plugin.js +48 -1
- package/test/Components/ExternalIntegration/Lti1p3/ManageProvider.spec.js +19 -0
- package/test/Components/ExternalIntegration/Lti1p3/ManageProviders.spec.js +19 -0
- package/test/Components/ExternalIntegration/ManageLti1p3.spec.js +19 -0
- package/test/Components/ExternalIntegration/ProviderTargetPicker.spec.js +1 -1
- package/test/Components/Integration/JobLog.spec.js +22 -0
- package/test/Components/Integration/JobTable.spec.js +23 -0
- package/test/__mocks__/componentsMock.js +12 -0
- package/test/mocks.js +12 -0
|
@@ -0,0 +1,259 @@
|
|
|
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>{{ $t('shared.forms.new') }}</template>
|
|
15
|
+
<template #form="{ on, attrs }"
|
|
16
|
+
><ManageProvider v-bind="attrs" v-on="on"></ManageProvider
|
|
17
|
+
></template>
|
|
18
|
+
</Dialog>
|
|
19
|
+
|
|
20
|
+
<v-data-table
|
|
21
|
+
:headers="headers"
|
|
22
|
+
:items="providers"
|
|
23
|
+
:items-per-page="10"
|
|
24
|
+
class="elevation-1"
|
|
25
|
+
>
|
|
26
|
+
<template #[`item.target`]="{ item }">
|
|
27
|
+
<ProviderTargetViewer
|
|
28
|
+
class="field--target text-truncate"
|
|
29
|
+
:target="item.target"
|
|
30
|
+
></ProviderTargetViewer>
|
|
31
|
+
</template>
|
|
32
|
+
<template #[`item.metadata.tool_public_keyset_url`]="{ item }">
|
|
33
|
+
<SecretField
|
|
34
|
+
v-model="item.metadata.tool_public_keyset_url"
|
|
35
|
+
:hidden="false"
|
|
36
|
+
></SecretField>
|
|
37
|
+
</template>
|
|
38
|
+
<template #[`item.metadata.tool_oidc_auth_endpoint`]="{ item }">
|
|
39
|
+
<SecretField
|
|
40
|
+
v-model="item.metadata.tool_oidc_auth_endpoint"
|
|
41
|
+
:hidden="false"
|
|
42
|
+
></SecretField>
|
|
43
|
+
</template>
|
|
44
|
+
<template #[`item.url`]="{ item }">
|
|
45
|
+
<SecretField v-model="item.url" :hidden="false"></SecretField>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<template #[`item.enabled`]="{ item }">
|
|
49
|
+
<v-icon :color="item.enabled ? 'success' : 'error'"
|
|
50
|
+
>{{ item.enabled ? 'mdi-check' : 'mdi-close' }}
|
|
51
|
+
</v-icon>
|
|
52
|
+
<span v-if="!item.enabled" class="sr-only">{{
|
|
53
|
+
$t('shared.forms.enabled')
|
|
54
|
+
}}</span>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<template #[`item.grade_sync`]="{ item }">
|
|
58
|
+
<v-icon :color="item.grade_sync ? 'success' : 'error'"
|
|
59
|
+
>{{ item.grade_sync ? 'mdi-check' : 'mdi-close' }}
|
|
60
|
+
</v-icon>
|
|
61
|
+
<span v-if="!item.grade_sync" class="sr-only">{{
|
|
62
|
+
$t(
|
|
63
|
+
'windward.integrations.components.external_integration.grade_sync'
|
|
64
|
+
)
|
|
65
|
+
}}</span>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<template #[`item.created_at`]="{ item }">
|
|
69
|
+
{{ $d(new Date(item.created_at), 'short') }}
|
|
70
|
+
</template>
|
|
71
|
+
<template #[`item.actions`]="{ index, item }">
|
|
72
|
+
<Dialog color="primary" action-save @click:save="onSaved">
|
|
73
|
+
<template #title>{{
|
|
74
|
+
$t(
|
|
75
|
+
'windward.integrations.components.external_integration.driver.lti1p3.edit'
|
|
76
|
+
)
|
|
77
|
+
}}</template>
|
|
78
|
+
<template #trigger>
|
|
79
|
+
<v-icon small>mdi-pencil</v-icon>
|
|
80
|
+
<span class="sr-only">{{
|
|
81
|
+
$t(
|
|
82
|
+
'windward.integrations.components.external_integration.driver.lti1p3.edit'
|
|
83
|
+
)
|
|
84
|
+
}}</span>
|
|
85
|
+
</template>
|
|
86
|
+
<template #form="{ on, attrs }"
|
|
87
|
+
><ManageProvider
|
|
88
|
+
v-model="providers[index]"
|
|
89
|
+
v-bind="attrs"
|
|
90
|
+
v-on="on"
|
|
91
|
+
></ManageProvider
|
|
92
|
+
></template>
|
|
93
|
+
</Dialog>
|
|
94
|
+
|
|
95
|
+
<v-btn icon>
|
|
96
|
+
<v-icon @click="onConfirmDelete(item)"> mdi-delete </v-icon>
|
|
97
|
+
<span class="sr-only">{{ $t('shared.forms.delete') }}</span>
|
|
98
|
+
</v-btn>
|
|
99
|
+
</template>
|
|
100
|
+
</v-data-table>
|
|
101
|
+
</div>
|
|
102
|
+
</template>
|
|
103
|
+
|
|
104
|
+
<script>
|
|
105
|
+
import _ from 'lodash'
|
|
106
|
+
import { mapGetters } from 'vuex'
|
|
107
|
+
import LtiProvider from '../../../../models/ExternalIntegration/LtiProvider'
|
|
108
|
+
import SecretField from '../../../SecretField.vue'
|
|
109
|
+
import ProviderTargetViewer from '../../ProviderTargetViewer.vue'
|
|
110
|
+
import ManageProvider from './ManageProvider.vue'
|
|
111
|
+
import Dialog from '~/components/Dialog.vue'
|
|
112
|
+
import Organization from '~/models/Organization'
|
|
113
|
+
import Course from '~/models/Course'
|
|
114
|
+
|
|
115
|
+
export default {
|
|
116
|
+
name: 'ManageLti1p1ProvidersDriver',
|
|
117
|
+
components: { SecretField, Dialog, ManageProvider, ProviderTargetViewer },
|
|
118
|
+
data() {
|
|
119
|
+
return {
|
|
120
|
+
providers: [],
|
|
121
|
+
headers: [
|
|
122
|
+
{
|
|
123
|
+
text: this.$t(
|
|
124
|
+
'windward.integrations.components.external_integration.target'
|
|
125
|
+
),
|
|
126
|
+
value: 'target',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
text: this.$t(
|
|
130
|
+
'windward.integrations.components.external_integration.driver.lti1p3.tool_public_keyset_url'
|
|
131
|
+
),
|
|
132
|
+
value: 'metadata.tool_public_keyset_url',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
text: this.$t(
|
|
136
|
+
'windward.integrations.components.external_integration.driver.lti1p3.tool_oidc_auth_endpoint'
|
|
137
|
+
),
|
|
138
|
+
value: 'metadata.tool_oidc_auth_endpoint',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
text: this.$t(
|
|
142
|
+
'windward.integrations.components.external_integration.launch_url'
|
|
143
|
+
),
|
|
144
|
+
value: 'url',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
text: this.$t('shared.forms.enabled'),
|
|
148
|
+
value: 'enabled',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
text: this.$t(
|
|
152
|
+
'windward.integrations.components.external_integration.grade_sync'
|
|
153
|
+
),
|
|
154
|
+
value: 'grade_sync',
|
|
155
|
+
},
|
|
156
|
+
{ text: this.$t('shared.forms.created'), value: 'created_at' },
|
|
157
|
+
{
|
|
158
|
+
text: this.$t('shared.forms.actions'),
|
|
159
|
+
value: 'actions',
|
|
160
|
+
sortable: false,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
async fetch() {
|
|
167
|
+
if (
|
|
168
|
+
!this.$PermissionService.userHasAccessTo(
|
|
169
|
+
'plugin.windward.integrations.course.externalIntegration',
|
|
170
|
+
'readable'
|
|
171
|
+
) ||
|
|
172
|
+
_.isEmpty(this.organization.id) ||
|
|
173
|
+
_.isEmpty(this.course.id)
|
|
174
|
+
) {
|
|
175
|
+
// Display an angry error that they can't view this driver
|
|
176
|
+
this.$dialog.error(this.$t('shared.error.description_401'), {
|
|
177
|
+
duration: null,
|
|
178
|
+
action: {
|
|
179
|
+
text: this.$t('shared.forms.close'),
|
|
180
|
+
onClick: (_e, toastObject) => {
|
|
181
|
+
toastObject.goAway(0)
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
if (_.isEmpty(this.organization.id) || _.isEmpty(this.course.id)) {
|
|
186
|
+
// eslint-disable-next-line no-console
|
|
187
|
+
console.error(
|
|
188
|
+
'Cannot load external integrations because organization or course is not set!'
|
|
189
|
+
)
|
|
190
|
+
} else {
|
|
191
|
+
// eslint-disable-next-line no-console
|
|
192
|
+
console.error(
|
|
193
|
+
'You do not have access to this external integration!'
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Return so we don't even attempt loading
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await this.loadProviders()
|
|
202
|
+
},
|
|
203
|
+
computed: {
|
|
204
|
+
...mapGetters({
|
|
205
|
+
organization: 'organization/get',
|
|
206
|
+
course: 'course/get',
|
|
207
|
+
}),
|
|
208
|
+
},
|
|
209
|
+
methods: {
|
|
210
|
+
onSaved() {
|
|
211
|
+
this.loadProviders()
|
|
212
|
+
},
|
|
213
|
+
async loadProviders() {
|
|
214
|
+
this.providers = await new LtiProvider()
|
|
215
|
+
.for(
|
|
216
|
+
new Organization({ id: this.organization.id }),
|
|
217
|
+
new Course({ id: this.course.id })
|
|
218
|
+
)
|
|
219
|
+
.where('version', '1.3')
|
|
220
|
+
.get()
|
|
221
|
+
},
|
|
222
|
+
onConfirmDelete(provider) {
|
|
223
|
+
this.$dialog.show(this.$t('shared.forms.confirm_delete_text'), {
|
|
224
|
+
icon: 'mdi-help',
|
|
225
|
+
duration: null,
|
|
226
|
+
action: [
|
|
227
|
+
{
|
|
228
|
+
text: this.$t('shared.forms.cancel'),
|
|
229
|
+
onClick: (_e, toastObject) => {
|
|
230
|
+
toastObject.goAway(0)
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
text: this.$t('shared.forms.confirm'),
|
|
235
|
+
// router navigation
|
|
236
|
+
onClick: (_e, toastObject) => {
|
|
237
|
+
this.deleteProvider(provider)
|
|
238
|
+
toastObject.goAway(0)
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
})
|
|
243
|
+
},
|
|
244
|
+
async deleteProvider(provider) {
|
|
245
|
+
await provider.delete()
|
|
246
|
+
this.$dialog.success(this.$t('shared.response.deleted'))
|
|
247
|
+
// Reload providers now that we deleted one
|
|
248
|
+
this.loadProviders()
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
</script>
|
|
253
|
+
|
|
254
|
+
<style scoped>
|
|
255
|
+
.field--target {
|
|
256
|
+
max-width: 15em;
|
|
257
|
+
display: inline-block;
|
|
258
|
+
}
|
|
259
|
+
</style>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<v-expansion-panel-header>
|
|
6
6
|
{{
|
|
7
7
|
$t(
|
|
8
|
-
'windward.integrations.components.external_integration.
|
|
8
|
+
'windward.integrations.components.external_integration.provider_panel_title'
|
|
9
9
|
)
|
|
10
10
|
}}
|
|
11
11
|
</v-expansion-panel-header>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<v-expansion-panel-header>
|
|
18
18
|
{{
|
|
19
19
|
$t(
|
|
20
|
-
'windward.integrations.components.external_integration.
|
|
20
|
+
'windward.integrations.components.external_integration.consumer_panel_title'
|
|
21
21
|
)
|
|
22
22
|
}}
|
|
23
23
|
</v-expansion-panel-header>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-expansion-panels v-model="panel">
|
|
4
|
+
<v-expansion-panel>
|
|
5
|
+
<v-expansion-panel-header>
|
|
6
|
+
{{
|
|
7
|
+
$t(
|
|
8
|
+
'windward.integrations.components.external_integration.provider_panel_title'
|
|
9
|
+
)
|
|
10
|
+
}}
|
|
11
|
+
</v-expansion-panel-header>
|
|
12
|
+
<v-expansion-panel-content>
|
|
13
|
+
<ManageProviders></ManageProviders>
|
|
14
|
+
</v-expansion-panel-content>
|
|
15
|
+
</v-expansion-panel>
|
|
16
|
+
<v-expansion-panel>
|
|
17
|
+
<v-expansion-panel-header>
|
|
18
|
+
{{
|
|
19
|
+
$t(
|
|
20
|
+
'windward.integrations.components.external_integration.consumer_panel_title'
|
|
21
|
+
)
|
|
22
|
+
}}
|
|
23
|
+
</v-expansion-panel-header>
|
|
24
|
+
<v-expansion-panel-content>
|
|
25
|
+
<!-- <ManageConsumers></ManageConsumers>-->
|
|
26
|
+
</v-expansion-panel-content>
|
|
27
|
+
</v-expansion-panel>
|
|
28
|
+
</v-expansion-panels>
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script>
|
|
33
|
+
import ManageProviders from './Lti1p3/ManageProviders.vue'
|
|
34
|
+
/* import ManageConsumers from './Lti1p3/ManageConsumers.vue' */
|
|
35
|
+
|
|
36
|
+
export default {
|
|
37
|
+
name: 'ManageLti1p3Driver',
|
|
38
|
+
components: { ManageProviders },
|
|
39
|
+
data() {
|
|
40
|
+
return {
|
|
41
|
+
panel: 0,
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
3
|
<v-radio-group
|
|
4
|
-
v-model="
|
|
4
|
+
v-model="provider.target_type"
|
|
5
5
|
row
|
|
6
6
|
@change="onTargetTypeChange"
|
|
7
7
|
>
|
|
@@ -50,10 +50,9 @@
|
|
|
50
50
|
|
|
51
51
|
<div
|
|
52
52
|
v-if="
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
localMetadata.target_type === 'locked_course_content'
|
|
53
|
+
provider.target_type === 'course_content' ||
|
|
54
|
+
provider.target_type === 'locked_course_content_children' ||
|
|
55
|
+
provider.target_type === 'locked_course_content'
|
|
57
56
|
"
|
|
58
57
|
class="content-tree secondary"
|
|
59
58
|
>
|
|
@@ -91,15 +90,13 @@ import { mapGetters } from 'vuex'
|
|
|
91
90
|
export default {
|
|
92
91
|
name: 'ExternalIntegrationProviderTargetPicker',
|
|
93
92
|
props: {
|
|
94
|
-
|
|
95
|
-
metadata: { type: Object, required: true, default: () => {} },
|
|
93
|
+
value: { type: Object, required: true },
|
|
96
94
|
},
|
|
97
95
|
data() {
|
|
98
96
|
return {
|
|
99
97
|
selectedContent: null,
|
|
100
98
|
selectedTreeviewContent: [],
|
|
101
|
-
|
|
102
|
-
localMetadata: {},
|
|
99
|
+
provider: {},
|
|
103
100
|
}
|
|
104
101
|
},
|
|
105
102
|
computed: {
|
|
@@ -110,41 +107,28 @@ export default {
|
|
|
110
107
|
},
|
|
111
108
|
watch: {
|
|
112
109
|
// Apply any changes coming up
|
|
113
|
-
|
|
110
|
+
value: {
|
|
114
111
|
handler(value) {
|
|
115
|
-
this.
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
metadata: {
|
|
119
|
-
deep: true,
|
|
120
|
-
handler(value) {
|
|
121
|
-
this.localMetadata = _.cloneDeep(value)
|
|
112
|
+
this.provider = _.cloneDeep(value)
|
|
122
113
|
},
|
|
123
114
|
},
|
|
124
115
|
},
|
|
125
116
|
mounted() {
|
|
126
117
|
// Init our local vars
|
|
127
|
-
this.
|
|
128
|
-
this.localMetadata = _.cloneDeep(this.metadata)
|
|
118
|
+
this.provider = _.cloneDeep(this.value)
|
|
129
119
|
|
|
130
120
|
// Set the target_type key if it doesn't exist
|
|
131
|
-
if (!this.
|
|
132
|
-
this.
|
|
121
|
+
if (!this.provider.target_type) {
|
|
122
|
+
this.provider.target_type = 'course'
|
|
133
123
|
}
|
|
134
124
|
|
|
135
125
|
// Preset the selected content
|
|
136
|
-
if (this.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
if (!_.isEmpty(match) && _.get(match, 'groups.content_id', null)) {
|
|
142
|
-
this.setSelectedContentById(
|
|
143
|
-
_.get(match, 'groups.content_id', null)
|
|
144
|
-
)
|
|
126
|
+
if (this.provider.target) {
|
|
127
|
+
if (!_.isEmpty(this.provider.course_content_id)) {
|
|
128
|
+
this.setSelectedContentById(this.provider.course_content_id)
|
|
145
129
|
}
|
|
146
130
|
}
|
|
147
|
-
this.sync(this.
|
|
131
|
+
this.sync(this.provider.target_type)
|
|
148
132
|
},
|
|
149
133
|
methods: {
|
|
150
134
|
setSelectedContentById(id) {
|
|
@@ -161,13 +145,14 @@ export default {
|
|
|
161
145
|
targetType === 'locked_course_content_children')
|
|
162
146
|
) {
|
|
163
147
|
url = `/course/${this.course.id}/content/${this.selectedContent.id}`
|
|
148
|
+
this.provider.course_content_id = this.selectedContent.id
|
|
164
149
|
} else if (targetType === 'course') {
|
|
165
150
|
url = `/course/${this.course.id}/`
|
|
151
|
+
this.provider.course_content_id = null
|
|
166
152
|
}
|
|
167
|
-
this.
|
|
153
|
+
this.provider.target = url
|
|
168
154
|
// Sync up the changes to the parent component
|
|
169
|
-
this.$emit('
|
|
170
|
-
this.$emit('update:metadata', this.localMetadata)
|
|
155
|
+
this.$emit('input', this.provider)
|
|
171
156
|
},
|
|
172
157
|
onTargetTypeChange(e) {
|
|
173
158
|
// Reset the selected content if you pick by course
|
|
@@ -182,8 +167,7 @@ export default {
|
|
|
182
167
|
let isIncluded = false
|
|
183
168
|
// If we aren't including children then return hard false
|
|
184
169
|
if (
|
|
185
|
-
this.
|
|
186
|
-
'locked_course_content_children'
|
|
170
|
+
this.provider.target_type !== 'locked_course_content_children'
|
|
187
171
|
) {
|
|
188
172
|
return false
|
|
189
173
|
}
|
|
@@ -220,7 +204,7 @@ export default {
|
|
|
220
204
|
this.selectedContent = null
|
|
221
205
|
}
|
|
222
206
|
|
|
223
|
-
this.sync(this.
|
|
207
|
+
this.sync(this.provider.target_type)
|
|
224
208
|
},
|
|
225
209
|
},
|
|
226
210
|
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
/>
|
|
17
17
|
|
|
18
18
|
<v-text-field
|
|
19
|
+
ref="atutor_url"
|
|
19
20
|
v-model="integration.metadata.config.url"
|
|
20
21
|
:label="
|
|
21
22
|
$t(
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
'windward.integrations.components.integration.driver.atutor.url_hint'
|
|
28
29
|
)
|
|
29
30
|
"
|
|
31
|
+
:rules="urlValidation"
|
|
30
32
|
></v-text-field>
|
|
31
33
|
<v-text-field
|
|
32
34
|
v-model="integration.metadata.config.username"
|
|
@@ -47,6 +49,7 @@
|
|
|
47
49
|
></v-text-field>
|
|
48
50
|
|
|
49
51
|
<v-text-field
|
|
52
|
+
ref="aws-url"
|
|
50
53
|
v-model="integration.metadata.config.aws_secure_url"
|
|
51
54
|
:label="
|
|
52
55
|
$t(
|
|
@@ -58,6 +61,7 @@
|
|
|
58
61
|
'windward.integrations.components.integration.driver.atutor.aws_secure_url_hint'
|
|
59
62
|
)
|
|
60
63
|
"
|
|
64
|
+
:rules="urlValidation"
|
|
61
65
|
></v-text-field>
|
|
62
66
|
|
|
63
67
|
<v-switch
|
|
@@ -100,6 +104,17 @@ export default {
|
|
|
100
104
|
// integration: { metadata: {...} } The integration object to write to. Defined and loaded in ManageBase.vue
|
|
101
105
|
errorMessage: '',
|
|
102
106
|
testConnectionLoading: false,
|
|
107
|
+
urlValidation: [
|
|
108
|
+
(value) => {
|
|
109
|
+
if (value && value.slice(-1) === '/') {
|
|
110
|
+
return this.$t(
|
|
111
|
+
'windward.integrations.components.integration.driver.remove_slash'
|
|
112
|
+
)
|
|
113
|
+
} else {
|
|
114
|
+
return true
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
],
|
|
103
118
|
}
|
|
104
119
|
},
|
|
105
120
|
methods: {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-btn color="primary" outlined @click="onViewLog()">
|
|
4
|
+
<v-icon>mdi-note-search</v-icon>
|
|
5
|
+
</v-btn>
|
|
6
|
+
<Dialog v-model="logDialog" :trigger="false">
|
|
7
|
+
<template #title>{{
|
|
8
|
+
$t('windward.integrations.components.integration.job.view_log')
|
|
9
|
+
}}</template>
|
|
10
|
+
<template #form>
|
|
11
|
+
<v-progress-circular v-if="loading" size="128" indeterminate />
|
|
12
|
+
<div v-if="!loading">
|
|
13
|
+
<SearchField v-model="search" hide-filters></SearchField>
|
|
14
|
+
|
|
15
|
+
<v-alert
|
|
16
|
+
v-for="logItem in filteredLog"
|
|
17
|
+
:key="logItem.id"
|
|
18
|
+
:type="logItem.level"
|
|
19
|
+
>
|
|
20
|
+
{{ logItem.message }}
|
|
21
|
+
</v-alert>
|
|
22
|
+
|
|
23
|
+
<p v-if="filteredLog.length === 0">
|
|
24
|
+
{{
|
|
25
|
+
$t(
|
|
26
|
+
'windward.integrations.components.integration.job.log_no_results'
|
|
27
|
+
)
|
|
28
|
+
}}
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
</Dialog>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script>
|
|
37
|
+
import { mapGetters } from 'vuex'
|
|
38
|
+
import SearchField from '~/components/SearchField.vue'
|
|
39
|
+
import Organization from '../../models/Organization'
|
|
40
|
+
|
|
41
|
+
export default {
|
|
42
|
+
components: { SearchField },
|
|
43
|
+
name: 'IntegrationJobLog',
|
|
44
|
+
props: {
|
|
45
|
+
id: { type: String, required: true },
|
|
46
|
+
},
|
|
47
|
+
data() {
|
|
48
|
+
return {
|
|
49
|
+
search: {},
|
|
50
|
+
loading: true,
|
|
51
|
+
log: [],
|
|
52
|
+
logDialog: false,
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
computed: {
|
|
56
|
+
...mapGetters({
|
|
57
|
+
organization: 'organization/get',
|
|
58
|
+
}),
|
|
59
|
+
filteredLog() {
|
|
60
|
+
if (!this.search.term || this.search.term.length < 3) {
|
|
61
|
+
return this.log
|
|
62
|
+
}
|
|
63
|
+
const filtered = this.log.filter((item) => {
|
|
64
|
+
return item.message
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.includes(this.search.term.toLowerCase())
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
return filtered
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
methods: {
|
|
73
|
+
async onViewLog() {
|
|
74
|
+
this.logDialog = true
|
|
75
|
+
this.log = []
|
|
76
|
+
|
|
77
|
+
const job = await new Organization({
|
|
78
|
+
id: this.organization.id,
|
|
79
|
+
})
|
|
80
|
+
.integrationJobs()
|
|
81
|
+
.with('log')
|
|
82
|
+
.find(this.id)
|
|
83
|
+
|
|
84
|
+
this.log = job.log
|
|
85
|
+
this.loading = false
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<style scoped>
|
|
92
|
+
.col-progress {
|
|
93
|
+
min-width: 200px;
|
|
94
|
+
}
|
|
95
|
+
.col-details {
|
|
96
|
+
max-width: 100px;
|
|
97
|
+
}
|
|
98
|
+
</style>
|
|
@@ -42,7 +42,14 @@
|
|
|
42
42
|
<th class="text-left">
|
|
43
43
|
{{
|
|
44
44
|
$t(
|
|
45
|
-
'windward.integrations.components.integration.job.
|
|
45
|
+
'windward.integrations.components.integration.job.started'
|
|
46
|
+
)
|
|
47
|
+
}}
|
|
48
|
+
</th>
|
|
49
|
+
<th class="text-left">
|
|
50
|
+
{{
|
|
51
|
+
$t(
|
|
52
|
+
'windward.integrations.components.integration.job.view_log'
|
|
46
53
|
)
|
|
47
54
|
}}
|
|
48
55
|
</th>
|
|
@@ -119,6 +126,9 @@
|
|
|
119
126
|
{{ jobDetails(item) }}
|
|
120
127
|
</td>
|
|
121
128
|
<td>{{ $d(new Date(item.created_at), 'long') }}</td>
|
|
129
|
+
<td>
|
|
130
|
+
<JobLog :id="item.id"></JobLog>
|
|
131
|
+
</td>
|
|
122
132
|
</tr>
|
|
123
133
|
</tbody>
|
|
124
134
|
</template>
|
|
@@ -132,9 +142,11 @@ import _ from 'lodash'
|
|
|
132
142
|
import { mapGetters } from 'vuex'
|
|
133
143
|
import Vendor from '../../models/Vendor'
|
|
134
144
|
import Organization from '../../models/Organization'
|
|
145
|
+
import JobLog from './JobLog.vue'
|
|
135
146
|
|
|
136
147
|
export default {
|
|
137
148
|
name: 'IntegrationJobs',
|
|
149
|
+
components: { JobLog },
|
|
138
150
|
props: {
|
|
139
151
|
channel: { type: String, required: true },
|
|
140
152
|
event: { type: String, required: true },
|
|
@@ -145,6 +157,8 @@ export default {
|
|
|
145
157
|
vendors: [],
|
|
146
158
|
orgIntegrations: [],
|
|
147
159
|
jobs: [],
|
|
160
|
+
log: [],
|
|
161
|
+
logDialog: false,
|
|
148
162
|
}
|
|
149
163
|
},
|
|
150
164
|
async fetch() {
|
|
@@ -2,6 +2,8 @@ export default {
|
|
|
2
2
|
launch: 'Launch',
|
|
3
3
|
configure_warning:
|
|
4
4
|
'This block needs to be configured. Please select a LTI tool in the settings panel.',
|
|
5
|
+
no_access:
|
|
6
|
+
'You do not have permissions to launch this link. Please contact your system administrator.',
|
|
5
7
|
missing_tool: 'The tool id {0} is missing',
|
|
6
8
|
unknown_error: 'Something went wrong! Could not launch this link!',
|
|
7
9
|
link_disabled: 'Link disabled',
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export default {
|
|
2
|
-
name: 'Name',
|
|
3
|
-
description: 'Description',
|
|
4
2
|
key: 'Key',
|
|
5
3
|
secret: 'Secret',
|
|
6
4
|
parameter_name: 'Custom Parameter Name',
|
|
@@ -11,25 +9,6 @@ export default {
|
|
|
11
9
|
change_secret: 'Change Secret',
|
|
12
10
|
new_key: 'New Key',
|
|
13
11
|
new_secret: 'New Secret',
|
|
14
|
-
target: 'Target',
|
|
15
|
-
target_url: 'Target Url',
|
|
16
|
-
launch_url: 'Launch Url',
|
|
17
12
|
new: 'New LTI Link',
|
|
18
13
|
edit: 'Edit LTI Link',
|
|
19
|
-
enabled: 'Enabled',
|
|
20
|
-
provider: 'Provider',
|
|
21
|
-
consumer: 'Consumer',
|
|
22
|
-
provider_panel: 'Provider (Students incoming to Windward)',
|
|
23
|
-
consumer_panel: 'Consumer (Students outgoing from Windward)',
|
|
24
|
-
role_map_panel: 'Role map',
|
|
25
|
-
role_in_host: 'Role in Host LMS',
|
|
26
|
-
role_in_local: 'Role in Windward',
|
|
27
|
-
remote_role: 'Remote Role name',
|
|
28
|
-
map_to_role: 'Map to Windward Role',
|
|
29
|
-
delete_role_map_item: 'Delete role map item',
|
|
30
|
-
add_role_map_item: 'Add role map item',
|
|
31
|
-
role_map_instructions:
|
|
32
|
-
'Only add roles to the below map if the host LMS uses different role names that you want to elevated permissions. Eg "Mentor" that you want to elevate to "External Instructor"',
|
|
33
|
-
role_map_warning:
|
|
34
|
-
'Only add roles to the below map if the host LMS uses different role names that you want to elevated permissions. Eg "Mentor" that you want to elevate to "External Instructor"',
|
|
35
14
|
}
|