@windward/integrations 0.17.0 → 0.19.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/CHANGELOG.md +25 -0
- package/components/Content/Blocks/ExternalIntegration/LtiConsumer.vue +3 -3
- package/components/Content/Blocks/ExternalIntegration/ScormConsumer.vue +34 -0
- package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumer.vue +10 -8
- package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumers.vue +2 -2
- package/components/ExternalIntegration/Driver/Lti1p1/ManageProvider.vue +8 -5
- package/components/ExternalIntegration/Driver/Lti1p1/ManageProviders.vue +3 -2
- package/components/ExternalIntegration/Driver/Lti1p3/ManageConsumer.vue +8 -6
- package/components/ExternalIntegration/Driver/Lti1p3/ManageConsumers.vue +2 -2
- package/components/ExternalIntegration/Driver/Lti1p3/ManageProvider.vue +27 -5
- package/components/ExternalIntegration/Driver/Lti1p3/ManageProviders.vue +4 -3
- package/components/ExternalIntegration/Driver/Lti1p3/ViewConsumer.vue +6 -5
- package/components/ExternalIntegration/Driver/ManageScorm.vue +45 -0
- package/components/ExternalIntegration/Driver/Scorm/ManageConsumer.vue +76 -0
- package/components/ExternalIntegration/Driver/Scorm/ManageConsumers.vue +233 -0
- package/components/ExternalIntegration/Driver/Scorm/ManageProvider.vue +475 -0
- package/components/ExternalIntegration/Driver/Scorm/ManageProviders.vue +299 -0
- package/components/Integration/Driver/LoginSamlButton.vue +119 -0
- package/components/Integration/Driver/ManageSaml.vue +327 -0
- package/components/LLM/GenerateContent/BlockQuestionGenerateButton.vue +34 -3
- package/components/SecretField.vue +99 -19
- package/components/Settings/ExternalIntegration/LtiConsumerSettings.vue +2 -2
- package/components/Settings/ExternalIntegration/ManageCourseIntegrationSettings.vue +6 -6
- package/components/Settings/ExternalIntegration/ScormConsumerSettings.vue +42 -0
- package/config/integration.config.js +2 -0
- package/helpers/Driver/SamlSso.ts +12 -0
- package/helpers/ExternalIntegration/ScormHelper.ts +155 -0
- package/i18n/en-US/components/external_integration/driver/lti1p3.ts +4 -1
- package/i18n/en-US/components/external_integration/driver/scorm.ts +14 -0
- package/i18n/en-US/components/external_integration/index.ts +3 -1
- package/i18n/en-US/components/integration/driver.ts +23 -0
- package/i18n/en-US/components/llm/generate_content/generate_questions.ts +7 -0
- package/i18n/en-US/pages/course/external_integration/index.ts +1 -1
- package/i18n/en-US/pages/login/index.ts +4 -0
- package/i18n/en-US/pages/login/lti.ts +2 -0
- package/i18n/en-US/pages/login/saml.ts +7 -0
- package/i18n/en-US/pages/login/scorm.ts +28 -0
- package/i18n/en-US/shared/content_blocks.ts +1 -0
- package/i18n/en-US/shared/settings.ts +1 -0
- package/i18n/es-ES/components/external_integration/driver/lti1p3.ts +4 -1
- package/i18n/es-ES/components/external_integration/driver/scorm.ts +15 -0
- package/i18n/es-ES/components/external_integration/index.ts +3 -1
- package/i18n/es-ES/components/integration/driver.ts +23 -0
- package/i18n/es-ES/components/llm/generate_content/generate_questions.ts +7 -0
- package/i18n/es-ES/pages/course/external_integration/index.ts +1 -1
- package/i18n/es-ES/pages/login/index.ts +4 -0
- package/i18n/es-ES/pages/login/lti.ts +2 -0
- package/i18n/es-ES/pages/login/saml.ts +7 -0
- package/i18n/es-ES/pages/login/scorm.ts +29 -0
- package/i18n/es-ES/shared/content_blocks.ts +1 -0
- package/i18n/es-ES/shared/settings.ts +1 -0
- package/i18n/sv-SE/components/external_integration/driver/lti1p3.ts +4 -1
- package/i18n/sv-SE/components/external_integration/driver/scorm.ts +14 -0
- package/i18n/sv-SE/components/external_integration/index.ts +3 -1
- package/i18n/sv-SE/components/integration/driver.ts +23 -0
- package/i18n/sv-SE/components/llm/generate_content/generate_questions.ts +7 -0
- package/i18n/sv-SE/pages/course/external_integration/index.ts +1 -1
- package/i18n/sv-SE/pages/login/index.ts +4 -0
- package/i18n/sv-SE/pages/login/lti.ts +2 -0
- package/i18n/sv-SE/pages/login/saml.ts +7 -0
- package/i18n/sv-SE/pages/login/scorm.ts +29 -0
- package/i18n/sv-SE/shared/content_blocks.ts +2 -1
- package/i18n/sv-SE/shared/settings.ts +1 -0
- package/jest.config.js +3 -0
- package/models/Auth/Saml.ts +21 -0
- package/models/ExternalIntegration/{LtiConsumer.ts → Consumer.ts} +2 -2
- package/models/ExternalIntegration/{LtiProvider.ts → Provider.ts} +2 -2
- package/package.json +2 -1
- package/pages/course/externalIntegration/index.vue +4 -0
- package/pages/login/scorm/error.vue +102 -0
- package/pages/login/scorm/promptEmail.vue +180 -0
- package/plugin.js +128 -7
- package/test/Components/ExternalIntegration/ManageScorm.spec.js +19 -0
- package/test/Components/ExternalIntegration/Scorm/ManageConsumer.spec.js +19 -0
- package/test/Components/ExternalIntegration/Scorm/ManageConsumers.spec.js +19 -0
- package/test/Components/ExternalIntegration/Scorm/ManageProvider.spec.js +19 -0
- package/test/Components/ExternalIntegration/Scorm/ManageProviders.spec.js +19 -0
- package/test/__mocks__/componentsMock.js +81 -1
- package/test/mocks.js +12 -0
- package/test/setup.js +1 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<DialogBox
|
|
4
|
+
color="primary"
|
|
5
|
+
action-save
|
|
6
|
+
action-save-new
|
|
7
|
+
@click:save="onSaved"
|
|
8
|
+
@click:save-new="onSaved"
|
|
9
|
+
>
|
|
10
|
+
<template #title>{{
|
|
11
|
+
$t(
|
|
12
|
+
'windward.integrations.components.external_integration.driver.scorm.new'
|
|
13
|
+
)
|
|
14
|
+
}}</template>
|
|
15
|
+
<template #trigger>{{ $t('shared.forms.new') }}</template>
|
|
16
|
+
<template #form="{ on, attrs }"
|
|
17
|
+
><ManageProvider v-bind="attrs" v-on="on"></ManageProvider
|
|
18
|
+
></template>
|
|
19
|
+
</DialogBox>
|
|
20
|
+
|
|
21
|
+
<v-data-table
|
|
22
|
+
:headers="headers"
|
|
23
|
+
:items="providers"
|
|
24
|
+
:items-per-page="10"
|
|
25
|
+
class="elevation-1"
|
|
26
|
+
>
|
|
27
|
+
<template #[`item.target`]="{ item }">
|
|
28
|
+
<ProviderTargetViewer
|
|
29
|
+
class="field--target text-truncate"
|
|
30
|
+
:target="item.target"
|
|
31
|
+
></ProviderTargetViewer>
|
|
32
|
+
</template>
|
|
33
|
+
<template #[`item.download_package`]="{ item }">
|
|
34
|
+
<v-btn
|
|
35
|
+
color="primary"
|
|
36
|
+
:loading="isDownloading"
|
|
37
|
+
@click="onClickDownload(item)"
|
|
38
|
+
>{{
|
|
39
|
+
$t(
|
|
40
|
+
'windward.integrations.components.external_integration.driver.scorm.download_package'
|
|
41
|
+
)
|
|
42
|
+
}}</v-btn
|
|
43
|
+
>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<template #[`item.enabled`]="{ item }">
|
|
47
|
+
<v-icon :color="item.enabled ? 'success' : 'error'"
|
|
48
|
+
>{{ item.enabled ? 'mdi-check' : 'mdi-close' }}
|
|
49
|
+
</v-icon>
|
|
50
|
+
<span v-if="!item.enabled" class="sr-only">{{
|
|
51
|
+
$t('shared.forms.enabled')
|
|
52
|
+
}}</span>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<template #[`item.created_at`]="{ item }">
|
|
56
|
+
{{ $d(new Date(item.created_at), 'short') }}
|
|
57
|
+
</template>
|
|
58
|
+
<template #[`item.actions`]="{ index, item }">
|
|
59
|
+
<SpeedDial
|
|
60
|
+
direction="left"
|
|
61
|
+
color="primary"
|
|
62
|
+
transition="slide-x-reverse-transition"
|
|
63
|
+
>
|
|
64
|
+
<v-btn
|
|
65
|
+
color="error"
|
|
66
|
+
outlined
|
|
67
|
+
elevation="0"
|
|
68
|
+
class="outlined"
|
|
69
|
+
@click="onConfirmDelete(item)"
|
|
70
|
+
>
|
|
71
|
+
{{ $t('shared.forms.delete') }}
|
|
72
|
+
<span class="sr-only">{{
|
|
73
|
+
$t('shared.forms.delete')
|
|
74
|
+
}}</span>
|
|
75
|
+
</v-btn>
|
|
76
|
+
<DialogBox
|
|
77
|
+
color="primary"
|
|
78
|
+
outlined
|
|
79
|
+
:class-prop="'outlined'"
|
|
80
|
+
action-save
|
|
81
|
+
@click:save="onSaved"
|
|
82
|
+
>
|
|
83
|
+
<template #title>{{
|
|
84
|
+
$t(
|
|
85
|
+
'windward.integrations.components.external_integration.driver.scorm.edit'
|
|
86
|
+
)
|
|
87
|
+
}}</template>
|
|
88
|
+
<template #trigger>
|
|
89
|
+
{{
|
|
90
|
+
$t(
|
|
91
|
+
'windward.integrations.components.external_integration.driver.scorm.edit_display'
|
|
92
|
+
)
|
|
93
|
+
}}
|
|
94
|
+
<span class="sr-only">{{
|
|
95
|
+
$t(
|
|
96
|
+
'windward.integrations.components.external_integration.driver.scorm.edit'
|
|
97
|
+
)
|
|
98
|
+
}}</span>
|
|
99
|
+
</template>
|
|
100
|
+
<template #form="{ on, attrs }"
|
|
101
|
+
><ManageProvider
|
|
102
|
+
v-model="providers[index]"
|
|
103
|
+
v-bind="attrs"
|
|
104
|
+
v-on="on"
|
|
105
|
+
></ManageProvider
|
|
106
|
+
></template>
|
|
107
|
+
</DialogBox>
|
|
108
|
+
</SpeedDial>
|
|
109
|
+
</template>
|
|
110
|
+
</v-data-table>
|
|
111
|
+
</div>
|
|
112
|
+
</template>
|
|
113
|
+
|
|
114
|
+
<script>
|
|
115
|
+
import _ from 'lodash'
|
|
116
|
+
import { mapGetters } from 'vuex'
|
|
117
|
+
import Provider from '../../../../models/ExternalIntegration/Provider'
|
|
118
|
+
import SecretField from '../../../SecretField.vue'
|
|
119
|
+
import ProviderTargetViewer from '../../ProviderTargetViewer.vue'
|
|
120
|
+
import ManageProvider from './ManageProvider.vue'
|
|
121
|
+
import DialogBox from '~/components/Core/DialogBox.vue'
|
|
122
|
+
import Organization from '~/models/Organization'
|
|
123
|
+
import Course from '~/models/Course'
|
|
124
|
+
import SpeedDial from '~/components/Core/SpeedDial.vue'
|
|
125
|
+
|
|
126
|
+
export default {
|
|
127
|
+
name: 'ManageScormProvidersDriver',
|
|
128
|
+
components: {
|
|
129
|
+
SecretField,
|
|
130
|
+
DialogBox,
|
|
131
|
+
ManageProvider,
|
|
132
|
+
ProviderTargetViewer,
|
|
133
|
+
SpeedDial,
|
|
134
|
+
},
|
|
135
|
+
data() {
|
|
136
|
+
return {
|
|
137
|
+
providers: [],
|
|
138
|
+
headers: [
|
|
139
|
+
{
|
|
140
|
+
text: this.$t(
|
|
141
|
+
'windward.integrations.components.external_integration.target'
|
|
142
|
+
),
|
|
143
|
+
value: 'target',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
text: this.$t(
|
|
147
|
+
'windward.integrations.components.external_integration.version'
|
|
148
|
+
),
|
|
149
|
+
value: 'version',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
text: this.$t(
|
|
153
|
+
'windward.integrations.components.external_integration.driver.scorm.download_package'
|
|
154
|
+
),
|
|
155
|
+
value: 'download_package',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
text: this.$t('shared.forms.enabled'),
|
|
159
|
+
value: 'enabled',
|
|
160
|
+
},
|
|
161
|
+
{ text: this.$t('shared.forms.created'), value: 'created_at' },
|
|
162
|
+
{
|
|
163
|
+
text: this.$t('shared.forms.actions'),
|
|
164
|
+
value: 'actions',
|
|
165
|
+
sortable: false,
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
isDownloading: false,
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
async fetch() {
|
|
173
|
+
if (
|
|
174
|
+
!this.$PermissionService.userHasAccessTo(
|
|
175
|
+
'plugin.windward.integrations.course.externalIntegration',
|
|
176
|
+
'readable'
|
|
177
|
+
) ||
|
|
178
|
+
_.isEmpty(this.organization.id) ||
|
|
179
|
+
_.isEmpty(this.course.id)
|
|
180
|
+
) {
|
|
181
|
+
// Display an angry error that they can't view this driver
|
|
182
|
+
this.$dialog.error(this.$t('shared.error.description_401'), {
|
|
183
|
+
duration: null,
|
|
184
|
+
action: {
|
|
185
|
+
text: this.$t('shared.forms.close'),
|
|
186
|
+
onClick: (_e, toastObject) => {
|
|
187
|
+
toastObject.goAway(0)
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
if (_.isEmpty(this.organization.id) || _.isEmpty(this.course.id)) {
|
|
192
|
+
// eslint-disable-next-line no-console
|
|
193
|
+
console.error(
|
|
194
|
+
'Cannot load external integrations because organization or course is not set!'
|
|
195
|
+
)
|
|
196
|
+
} else {
|
|
197
|
+
// eslint-disable-next-line no-console
|
|
198
|
+
console.error(
|
|
199
|
+
'You do not have access to this external integration!'
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Return so we don't even attempt loading
|
|
204
|
+
return false
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await this.loadProviders()
|
|
208
|
+
},
|
|
209
|
+
computed: {
|
|
210
|
+
...mapGetters({
|
|
211
|
+
organization: 'organization/get',
|
|
212
|
+
course: 'course/get',
|
|
213
|
+
}),
|
|
214
|
+
},
|
|
215
|
+
methods: {
|
|
216
|
+
onSaved() {
|
|
217
|
+
this.loadProviders()
|
|
218
|
+
},
|
|
219
|
+
async loadProviders() {
|
|
220
|
+
this.providers = await new Provider()
|
|
221
|
+
.for(
|
|
222
|
+
new Organization({ id: this.organization.id }),
|
|
223
|
+
new Course({ id: this.course.id })
|
|
224
|
+
)
|
|
225
|
+
.where('type', 'scorm')
|
|
226
|
+
.get()
|
|
227
|
+
},
|
|
228
|
+
onConfirmDelete(provider) {
|
|
229
|
+
this.$dialog.show(this.$t('shared.forms.confirm_delete_text'), {
|
|
230
|
+
icon: 'mdi-help',
|
|
231
|
+
duration: null,
|
|
232
|
+
action: [
|
|
233
|
+
{
|
|
234
|
+
text: this.$t('shared.forms.cancel'),
|
|
235
|
+
onClick: (_e, toastObject) => {
|
|
236
|
+
toastObject.goAway(0)
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
text: this.$t('shared.forms.confirm'),
|
|
241
|
+
// router navigation
|
|
242
|
+
onClick: (_e, toastObject) => {
|
|
243
|
+
this.deleteProvider(provider)
|
|
244
|
+
toastObject.goAway(0)
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
})
|
|
249
|
+
},
|
|
250
|
+
async deleteProvider(provider) {
|
|
251
|
+
await provider.delete()
|
|
252
|
+
this.$dialog.success(this.$t('shared.response.deleted'))
|
|
253
|
+
// Reload providers now that we deleted one
|
|
254
|
+
this.loadProviders()
|
|
255
|
+
},
|
|
256
|
+
async onClickDownload(provider) {
|
|
257
|
+
this.isDownloading = true
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const result = await Provider.config({
|
|
261
|
+
method: 'GET',
|
|
262
|
+
})
|
|
263
|
+
.custom(
|
|
264
|
+
new Organization(this.organization),
|
|
265
|
+
new Course(this.course),
|
|
266
|
+
new Provider(provider),
|
|
267
|
+
'download'
|
|
268
|
+
)
|
|
269
|
+
.file()
|
|
270
|
+
|
|
271
|
+
let filename = `${this.organization.name}_${this.course.name}_${provider.type}_${provider.version}_provider_${provider.id}`
|
|
272
|
+
// Replace all non letter/numbers with _
|
|
273
|
+
filename = filename.replaceAll(/[^a-zA-Z0-9\_\-]/gi, '_')
|
|
274
|
+
// Replace all double+ underscores with single
|
|
275
|
+
filename = filename.replaceAll(/_+/gi, '_')
|
|
276
|
+
filename += '.zip'
|
|
277
|
+
|
|
278
|
+
const url = URL.createObjectURL(result)
|
|
279
|
+
const link = document.createElement('a')
|
|
280
|
+
link.href = url
|
|
281
|
+
link.download = filename
|
|
282
|
+
link.click()
|
|
283
|
+
URL.revokeObjectURL(url)
|
|
284
|
+
} catch (e) {
|
|
285
|
+
console.error('Error getting download', e)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
this.isDownloading = false
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
}
|
|
292
|
+
</script>
|
|
293
|
+
|
|
294
|
+
<style scoped>
|
|
295
|
+
.field--target {
|
|
296
|
+
max-width: 15em;
|
|
297
|
+
display: inline-block;
|
|
298
|
+
}
|
|
299
|
+
</style>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="samlEnabled">
|
|
3
|
+
<v-btn
|
|
4
|
+
color="primary"
|
|
5
|
+
elevation="0"
|
|
6
|
+
large
|
|
7
|
+
block
|
|
8
|
+
@click="loginWithSaml"
|
|
9
|
+
:loading="ssoLoading || loading"
|
|
10
|
+
:disabled="loading"
|
|
11
|
+
>
|
|
12
|
+
<v-icon class="pr-2">{{ samlButtonIcon }}</v-icon>
|
|
13
|
+
{{ samlButtonLabel }}
|
|
14
|
+
</v-btn>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import Saml from '../../../models/Auth/Saml'
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
name: 'LoginSamlButton',
|
|
23
|
+
props: {
|
|
24
|
+
loading: {
|
|
25
|
+
type: Boolean,
|
|
26
|
+
default: false,
|
|
27
|
+
},
|
|
28
|
+
valid: {
|
|
29
|
+
type: Boolean,
|
|
30
|
+
default: true,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
data() {
|
|
34
|
+
return {
|
|
35
|
+
ssoLoading: false,
|
|
36
|
+
samlEnabled: false,
|
|
37
|
+
samlButtonLabel: 'Sign in with SSO',
|
|
38
|
+
samlButtonIcon: 'mdi-login',
|
|
39
|
+
organizationId: null,
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
mounted() {
|
|
43
|
+
this.checkSamlStatus()
|
|
44
|
+
},
|
|
45
|
+
methods: {
|
|
46
|
+
async checkSamlStatus() {
|
|
47
|
+
try {
|
|
48
|
+
// Get organization from store or context
|
|
49
|
+
const organization = this.$store.getters['organization/get']
|
|
50
|
+
|
|
51
|
+
// If no organization in store, try to use the hostname
|
|
52
|
+
let orgIdentifier = null
|
|
53
|
+
|
|
54
|
+
if (organization && organization.id) {
|
|
55
|
+
// Prefer using the organization ID if available
|
|
56
|
+
orgIdentifier = organization.id
|
|
57
|
+
} else {
|
|
58
|
+
// Fallback to using the hostname (full base_url)
|
|
59
|
+
orgIdentifier = window.location.hostname
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!orgIdentifier) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if SAML is enabled for this organization
|
|
67
|
+
const response = await Saml.custom('saml/status').get()
|
|
68
|
+
|
|
69
|
+
// The API returns an array, so we need to get the first element
|
|
70
|
+
const samlData = Array.isArray(response)
|
|
71
|
+
? response[0]
|
|
72
|
+
: response
|
|
73
|
+
|
|
74
|
+
if (samlData && samlData.enabled && samlData.configured) {
|
|
75
|
+
this.samlEnabled = true
|
|
76
|
+
this.organizationId = samlData.organization_id
|
|
77
|
+
|
|
78
|
+
// Use button settings from the integration configuration
|
|
79
|
+
this.samlButtonLabel =
|
|
80
|
+
samlData.button_label || 'Sign in with SSO'
|
|
81
|
+
this.samlButtonIcon = samlData.button_icon || 'mdi-login'
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// SAML not configured or error checking status
|
|
85
|
+
// Silently fail - SSO button won't show
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
async loginWithSaml() {
|
|
89
|
+
this.ssoLoading = true
|
|
90
|
+
try {
|
|
91
|
+
// Get the SSO URL from the backend
|
|
92
|
+
const response = await Saml.custom('users/saml-login').get()
|
|
93
|
+
|
|
94
|
+
// The API returns an array, so we need to get the first element
|
|
95
|
+
const loginData = Array.isArray(response)
|
|
96
|
+
? response[0]
|
|
97
|
+
: response
|
|
98
|
+
|
|
99
|
+
if (loginData && loginData.sso_url) {
|
|
100
|
+
// Redirect to the IdP for authentication
|
|
101
|
+
window.location.href = loginData.sso_url
|
|
102
|
+
} else {
|
|
103
|
+
this.$dialog.error('Unable to initiate SSO login')
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
// Only log error in development mode
|
|
107
|
+
if (process.env.NODE_ENV === 'development') {
|
|
108
|
+
console.error('SSO login failed:', error)
|
|
109
|
+
}
|
|
110
|
+
this.$dialog.error(
|
|
111
|
+
'SSO login failed. Please try again or use standard login.'
|
|
112
|
+
)
|
|
113
|
+
} finally {
|
|
114
|
+
this.ssoLoading = false
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
</script>
|