Original file line numberDiff line numberDiff line change
Expand Up@@ -13,13 +13,17 @@ import ExcelConnectionProperties from "./cdata/ExcelConnectionProperties.vue";
import GithubConnectionProperties from "./cdata/GithubConnectionProperties.vue";
import DocusignConnectionProperties from "./cdata/DocusignConnectionProperties.vue";
import GmailConnectionProperties from "./cdata/GmailConnectionProperties.vue";
import BamboohrConnectionProperties from "./cdata/BamboohrConnectionProperties.vue";
import SapHanaConnectionProperties from "./cdata/SapHanaConnectionProperties.vue";

export default {
components: {
ExcelConnectionProperties,
GithubConnectionProperties,
DocusignConnectionProperties,
GmailConnectionProperties,
BamboohrConnectionProperties,
SapHanaConnectionProperties,
},
props: {
formData: {
Expand All@@ -38,6 +42,8 @@ export default {
"cdata.github": "github-connection-properties",
"cdata.docusign": "docusign-connection-properties",
"cdata.gmail": "gmail-connection-properties",
"cdata.BambooHR": "bamboohr-connection-properties",
"cdata.saphana": "sap-hana-connection-properties",
},
};
},
Expand Down
242 changes: 80 additions & 162 deletions resources/js/admin/settings/components/SettingDriverAuthorization.vue
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
<template>
<div>
<!-- Authorize badge -->
<div v-if="hasAuthorizedBadge">
<b-badge
pill
:variant="isAuthorized ? 'success' : 'warning'"
>
<span v-if="isAuthorized">{{ $t('Authorized') }}</span>
<span v-else>{{ $t('Not Authorized') }}</span>
<span v-if="isAuthorized">{{ $t("Authorized") }}</span>
<span v-else>{{ $t("Not Authorized") }}</span>
</b-badge>
</div>
<div v-else>
Empty
{{ $t("Empty") }}
</div>

<!-- Connection properties modal -->
<b-modal
v-model="showModal"
class="setting-object-modal"
Expand All@@ -24,110 +27,30 @@
class="d-block"
>
<div>
<h5
v-if="setting.name"
class="mb-0"
>
{{ $t(setting.name) }}
</h5>
<h5
v-else
class="mb-0"
>
{{ setting.key }}
<h5 class="mb-0">
<span v-if="setting.name">{{ $t(setting.name) }}</span>
<span v-else>{{ $t(setting.key) }}</span>
</h5>
<small class="form-text text-muted">{{ $t('Configure the driver connection properties.') }}</small>
<small class="form-text text-muted">
{{ $t("Configure the driver connection properties.") }}
</small>
</div>
<button
type="button"
:aria-label="$t('Close')"
class="close"
:aria-label="$t('Close')"
@click="onCancel"
>
×
&times;
</button>
</template>
<div>
<b-form-group
required
:label="$t('Client ID')"
:description="formDescription('The client ID assigned when you register your application.', 'client_id', errors)"
:invalid-feedback="errorMessage('client_id', errors)"
:state="errorState('client_id', errors)"
>
<b-form-input
v-model="formData.client_id"
required
autofocus
autocomplete="off"
:state="errorState('client_id', errors)"
name="client_id"
data-cy="client_id"
/>
</b-form-group>

<b-form-group
required
:label="$t('Client Secret')"
:description="formDescription('The client secret assigned when you register your application.', 'client_secret', errors)"
:invalid-feedback="errorMessage('client_secret', errors)"
:state="errorState('client_secret', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.client_secret"
required
autofocus
autocomplete="off"
trim
:type="type"
:state="errorState('client_secret', errors)"
name="client_secret"
data-cy="client_secret"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Toggle Show Password')"
variant="secondary"
@click="togglePassword"
>
<i
class="fas"
:class="icon"
/>
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>

<b-form-group
required
:label="$t('Redirect URL')"
:description="formDescription('This value must match the callback URL you specify in your app settings.', 'callback_url', errors)"
:invalid-feedback="errorMessage('callback_url', errors)"
:state="errorState('callback_url', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.callback_url"
autofocus
readonly
autocomplete="off"
:state="errorState('callback_url', errors)"
name="callback_url"
data-cy="callback_url"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Copy')"
variant="secondary"
@click="onCopy"
>
<i class="fas fa-copy" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>
<component
:is="authSchemeToComponent(setting.config?.AuthScheme)"
:form-data="formData"
:auth-scheme="setting.config?.AuthScheme"
@updateFormData="updateFormData"
/>

<additional-driver-connection-properties
:driver-key="setting?.key"
Expand All@@ -145,20 +68,20 @@
data-cy="cancel-button"
@click="onCancel"
>
{{ $t('Cancel') }}
{{ $t("Cancel") }}
</button>
<button
type="button"
class="btn btn-secondary ml-3"
data-cy="authorize-button"
:disabled="isButtonDisabled"
@click="onSave"
>
{{ $t('Authorize') }}
{{ $t("Authorize") }}
</button>
</div>
</b-modal>

<!-- Authorizing modal -->
<b-modal
v-model="showAuthorizingModal"
class="setting-object-modal"
Expand All@@ -169,26 +92,33 @@
no-fade
>
<div class="text-center">
<h3>{{ $t('Connecting Driver') }}</h3>
<h3>{{ $t("Connecting Driver") }}</h3>
<i class="fas fa-circle-notch fa-spin fa-3x p-0 text-primary" />
</div>
</b-modal>
</div>
</template>

<script>
// eslint-disable-next-line import/no-unresolved
import { FormErrorsMixin, Required } from "SharedComponents";
import settingMixin from "../mixins/setting";
import AdditionalDriverConnectionProperties from "./AdditionalDriverConnectionProperties.vue";
import OauthConnectionProperties from "./cdata/OauthConnectionProperties.vue";
import NoneConnectionProperties from "./cdata/NoneConnectionProperties.vue";
import PasswordConnectionProperties from "./cdata/PasswordConnectionProperties.vue";

export default {
components: { AdditionalDriverConnectionProperties },
components: {
AdditionalDriverConnectionProperties,
OauthConnectionProperties,
NoneConnectionProperties,
PasswordConnectionProperties,
},
mixins: [settingMixin, FormErrorsMixin, Required],
props: {
setting: {
type: [Object, null],
default: null,
default: () => ({}),
},
value: {
type: Object,
Expand All@@ -198,19 +128,19 @@ export default {
data() {
return {
input: "",
formData: {
client_id: "",
client_secret: "",
callback_url: "",
},
formData: {},
selected: null,
showModal: false,
showAuthorizingModal: false,
transformed: null,
errors: {},
isInvalid: true,
type: "password",
resetData: true,
componentsMap: {
OAuth: "oauth-connection-properties",
None: "none-connection-properties",
Password: "password-connection-properties",
Basic: "password-connection-properties",
},
};
},
computed: {
Expand All@@ -227,57 +157,18 @@ export default {
}
return false;
},
changed() {
return JSON.stringify(this.formData) !== JSON.stringify(this.transformed);
},
icon() {
if (this.type === "password") {
return "fa-eye";
}
return "fa-eye-slash";
},
isButtonDisabled() {
return this.isInvalid || (this.isAuthorized && !this.changed);
},
},
watch: {
formData: {
handler() {
this.isInvalid = this.validateData();
},
deep: true,
},
},
mounted() {
if (this.value === null) {
this.resetFormData();
} else {
this.formData = this.value;
}
this.isInvalid = this.validateData();
this.transformed = this.copy(this.formData);
},
methods: {
onCopy() {
navigator.clipboard.writeText(this.formData.callback_url).then(() => {
ProcessMaker.alert(this.$t("The setting was copied to your clipboard."), "success");
}, () => {
ProcessMaker.alert(this.$t("The setting was not copied to your clipboard."), "danger");
});
},
togglePassword() {
if (this.type === "text") {
this.type = "password";
} else {
this.type = "text";
}
},
validateData() {
// Check if client_id and client_secret are empty
const clientIdEmpty = _.isEmpty(this.formData.client_id);
const clientSecretEmpty = _.isEmpty(this.formData.client_secret);

return _.isEmpty(this.formData) || clientIdEmpty || clientSecretEmpty;
authSchemeToComponent(scheme) {
return this.componentsMap[scheme] || null;
},
onCancel() {
this.showModal = false;
Expand All@@ -294,14 +185,48 @@ export default {
onModalHidden() {
this.resetFormData();
},
generateCallbackUrl(item) {
if (item.config.AuthScheme === "OAuth") {
const name = item.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
}
},
authorizeConnection() {
this.showAuthorizingModal = true;
this.showModal = false;
this.resetData = false;
ProcessMaker.apiClient.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)

if (this.setting.config.AuthScheme === "OAuth") {
this.authorizeOAuthConnection();
} else {
this.authorizeNoneConnection();
}
},
authorizeOAuthConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)
.then((response) => {
window.location = response.data?.url;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
})
.finally(() => {
this.showModal = true;
this.showAuthorizingModal = false;
});
},
authorizeNoneConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/authorize-driver`, this.formData)
.then((response) => {
window.location = response.data.url;
this.showModal = false;
this.showAuthorizingModal = true;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
Expand All@@ -310,18 +235,11 @@ export default {
});
},
onSave() {
const driver = this.setting.key.split("cdata.")[1];

this.formData.driver = driver;
this.formData.name = this.setting.config?.name;
this.formData.driver = this.setting.config?.driver;
this.transformed = { ...this.formData };
this.authorizeConnection();
},
generateCallbackUrl(data) {
const name = data.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
},
resetFormData() {
if (this.resetData) {
this.formData = {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,13 +13,17 @@ import ExcelConnectionProperties from "./cdata/ExcelConnectionProperties.vue";
import GithubConnectionProperties from "./cdata/GithubConnectionProperties.vue";
import DocusignConnectionProperties from "./cdata/DocusignConnectionProperties.vue";
import GmailConnectionProperties from "./cdata/GmailConnectionProperties.vue";
import BamboohrConnectionProperties from "./cdata/BamboohrConnectionProperties.vue";
import SapHanaConnectionProperties from "./cdata/SapHanaConnectionProperties.vue";

export default {
components: {
ExcelConnectionProperties,
GithubConnectionProperties,
DocusignConnectionProperties,
GmailConnectionProperties,
BamboohrConnectionProperties,
SapHanaConnectionProperties,
},
props: {
formData: {
Expand All@@ -38,6 +42,8 @@ export default {
"cdata.github": "github-connection-properties",
"cdata.docusign": "docusign-connection-properties",
"cdata.gmail": "gmail-connection-properties",
"cdata.BambooHR": "bamboohr-connection-properties",
"cdata.saphana": "sap-hana-connection-properties",
},
};
},
Expand Down
242 changes: 80 additions & 162 deletions resources/js/admin/settings/components/SettingDriverAuthorization.vue
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
<template>
<div>
<!-- Authorize badge -->
<div v-if="hasAuthorizedBadge">
<b-badge
pill
:variant="isAuthorized ? 'success' : 'warning'"
>
<span v-if="isAuthorized">{{ $t('Authorized') }}</span>
<span v-else>{{ $t('Not Authorized') }}</span>
<span v-if="isAuthorized">{{ $t("Authorized") }}</span>
<span v-else>{{ $t("Not Authorized") }}</span>
</b-badge>
</div>
<div v-else>
Empty
{{ $t("Empty") }}
</div>

<!-- Connection properties modal -->
<b-modal
v-model="showModal"
class="setting-object-modal"
Expand All@@ -24,110 +27,30 @@
class="d-block"
>
<div>
<h5
v-if="setting.name"
class="mb-0"
>
{{ $t(setting.name) }}
</h5>
<h5
v-else
class="mb-0"
>
{{ setting.key }}
<h5 class="mb-0">
<span v-if="setting.name">{{ $t(setting.name) }}</span>
<span v-else>{{ $t(setting.key) }}</span>
</h5>
<small class="form-text text-muted">{{ $t('Configure the driver connection properties.') }}</small>
<small class="form-text text-muted">
{{ $t("Configure the driver connection properties.") }}
</small>
</div>
<button
type="button"
:aria-label="$t('Close')"
class="close"
:aria-label="$t('Close')"
@click="onCancel"
>
×
&times;
</button>
</template>
<div>
<b-form-group
required
:label="$t('Client ID')"
:description="formDescription('The client ID assigned when you register your application.', 'client_id', errors)"
:invalid-feedback="errorMessage('client_id', errors)"
:state="errorState('client_id', errors)"
>
<b-form-input
v-model="formData.client_id"
required
autofocus
autocomplete="off"
:state="errorState('client_id', errors)"
name="client_id"
data-cy="client_id"
/>
</b-form-group>

<b-form-group
required
:label="$t('Client Secret')"
:description="formDescription('The client secret assigned when you register your application.', 'client_secret', errors)"
:invalid-feedback="errorMessage('client_secret', errors)"
:state="errorState('client_secret', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.client_secret"
required
autofocus
autocomplete="off"
trim
:type="type"
:state="errorState('client_secret', errors)"
name="client_secret"
data-cy="client_secret"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Toggle Show Password')"
variant="secondary"
@click="togglePassword"
>
<i
class="fas"
:class="icon"
/>
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>

<b-form-group
required
:label="$t('Redirect URL')"
:description="formDescription('This value must match the callback URL you specify in your app settings.', 'callback_url', errors)"
:invalid-feedback="errorMessage('callback_url', errors)"
:state="errorState('callback_url', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.callback_url"
autofocus
readonly
autocomplete="off"
:state="errorState('callback_url', errors)"
name="callback_url"
data-cy="callback_url"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Copy')"
variant="secondary"
@click="onCopy"
>
<i class="fas fa-copy" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>
<component
:is="authSchemeToComponent(setting.config?.AuthScheme)"
:form-data="formData"
:auth-scheme="setting.config?.AuthScheme"
@updateFormData="updateFormData"
/>

<additional-driver-connection-properties
:driver-key="setting?.key"
Expand All@@ -145,20 +68,20 @@
data-cy="cancel-button"
@click="onCancel"
>
{{ $t('Cancel') }}
{{ $t("Cancel") }}
</button>
<button
type="button"
class="btn btn-secondary ml-3"
data-cy="authorize-button"
:disabled="isButtonDisabled"
@click="onSave"
>
{{ $t('Authorize') }}
{{ $t("Authorize") }}
</button>
</div>
</b-modal>

<!-- Authorizing modal -->
<b-modal
v-model="showAuthorizingModal"
class="setting-object-modal"
Expand All@@ -169,26 +92,33 @@
no-fade
>
<div class="text-center">
<h3>{{ $t('Connecting Driver') }}</h3>
<h3>{{ $t("Connecting Driver") }}</h3>
<i class="fas fa-circle-notch fa-spin fa-3x p-0 text-primary" />
</div>
</b-modal>
</div>
</template>

<script>
// eslint-disable-next-line import/no-unresolved
import { FormErrorsMixin, Required } from "SharedComponents";
import settingMixin from "../mixins/setting";
import AdditionalDriverConnectionProperties from "./AdditionalDriverConnectionProperties.vue";
import OauthConnectionProperties from "./cdata/OauthConnectionProperties.vue";
import NoneConnectionProperties from "./cdata/NoneConnectionProperties.vue";
import PasswordConnectionProperties from "./cdata/PasswordConnectionProperties.vue";

export default {
components: { AdditionalDriverConnectionProperties },
components: {
AdditionalDriverConnectionProperties,
OauthConnectionProperties,
NoneConnectionProperties,
PasswordConnectionProperties,
},
mixins: [settingMixin, FormErrorsMixin, Required],
props: {
setting: {
type: [Object, null],
default: null,
default: () => ({}),
},
value: {
type: Object,
Expand All@@ -198,19 +128,19 @@ export default {
data() {
return {
input: "",
formData: {
client_id: "",
client_secret: "",
callback_url: "",
},
formData: {},
selected: null,
showModal: false,
showAuthorizingModal: false,
transformed: null,
errors: {},
isInvalid: true,
type: "password",
resetData: true,
componentsMap: {
OAuth: "oauth-connection-properties",
None: "none-connection-properties",
Password: "password-connection-properties",
Basic: "password-connection-properties",
},
};
},
computed: {
Expand All@@ -227,57 +157,18 @@ export default {
}
return false;
},
changed() {
return JSON.stringify(this.formData) !== JSON.stringify(this.transformed);
},
icon() {
if (this.type === "password") {
return "fa-eye";
}
return "fa-eye-slash";
},
isButtonDisabled() {
return this.isInvalid || (this.isAuthorized && !this.changed);
},
},
watch: {
formData: {
handler() {
this.isInvalid = this.validateData();
},
deep: true,
},
},
mounted() {
if (this.value === null) {
this.resetFormData();
} else {
this.formData = this.value;
}
this.isInvalid = this.validateData();
this.transformed = this.copy(this.formData);
},
methods: {
onCopy() {
navigator.clipboard.writeText(this.formData.callback_url).then(() => {
ProcessMaker.alert(this.$t("The setting was copied to your clipboard."), "success");
}, () => {
ProcessMaker.alert(this.$t("The setting was not copied to your clipboard."), "danger");
});
},
togglePassword() {
if (this.type === "text") {
this.type = "password";
} else {
this.type = "text";
}
},
validateData() {
// Check if client_id and client_secret are empty
const clientIdEmpty = _.isEmpty(this.formData.client_id);
const clientSecretEmpty = _.isEmpty(this.formData.client_secret);

return _.isEmpty(this.formData) || clientIdEmpty || clientSecretEmpty;
authSchemeToComponent(scheme) {
return this.componentsMap[scheme] || null;
},
onCancel() {
this.showModal = false;
Expand All@@ -294,14 +185,48 @@ export default {
onModalHidden() {
this.resetFormData();
},
generateCallbackUrl(item) {
if (item.config.AuthScheme === "OAuth") {
const name = item.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
}
},
authorizeConnection() {
this.showAuthorizingModal = true;
this.showModal = false;
this.resetData = false;
ProcessMaker.apiClient.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)

if (this.setting.config.AuthScheme === "OAuth") {
this.authorizeOAuthConnection();
} else {
this.authorizeNoneConnection();
}
},
authorizeOAuthConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)
.then((response) => {
window.location = response.data?.url;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
})
.finally(() => {
this.showModal = true;
this.showAuthorizingModal = false;
});
},
authorizeNoneConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/authorize-driver`, this.formData)
.then((response) => {
window.location = response.data.url;
this.showModal = false;
this.showAuthorizingModal = true;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
Expand All@@ -310,18 +235,11 @@ export default {
});
},
onSave() {
const driver = this.setting.key.split("cdata.")[1];

this.formData.driver = driver;
this.formData.name = this.setting.config?.name;
this.formData.driver = this.setting.config?.driver;
this.transformed = { ...this.formData };
this.authorizeConnection();
},
generateCallbackUrl(data) {
const name = data.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
},
resetFormData() {
if (this.resetData) {
this.formData = {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,13 +13,17 @@ import ExcelConnectionProperties from "./cdata/ExcelConnectionProperties.vue";
import GithubConnectionProperties from "./cdata/GithubConnectionProperties.vue";
import DocusignConnectionProperties from "./cdata/DocusignConnectionProperties.vue";
import GmailConnectionProperties from "./cdata/GmailConnectionProperties.vue";
import BamboohrConnectionProperties from "./cdata/BamboohrConnectionProperties.vue";
import SapHanaConnectionProperties from "./cdata/SapHanaConnectionProperties.vue";

export default {
components: {
ExcelConnectionProperties,
GithubConnectionProperties,
DocusignConnectionProperties,
GmailConnectionProperties,
BamboohrConnectionProperties,
SapHanaConnectionProperties,
},
props: {
formData: {
Expand All@@ -38,6 +42,8 @@ export default {
"cdata.github": "github-connection-properties",
"cdata.docusign": "docusign-connection-properties",
"cdata.gmail": "gmail-connection-properties",
"cdata.BambooHR": "bamboohr-connection-properties",
"cdata.saphana": "sap-hana-connection-properties",
},
};
},
Expand Down
242 changes: 80 additions & 162 deletions resources/js/admin/settings/components/SettingDriverAuthorization.vue
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
<template>
<div>
<!-- Authorize badge -->
<div v-if="hasAuthorizedBadge">
<b-badge
pill
:variant="isAuthorized ? 'success' : 'warning'"
>
<span v-if="isAuthorized">{{ $t('Authorized') }}</span>
<span v-else>{{ $t('Not Authorized') }}</span>
<span v-if="isAuthorized">{{ $t("Authorized") }}</span>
<span v-else>{{ $t("Not Authorized") }}</span>
</b-badge>
</div>
<div v-else>
Empty
{{ $t("Empty") }}
</div>

<!-- Connection properties modal -->
<b-modal
v-model="showModal"
class="setting-object-modal"
Expand All@@ -24,110 +27,30 @@
class="d-block"
>
<div>
<h5
v-if="setting.name"
class="mb-0"
>
{{ $t(setting.name) }}
</h5>
<h5
v-else
class="mb-0"
>
{{ setting.key }}
<h5 class="mb-0">
<span v-if="setting.name">{{ $t(setting.name) }}</span>
<span v-else>{{ $t(setting.key) }}</span>
</h5>
<small class="form-text text-muted">{{ $t('Configure the driver connection properties.') }}</small>
<small class="form-text text-muted">
{{ $t("Configure the driver connection properties.") }}
</small>
</div>
<button
type="button"
:aria-label="$t('Close')"
class="close"
:aria-label="$t('Close')"
@click="onCancel"
>
×
&times;
</button>
</template>
<div>
<b-form-group
required
:label="$t('Client ID')"
:description="formDescription('The client ID assigned when you register your application.', 'client_id', errors)"
:invalid-feedback="errorMessage('client_id', errors)"
:state="errorState('client_id', errors)"
>
<b-form-input
v-model="formData.client_id"
required
autofocus
autocomplete="off"
:state="errorState('client_id', errors)"
name="client_id"
data-cy="client_id"
/>
</b-form-group>

<b-form-group
required
:label="$t('Client Secret')"
:description="formDescription('The client secret assigned when you register your application.', 'client_secret', errors)"
:invalid-feedback="errorMessage('client_secret', errors)"
:state="errorState('client_secret', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.client_secret"
required
autofocus
autocomplete="off"
trim
:type="type"
:state="errorState('client_secret', errors)"
name="client_secret"
data-cy="client_secret"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Toggle Show Password')"
variant="secondary"
@click="togglePassword"
>
<i
class="fas"
:class="icon"
/>
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>

<b-form-group
required
:label="$t('Redirect URL')"
:description="formDescription('This value must match the callback URL you specify in your app settings.', 'callback_url', errors)"
:invalid-feedback="errorMessage('callback_url', errors)"
:state="errorState('callback_url', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.callback_url"
autofocus
readonly
autocomplete="off"
:state="errorState('callback_url', errors)"
name="callback_url"
data-cy="callback_url"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Copy')"
variant="secondary"
@click="onCopy"
>
<i class="fas fa-copy" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>
<component
:is="authSchemeToComponent(setting.config?.AuthScheme)"
:form-data="formData"
:auth-scheme="setting.config?.AuthScheme"
@updateFormData="updateFormData"
/>

<additional-driver-connection-properties
:driver-key="setting?.key"
Expand All@@ -145,20 +68,20 @@
data-cy="cancel-button"
@click="onCancel"
>
{{ $t('Cancel') }}
{{ $t("Cancel") }}
</button>
<button
type="button"
class="btn btn-secondary ml-3"
data-cy="authorize-button"
:disabled="isButtonDisabled"
@click="onSave"
>
{{ $t('Authorize') }}
{{ $t("Authorize") }}
</button>
</div>
</b-modal>

<!-- Authorizing modal -->
<b-modal
v-model="showAuthorizingModal"
class="setting-object-modal"
Expand All@@ -169,26 +92,33 @@
no-fade
>
<div class="text-center">
<h3>{{ $t('Connecting Driver') }}</h3>
<h3>{{ $t("Connecting Driver") }}</h3>
<i class="fas fa-circle-notch fa-spin fa-3x p-0 text-primary" />
</div>
</b-modal>
</div>
</template>

<script>
// eslint-disable-next-line import/no-unresolved
import { FormErrorsMixin, Required } from "SharedComponents";
import settingMixin from "../mixins/setting";
import AdditionalDriverConnectionProperties from "./AdditionalDriverConnectionProperties.vue";
import OauthConnectionProperties from "./cdata/OauthConnectionProperties.vue";
import NoneConnectionProperties from "./cdata/NoneConnectionProperties.vue";
import PasswordConnectionProperties from "./cdata/PasswordConnectionProperties.vue";

export default {
components: { AdditionalDriverConnectionProperties },
components: {
AdditionalDriverConnectionProperties,
OauthConnectionProperties,
NoneConnectionProperties,
PasswordConnectionProperties,
},
mixins: [settingMixin, FormErrorsMixin, Required],
props: {
setting: {
type: [Object, null],
default: null,
default: () => ({}),
},
value: {
type: Object,
Expand All@@ -198,19 +128,19 @@ export default {
data() {
return {
input: "",
formData: {
client_id: "",
client_secret: "",
callback_url: "",
},
formData: {},
selected: null,
showModal: false,
showAuthorizingModal: false,
transformed: null,
errors: {},
isInvalid: true,
type: "password",
resetData: true,
componentsMap: {
OAuth: "oauth-connection-properties",
None: "none-connection-properties",
Password: "password-connection-properties",
Basic: "password-connection-properties",
},
};
},
computed: {
Expand All@@ -227,57 +157,18 @@ export default {
}
return false;
},
changed() {
return JSON.stringify(this.formData) !== JSON.stringify(this.transformed);
},
icon() {
if (this.type === "password") {
return "fa-eye";
}
return "fa-eye-slash";
},
isButtonDisabled() {
return this.isInvalid || (this.isAuthorized && !this.changed);
},
},
watch: {
formData: {
handler() {
this.isInvalid = this.validateData();
},
deep: true,
},
},
mounted() {
if (this.value === null) {
this.resetFormData();
} else {
this.formData = this.value;
}
this.isInvalid = this.validateData();
this.transformed = this.copy(this.formData);
},
methods: {
onCopy() {
navigator.clipboard.writeText(this.formData.callback_url).then(() => {
ProcessMaker.alert(this.$t("The setting was copied to your clipboard."), "success");
}, () => {
ProcessMaker.alert(this.$t("The setting was not copied to your clipboard."), "danger");
});
},
togglePassword() {
if (this.type === "text") {
this.type = "password";
} else {
this.type = "text";
}
},
validateData() {
// Check if client_id and client_secret are empty
const clientIdEmpty = _.isEmpty(this.formData.client_id);
const clientSecretEmpty = _.isEmpty(this.formData.client_secret);

return _.isEmpty(this.formData) || clientIdEmpty || clientSecretEmpty;
authSchemeToComponent(scheme) {
return this.componentsMap[scheme] || null;
},
onCancel() {
this.showModal = false;
Expand All@@ -294,14 +185,48 @@ export default {
onModalHidden() {
this.resetFormData();
},
generateCallbackUrl(item) {
if (item.config.AuthScheme === "OAuth") {
const name = item.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
}
},
authorizeConnection() {
this.showAuthorizingModal = true;
this.showModal = false;
this.resetData = false;
ProcessMaker.apiClient.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)

if (this.setting.config.AuthScheme === "OAuth") {
this.authorizeOAuthConnection();
} else {
this.authorizeNoneConnection();
}
},
authorizeOAuthConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)
.then((response) => {
window.location = response.data?.url;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
})
.finally(() => {
this.showModal = true;
this.showAuthorizingModal = false;
});
},
authorizeNoneConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/authorize-driver`, this.formData)
.then((response) => {
window.location = response.data.url;
this.showModal = false;
this.showAuthorizingModal = true;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
Expand All@@ -310,18 +235,11 @@ export default {
});
},
onSave() {
const driver = this.setting.key.split("cdata.")[1];

this.formData.driver = driver;
this.formData.name = this.setting.config?.name;
this.formData.driver = this.setting.config?.driver;
this.transformed = { ...this.formData };
this.authorizeConnection();
},
generateCallbackUrl(data) {
const name = data.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
},
resetFormData() {
if (this.resetData) {
this.formData = {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,13 +13,17 @@ import ExcelConnectionProperties from "./cdata/ExcelConnectionProperties.vue";
import GithubConnectionProperties from "./cdata/GithubConnectionProperties.vue";
import DocusignConnectionProperties from "./cdata/DocusignConnectionProperties.vue";
import GmailConnectionProperties from "./cdata/GmailConnectionProperties.vue";
import BamboohrConnectionProperties from "./cdata/BamboohrConnectionProperties.vue";
import SapHanaConnectionProperties from "./cdata/SapHanaConnectionProperties.vue";

export default {
components: {
ExcelConnectionProperties,
GithubConnectionProperties,
DocusignConnectionProperties,
GmailConnectionProperties,
BamboohrConnectionProperties,
SapHanaConnectionProperties,
},
props: {
formData: {
Expand All@@ -38,6 +42,8 @@ export default {
"cdata.github": "github-connection-properties",
"cdata.docusign": "docusign-connection-properties",
"cdata.gmail": "gmail-connection-properties",
"cdata.BambooHR": "bamboohr-connection-properties",
"cdata.saphana": "sap-hana-connection-properties",
},
};
},
Expand Down
242 changes: 80 additions & 162 deletions resources/js/admin/settings/components/SettingDriverAuthorization.vue
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
<template>
<div>
<!-- Authorize badge -->
<div v-if="hasAuthorizedBadge">
<b-badge
pill
:variant="isAuthorized ? 'success' : 'warning'"
>
<span v-if="isAuthorized">{{ $t('Authorized') }}</span>
<span v-else>{{ $t('Not Authorized') }}</span>
<span v-if="isAuthorized">{{ $t("Authorized") }}</span>
<span v-else>{{ $t("Not Authorized") }}</span>
</b-badge>
</div>
<div v-else>
Empty
{{ $t("Empty") }}
</div>

<!-- Connection properties modal -->
<b-modal
v-model="showModal"
class="setting-object-modal"
Expand All@@ -24,110 +27,30 @@
class="d-block"
>
<div>
<h5
v-if="setting.name"
class="mb-0"
>
{{ $t(setting.name) }}
</h5>
<h5
v-else
class="mb-0"
>
{{ setting.key }}
<h5 class="mb-0">
<span v-if="setting.name">{{ $t(setting.name) }}</span>
<span v-else>{{ $t(setting.key) }}</span>
</h5>
<small class="form-text text-muted">{{ $t('Configure the driver connection properties.') }}</small>
<small class="form-text text-muted">
{{ $t("Configure the driver connection properties.") }}
</small>
</div>
<button
type="button"
:aria-label="$t('Close')"
class="close"
:aria-label="$t('Close')"
@click="onCancel"
>
×
&times;
</button>
</template>
<div>
<b-form-group
required
:label="$t('Client ID')"
:description="formDescription('The client ID assigned when you register your application.', 'client_id', errors)"
:invalid-feedback="errorMessage('client_id', errors)"
:state="errorState('client_id', errors)"
>
<b-form-input
v-model="formData.client_id"
required
autofocus
autocomplete="off"
:state="errorState('client_id', errors)"
name="client_id"
data-cy="client_id"
/>
</b-form-group>

<b-form-group
required
:label="$t('Client Secret')"
:description="formDescription('The client secret assigned when you register your application.', 'client_secret', errors)"
:invalid-feedback="errorMessage('client_secret', errors)"
:state="errorState('client_secret', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.client_secret"
required
autofocus
autocomplete="off"
trim
:type="type"
:state="errorState('client_secret', errors)"
name="client_secret"
data-cy="client_secret"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Toggle Show Password')"
variant="secondary"
@click="togglePassword"
>
<i
class="fas"
:class="icon"
/>
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>

<b-form-group
required
:label="$t('Redirect URL')"
:description="formDescription('This value must match the callback URL you specify in your app settings.', 'callback_url', errors)"
:invalid-feedback="errorMessage('callback_url', errors)"
:state="errorState('callback_url', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.callback_url"
autofocus
readonly
autocomplete="off"
:state="errorState('callback_url', errors)"
name="callback_url"
data-cy="callback_url"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Copy')"
variant="secondary"
@click="onCopy"
>
<i class="fas fa-copy" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>
<component
:is="authSchemeToComponent(setting.config?.AuthScheme)"
:form-data="formData"
:auth-scheme="setting.config?.AuthScheme"
@updateFormData="updateFormData"
/>

<additional-driver-connection-properties
:driver-key="setting?.key"
Expand All@@ -145,20 +68,20 @@
data-cy="cancel-button"
@click="onCancel"
>
{{ $t('Cancel') }}
{{ $t("Cancel") }}
</button>
<button
type="button"
class="btn btn-secondary ml-3"
data-cy="authorize-button"
:disabled="isButtonDisabled"
@click="onSave"
>
{{ $t('Authorize') }}
{{ $t("Authorize") }}
</button>
</div>
</b-modal>

<!-- Authorizing modal -->
<b-modal
v-model="showAuthorizingModal"
class="setting-object-modal"
Expand All@@ -169,26 +92,33 @@
no-fade
>
<div class="text-center">
<h3>{{ $t('Connecting Driver') }}</h3>
<h3>{{ $t("Connecting Driver") }}</h3>
<i class="fas fa-circle-notch fa-spin fa-3x p-0 text-primary" />
</div>
</b-modal>
</div>
</template>

<script>
// eslint-disable-next-line import/no-unresolved
import { FormErrorsMixin, Required } from "SharedComponents";
import settingMixin from "../mixins/setting";
import AdditionalDriverConnectionProperties from "./AdditionalDriverConnectionProperties.vue";
import OauthConnectionProperties from "./cdata/OauthConnectionProperties.vue";
import NoneConnectionProperties from "./cdata/NoneConnectionProperties.vue";
import PasswordConnectionProperties from "./cdata/PasswordConnectionProperties.vue";

export default {
components: { AdditionalDriverConnectionProperties },
components: {
AdditionalDriverConnectionProperties,
OauthConnectionProperties,
NoneConnectionProperties,
PasswordConnectionProperties,
},
mixins: [settingMixin, FormErrorsMixin, Required],
props: {
setting: {
type: [Object, null],
default: null,
default: () => ({}),
},
value: {
type: Object,
Expand All@@ -198,19 +128,19 @@ export default {
data() {
return {
input: "",
formData: {
client_id: "",
client_secret: "",
callback_url: "",
},
formData: {},
selected: null,
showModal: false,
showAuthorizingModal: false,
transformed: null,
errors: {},
isInvalid: true,
type: "password",
resetData: true,
componentsMap: {
OAuth: "oauth-connection-properties",
None: "none-connection-properties",
Password: "password-connection-properties",
Basic: "password-connection-properties",
},
};
},
computed: {
Expand All@@ -227,57 +157,18 @@ export default {
}
return false;
},
changed() {
return JSON.stringify(this.formData) !== JSON.stringify(this.transformed);
},
icon() {
if (this.type === "password") {
return "fa-eye";
}
return "fa-eye-slash";
},
isButtonDisabled() {
return this.isInvalid || (this.isAuthorized && !this.changed);
},
},
watch: {
formData: {
handler() {
this.isInvalid = this.validateData();
},
deep: true,
},
},
mounted() {
if (this.value === null) {
this.resetFormData();
} else {
this.formData = this.value;
}
this.isInvalid = this.validateData();
this.transformed = this.copy(this.formData);
},
methods: {
onCopy() {
navigator.clipboard.writeText(this.formData.callback_url).then(() => {
ProcessMaker.alert(this.$t("The setting was copied to your clipboard."), "success");
}, () => {
ProcessMaker.alert(this.$t("The setting was not copied to your clipboard."), "danger");
});
},
togglePassword() {
if (this.type === "text") {
this.type = "password";
} else {
this.type = "text";
}
},
validateData() {
// Check if client_id and client_secret are empty
const clientIdEmpty = _.isEmpty(this.formData.client_id);
const clientSecretEmpty = _.isEmpty(this.formData.client_secret);

return _.isEmpty(this.formData) || clientIdEmpty || clientSecretEmpty;
authSchemeToComponent(scheme) {
return this.componentsMap[scheme] || null;
},
onCancel() {
this.showModal = false;
Expand All@@ -294,14 +185,48 @@ export default {
onModalHidden() {
this.resetFormData();
},
generateCallbackUrl(item) {
if (item.config.AuthScheme === "OAuth") {
const name = item.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
}
},
authorizeConnection() {
this.showAuthorizingModal = true;
this.showModal = false;
this.resetData = false;
ProcessMaker.apiClient.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)

if (this.setting.config.AuthScheme === "OAuth") {
this.authorizeOAuthConnection();
} else {
this.authorizeNoneConnection();
}
},
authorizeOAuthConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)
.then((response) => {
window.location = response.data?.url;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
})
.finally(() => {
this.showModal = true;
this.showAuthorizingModal = false;
});
},
authorizeNoneConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/authorize-driver`, this.formData)
.then((response) => {
window.location = response.data.url;
this.showModal = false;
this.showAuthorizingModal = true;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
Expand All@@ -310,18 +235,11 @@ export default {
});
},
onSave() {
const driver = this.setting.key.split("cdata.")[1];

this.formData.driver = driver;
this.formData.name = this.setting.config?.name;
this.formData.driver = this.setting.config?.driver;
this.transformed = { ...this.formData };
this.authorizeConnection();
},
generateCallbackUrl(data) {
const name = data.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
},
resetFormData() {
if (this.resetData) {
this.formData = {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,13 +13,17 @@ import ExcelConnectionProperties from "./cdata/ExcelConnectionProperties.vue";
import GithubConnectionProperties from "./cdata/GithubConnectionProperties.vue";
import DocusignConnectionProperties from "./cdata/DocusignConnectionProperties.vue";
import GmailConnectionProperties from "./cdata/GmailConnectionProperties.vue";
import BamboohrConnectionProperties from "./cdata/BamboohrConnectionProperties.vue";
import SapHanaConnectionProperties from "./cdata/SapHanaConnectionProperties.vue";

export default {
components: {
ExcelConnectionProperties,
GithubConnectionProperties,
DocusignConnectionProperties,
GmailConnectionProperties,
BamboohrConnectionProperties,
SapHanaConnectionProperties,
},
props: {
formData: {
Expand All@@ -38,6 +42,8 @@ export default {
"cdata.github": "github-connection-properties",
"cdata.docusign": "docusign-connection-properties",
"cdata.gmail": "gmail-connection-properties",
"cdata.BambooHR": "bamboohr-connection-properties",
"cdata.saphana": "sap-hana-connection-properties",
},
};
},
Expand Down
242 changes: 80 additions & 162 deletions resources/js/admin/settings/components/SettingDriverAuthorization.vue
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
<template>
<div>
<!-- Authorize badge -->
<div v-if="hasAuthorizedBadge">
<b-badge
pill
:variant="isAuthorized ? 'success' : 'warning'"
>
<span v-if="isAuthorized">{{ $t('Authorized') }}</span>
<span v-else>{{ $t('Not Authorized') }}</span>
<span v-if="isAuthorized">{{ $t("Authorized") }}</span>
<span v-else>{{ $t("Not Authorized") }}</span>
</b-badge>
</div>
<div v-else>
Empty
{{ $t("Empty") }}
</div>

<!-- Connection properties modal -->
<b-modal
v-model="showModal"
class="setting-object-modal"
Expand All@@ -24,110 +27,30 @@
class="d-block"
>
<div>
<h5
v-if="setting.name"
class="mb-0"
>
{{ $t(setting.name) }}
</h5>
<h5
v-else
class="mb-0"
>
{{ setting.key }}
<h5 class="mb-0">
<span v-if="setting.name">{{ $t(setting.name) }}</span>
<span v-else>{{ $t(setting.key) }}</span>
</h5>
<small class="form-text text-muted">{{ $t('Configure the driver connection properties.') }}</small>
<small class="form-text text-muted">
{{ $t("Configure the driver connection properties.") }}
</small>
</div>
<button
type="button"
:aria-label="$t('Close')"
class="close"
:aria-label="$t('Close')"
@click="onCancel"
>
×
&times;
</button>
</template>
<div>
<b-form-group
required
:label="$t('Client ID')"
:description="formDescription('The client ID assigned when you register your application.', 'client_id', errors)"
:invalid-feedback="errorMessage('client_id', errors)"
:state="errorState('client_id', errors)"
>
<b-form-input
v-model="formData.client_id"
required
autofocus
autocomplete="off"
:state="errorState('client_id', errors)"
name="client_id"
data-cy="client_id"
/>
</b-form-group>

<b-form-group
required
:label="$t('Client Secret')"
:description="formDescription('The client secret assigned when you register your application.', 'client_secret', errors)"
:invalid-feedback="errorMessage('client_secret', errors)"
:state="errorState('client_secret', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.client_secret"
required
autofocus
autocomplete="off"
trim
:type="type"
:state="errorState('client_secret', errors)"
name="client_secret"
data-cy="client_secret"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Toggle Show Password')"
variant="secondary"
@click="togglePassword"
>
<i
class="fas"
:class="icon"
/>
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>

<b-form-group
required
:label="$t('Redirect URL')"
:description="formDescription('This value must match the callback URL you specify in your app settings.', 'callback_url', errors)"
:invalid-feedback="errorMessage('callback_url', errors)"
:state="errorState('callback_url', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.callback_url"
autofocus
readonly
autocomplete="off"
:state="errorState('callback_url', errors)"
name="callback_url"
data-cy="callback_url"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Copy')"
variant="secondary"
@click="onCopy"
>
<i class="fas fa-copy" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>
<component
:is="authSchemeToComponent(setting.config?.AuthScheme)"
:form-data="formData"
:auth-scheme="setting.config?.AuthScheme"
@updateFormData="updateFormData"
/>

<additional-driver-connection-properties
:driver-key="setting?.key"
Expand All@@ -145,20 +68,20 @@
data-cy="cancel-button"
@click="onCancel"
>
{{ $t('Cancel') }}
{{ $t("Cancel") }}
</button>
<button
type="button"
class="btn btn-secondary ml-3"
data-cy="authorize-button"
:disabled="isButtonDisabled"
@click="onSave"
>
{{ $t('Authorize') }}
{{ $t("Authorize") }}
</button>
</div>
</b-modal>

<!-- Authorizing modal -->
<b-modal
v-model="showAuthorizingModal"
class="setting-object-modal"
Expand All@@ -169,26 +92,33 @@
no-fade
>
<div class="text-center">
<h3>{{ $t('Connecting Driver') }}</h3>
<h3>{{ $t("Connecting Driver") }}</h3>
<i class="fas fa-circle-notch fa-spin fa-3x p-0 text-primary" />
</div>
</b-modal>
</div>
</template>

<script>
// eslint-disable-next-line import/no-unresolved
import { FormErrorsMixin, Required } from "SharedComponents";
import settingMixin from "../mixins/setting";
import AdditionalDriverConnectionProperties from "./AdditionalDriverConnectionProperties.vue";
import OauthConnectionProperties from "./cdata/OauthConnectionProperties.vue";
import NoneConnectionProperties from "./cdata/NoneConnectionProperties.vue";
import PasswordConnectionProperties from "./cdata/PasswordConnectionProperties.vue";

export default {
components: { AdditionalDriverConnectionProperties },
components: {
AdditionalDriverConnectionProperties,
OauthConnectionProperties,
NoneConnectionProperties,
PasswordConnectionProperties,
},
mixins: [settingMixin, FormErrorsMixin, Required],
props: {
setting: {
type: [Object, null],
default: null,
default: () => ({}),
},
value: {
type: Object,
Expand All@@ -198,19 +128,19 @@ export default {
data() {
return {
input: "",
formData: {
client_id: "",
client_secret: "",
callback_url: "",
},
formData: {},
selected: null,
showModal: false,
showAuthorizingModal: false,
transformed: null,
errors: {},
isInvalid: true,
type: "password",
resetData: true,
componentsMap: {
OAuth: "oauth-connection-properties",
None: "none-connection-properties",
Password: "password-connection-properties",
Basic: "password-connection-properties",
},
};
},
computed: {
Expand All@@ -227,57 +157,18 @@ export default {
}
return false;
},
changed() {
return JSON.stringify(this.formData) !== JSON.stringify(this.transformed);
},
icon() {
if (this.type === "password") {
return "fa-eye";
}
return "fa-eye-slash";
},
isButtonDisabled() {
return this.isInvalid || (this.isAuthorized && !this.changed);
},
},
watch: {
formData: {
handler() {
this.isInvalid = this.validateData();
},
deep: true,
},
},
mounted() {
if (this.value === null) {
this.resetFormData();
} else {
this.formData = this.value;
}
this.isInvalid = this.validateData();
this.transformed = this.copy(this.formData);
},
methods: {
onCopy() {
navigator.clipboard.writeText(this.formData.callback_url).then(() => {
ProcessMaker.alert(this.$t("The setting was copied to your clipboard."), "success");
}, () => {
ProcessMaker.alert(this.$t("The setting was not copied to your clipboard."), "danger");
});
},
togglePassword() {
if (this.type === "text") {
this.type = "password";
} else {
this.type = "text";
}
},
validateData() {
// Check if client_id and client_secret are empty
const clientIdEmpty = _.isEmpty(this.formData.client_id);
const clientSecretEmpty = _.isEmpty(this.formData.client_secret);

return _.isEmpty(this.formData) || clientIdEmpty || clientSecretEmpty;
authSchemeToComponent(scheme) {
return this.componentsMap[scheme] || null;
},
onCancel() {
this.showModal = false;
Expand All@@ -294,14 +185,48 @@ export default {
onModalHidden() {
this.resetFormData();
},
generateCallbackUrl(item) {
if (item.config.AuthScheme === "OAuth") {
const name = item.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
}
},
authorizeConnection() {
this.showAuthorizingModal = true;
this.showModal = false;
this.resetData = false;
ProcessMaker.apiClient.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)

if (this.setting.config.AuthScheme === "OAuth") {
this.authorizeOAuthConnection();
} else {
this.authorizeNoneConnection();
}
},
authorizeOAuthConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)
.then((response) => {
window.location = response.data?.url;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
})
.finally(() => {
this.showModal = true;
this.showAuthorizingModal = false;
});
},
authorizeNoneConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/authorize-driver`, this.formData)
.then((response) => {
window.location = response.data.url;
this.showModal = false;
this.showAuthorizingModal = true;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
Expand All@@ -310,18 +235,11 @@ export default {
});
},
onSave() {
const driver = this.setting.key.split("cdata.")[1];

this.formData.driver = driver;
this.formData.name = this.setting.config?.name;
this.formData.driver = this.setting.config?.driver;
this.transformed = { ...this.formData };
this.authorizeConnection();
},
generateCallbackUrl(data) {
const name = data.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
},
resetFormData() {
if (this.resetData) {
this.formData = {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,13 +13,17 @@ import ExcelConnectionProperties from "./cdata/ExcelConnectionProperties.vue";
import GithubConnectionProperties from "./cdata/GithubConnectionProperties.vue";
import DocusignConnectionProperties from "./cdata/DocusignConnectionProperties.vue";
import GmailConnectionProperties from "./cdata/GmailConnectionProperties.vue";
import BamboohrConnectionProperties from "./cdata/BamboohrConnectionProperties.vue";
import SapHanaConnectionProperties from "./cdata/SapHanaConnectionProperties.vue";

export default {
components: {
ExcelConnectionProperties,
GithubConnectionProperties,
DocusignConnectionProperties,
GmailConnectionProperties,
BamboohrConnectionProperties,
SapHanaConnectionProperties,
},
props: {
formData: {
Expand All@@ -38,6 +42,8 @@ export default {
"cdata.github": "github-connection-properties",
"cdata.docusign": "docusign-connection-properties",
"cdata.gmail": "gmail-connection-properties",
"cdata.BambooHR": "bamboohr-connection-properties",
"cdata.saphana": "sap-hana-connection-properties",
},
};
},
Expand Down
242 changes: 80 additions & 162 deletions resources/js/admin/settings/components/SettingDriverAuthorization.vue
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
<template>
<div>
<!-- Authorize badge -->
<div v-if="hasAuthorizedBadge">
<b-badge
pill
:variant="isAuthorized ? 'success' : 'warning'"
>
<span v-if="isAuthorized">{{ $t('Authorized') }}</span>
<span v-else>{{ $t('Not Authorized') }}</span>
<span v-if="isAuthorized">{{ $t("Authorized") }}</span>
<span v-else>{{ $t("Not Authorized") }}</span>
</b-badge>
</div>
<div v-else>
Empty
{{ $t("Empty") }}
</div>

<!-- Connection properties modal -->
<b-modal
v-model="showModal"
class="setting-object-modal"
Expand All@@ -24,110 +27,30 @@
class="d-block"
>
<div>
<h5
v-if="setting.name"
class="mb-0"
>
{{ $t(setting.name) }}
</h5>
<h5
v-else
class="mb-0"
>
{{ setting.key }}
<h5 class="mb-0">
<span v-if="setting.name">{{ $t(setting.name) }}</span>
<span v-else>{{ $t(setting.key) }}</span>
</h5>
<small class="form-text text-muted">{{ $t('Configure the driver connection properties.') }}</small>
<small class="form-text text-muted">
{{ $t("Configure the driver connection properties.") }}
</small>
</div>
<button
type="button"
:aria-label="$t('Close')"
class="close"
:aria-label="$t('Close')"
@click="onCancel"
>
×
&times;
</button>
</template>
<div>
<b-form-group
required
:label="$t('Client ID')"
:description="formDescription('The client ID assigned when you register your application.', 'client_id', errors)"
:invalid-feedback="errorMessage('client_id', errors)"
:state="errorState('client_id', errors)"
>
<b-form-input
v-model="formData.client_id"
required
autofocus
autocomplete="off"
:state="errorState('client_id', errors)"
name="client_id"
data-cy="client_id"
/>
</b-form-group>

<b-form-group
required
:label="$t('Client Secret')"
:description="formDescription('The client secret assigned when you register your application.', 'client_secret', errors)"
:invalid-feedback="errorMessage('client_secret', errors)"
:state="errorState('client_secret', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.client_secret"
required
autofocus
autocomplete="off"
trim
:type="type"
:state="errorState('client_secret', errors)"
name="client_secret"
data-cy="client_secret"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Toggle Show Password')"
variant="secondary"
@click="togglePassword"
>
<i
class="fas"
:class="icon"
/>
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>

<b-form-group
required
:label="$t('Redirect URL')"
:description="formDescription('This value must match the callback URL you specify in your app settings.', 'callback_url', errors)"
:invalid-feedback="errorMessage('callback_url', errors)"
:state="errorState('callback_url', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.callback_url"
autofocus
readonly
autocomplete="off"
:state="errorState('callback_url', errors)"
name="callback_url"
data-cy="callback_url"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Copy')"
variant="secondary"
@click="onCopy"
>
<i class="fas fa-copy" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>
<component
:is="authSchemeToComponent(setting.config?.AuthScheme)"
:form-data="formData"
:auth-scheme="setting.config?.AuthScheme"
@updateFormData="updateFormData"
/>

<additional-driver-connection-properties
:driver-key="setting?.key"
Expand All@@ -145,20 +68,20 @@
data-cy="cancel-button"
@click="onCancel"
>
{{ $t('Cancel') }}
{{ $t("Cancel") }}
</button>
<button
type="button"
class="btn btn-secondary ml-3"
data-cy="authorize-button"
:disabled="isButtonDisabled"
@click="onSave"
>
{{ $t('Authorize') }}
{{ $t("Authorize") }}
</button>
</div>
</b-modal>

<!-- Authorizing modal -->
<b-modal
v-model="showAuthorizingModal"
class="setting-object-modal"
Expand All@@ -169,26 +92,33 @@
no-fade
>
<div class="text-center">
<h3>{{ $t('Connecting Driver') }}</h3>
<h3>{{ $t("Connecting Driver") }}</h3>
<i class="fas fa-circle-notch fa-spin fa-3x p-0 text-primary" />
</div>
</b-modal>
</div>
</template>

<script>
// eslint-disable-next-line import/no-unresolved
import { FormErrorsMixin, Required } from "SharedComponents";
import settingMixin from "../mixins/setting";
import AdditionalDriverConnectionProperties from "./AdditionalDriverConnectionProperties.vue";
import OauthConnectionProperties from "./cdata/OauthConnectionProperties.vue";
import NoneConnectionProperties from "./cdata/NoneConnectionProperties.vue";
import PasswordConnectionProperties from "./cdata/PasswordConnectionProperties.vue";

export default {
components: { AdditionalDriverConnectionProperties },
components: {
AdditionalDriverConnectionProperties,
OauthConnectionProperties,
NoneConnectionProperties,
PasswordConnectionProperties,
},
mixins: [settingMixin, FormErrorsMixin, Required],
props: {
setting: {
type: [Object, null],
default: null,
default: () => ({}),
},
value: {
type: Object,
Expand All@@ -198,19 +128,19 @@ export default {
data() {
return {
input: "",
formData: {
client_id: "",
client_secret: "",
callback_url: "",
},
formData: {},
selected: null,
showModal: false,
showAuthorizingModal: false,
transformed: null,
errors: {},
isInvalid: true,
type: "password",
resetData: true,
componentsMap: {
OAuth: "oauth-connection-properties",
None: "none-connection-properties",
Password: "password-connection-properties",
Basic: "password-connection-properties",
},
};
},
computed: {
Expand All@@ -227,57 +157,18 @@ export default {
}
return false;
},
changed() {
return JSON.stringify(this.formData) !== JSON.stringify(this.transformed);
},
icon() {
if (this.type === "password") {
return "fa-eye";
}
return "fa-eye-slash";
},
isButtonDisabled() {
return this.isInvalid || (this.isAuthorized && !this.changed);
},
},
watch: {
formData: {
handler() {
this.isInvalid = this.validateData();
},
deep: true,
},
},
mounted() {
if (this.value === null) {
this.resetFormData();
} else {
this.formData = this.value;
}
this.isInvalid = this.validateData();
this.transformed = this.copy(this.formData);
},
methods: {
onCopy() {
navigator.clipboard.writeText(this.formData.callback_url).then(() => {
ProcessMaker.alert(this.$t("The setting was copied to your clipboard."), "success");
}, () => {
ProcessMaker.alert(this.$t("The setting was not copied to your clipboard."), "danger");
});
},
togglePassword() {
if (this.type === "text") {
this.type = "password";
} else {
this.type = "text";
}
},
validateData() {
// Check if client_id and client_secret are empty
const clientIdEmpty = _.isEmpty(this.formData.client_id);
const clientSecretEmpty = _.isEmpty(this.formData.client_secret);

return _.isEmpty(this.formData) || clientIdEmpty || clientSecretEmpty;
authSchemeToComponent(scheme) {
return this.componentsMap[scheme] || null;
},
onCancel() {
this.showModal = false;
Expand All@@ -294,14 +185,48 @@ export default {
onModalHidden() {
this.resetFormData();
},
generateCallbackUrl(item) {
if (item.config.AuthScheme === "OAuth") {
const name = item.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
}
},
authorizeConnection() {
this.showAuthorizingModal = true;
this.showModal = false;
this.resetData = false;
ProcessMaker.apiClient.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)

if (this.setting.config.AuthScheme === "OAuth") {
this.authorizeOAuthConnection();
} else {
this.authorizeNoneConnection();
}
},
authorizeOAuthConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)
.then((response) => {
window.location = response.data?.url;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
})
.finally(() => {
this.showModal = true;
this.showAuthorizingModal = false;
});
},
authorizeNoneConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/authorize-driver`, this.formData)
.then((response) => {
window.location = response.data.url;
this.showModal = false;
this.showAuthorizingModal = true;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
Expand All@@ -310,18 +235,11 @@ export default {
});
},
onSave() {
const driver = this.setting.key.split("cdata.")[1];

this.formData.driver = driver;
this.formData.name = this.setting.config?.name;
this.formData.driver = this.setting.config?.driver;
this.transformed = { ...this.formData };
this.authorizeConnection();
},
generateCallbackUrl(data) {
const name = data.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
},
resetFormData() {
if (this.resetData) {
this.formData = {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,13 +13,17 @@ import ExcelConnectionProperties from "./cdata/ExcelConnectionProperties.vue";
import GithubConnectionProperties from "./cdata/GithubConnectionProperties.vue";
import DocusignConnectionProperties from "./cdata/DocusignConnectionProperties.vue";
import GmailConnectionProperties from "./cdata/GmailConnectionProperties.vue";
import BamboohrConnectionProperties from "./cdata/BamboohrConnectionProperties.vue";
import SapHanaConnectionProperties from "./cdata/SapHanaConnectionProperties.vue";

export default {
components: {
ExcelConnectionProperties,
GithubConnectionProperties,
DocusignConnectionProperties,
GmailConnectionProperties,
BamboohrConnectionProperties,
SapHanaConnectionProperties,
},
props: {
formData: {
Expand All@@ -38,6 +42,8 @@ export default {
"cdata.github": "github-connection-properties",
"cdata.docusign": "docusign-connection-properties",
"cdata.gmail": "gmail-connection-properties",
"cdata.BambooHR": "bamboohr-connection-properties",
"cdata.saphana": "sap-hana-connection-properties",
},
};
},
Expand Down
242 changes: 80 additions & 162 deletions resources/js/admin/settings/components/SettingDriverAuthorization.vue
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
<template>
<div>
<!-- Authorize badge -->
<div v-if="hasAuthorizedBadge">
<b-badge
pill
:variant="isAuthorized ? 'success' : 'warning'"
>
<span v-if="isAuthorized">{{ $t('Authorized') }}</span>
<span v-else>{{ $t('Not Authorized') }}</span>
<span v-if="isAuthorized">{{ $t("Authorized") }}</span>
<span v-else>{{ $t("Not Authorized") }}</span>
</b-badge>
</div>
<div v-else>
Empty
{{ $t("Empty") }}
</div>

<!-- Connection properties modal -->
<b-modal
v-model="showModal"
class="setting-object-modal"
Expand All@@ -24,110 +27,30 @@
class="d-block"
>
<div>
<h5
v-if="setting.name"
class="mb-0"
>
{{ $t(setting.name) }}
</h5>
<h5
v-else
class="mb-0"
>
{{ setting.key }}
<h5 class="mb-0">
<span v-if="setting.name">{{ $t(setting.name) }}</span>
<span v-else>{{ $t(setting.key) }}</span>
</h5>
<small class="form-text text-muted">{{ $t('Configure the driver connection properties.') }}</small>
<small class="form-text text-muted">
{{ $t("Configure the driver connection properties.") }}
</small>
</div>
<button
type="button"
:aria-label="$t('Close')"
class="close"
:aria-label="$t('Close')"
@click="onCancel"
>
×
&times;
</button>
</template>
<div>
<b-form-group
required
:label="$t('Client ID')"
:description="formDescription('The client ID assigned when you register your application.', 'client_id', errors)"
:invalid-feedback="errorMessage('client_id', errors)"
:state="errorState('client_id', errors)"
>
<b-form-input
v-model="formData.client_id"
required
autofocus
autocomplete="off"
:state="errorState('client_id', errors)"
name="client_id"
data-cy="client_id"
/>
</b-form-group>

<b-form-group
required
:label="$t('Client Secret')"
:description="formDescription('The client secret assigned when you register your application.', 'client_secret', errors)"
:invalid-feedback="errorMessage('client_secret', errors)"
:state="errorState('client_secret', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.client_secret"
required
autofocus
autocomplete="off"
trim
:type="type"
:state="errorState('client_secret', errors)"
name="client_secret"
data-cy="client_secret"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Toggle Show Password')"
variant="secondary"
@click="togglePassword"
>
<i
class="fas"
:class="icon"
/>
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>

<b-form-group
required
:label="$t('Redirect URL')"
:description="formDescription('This value must match the callback URL you specify in your app settings.', 'callback_url', errors)"
:invalid-feedback="errorMessage('callback_url', errors)"
:state="errorState('callback_url', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.callback_url"
autofocus
readonly
autocomplete="off"
:state="errorState('callback_url', errors)"
name="callback_url"
data-cy="callback_url"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Copy')"
variant="secondary"
@click="onCopy"
>
<i class="fas fa-copy" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>
<component
:is="authSchemeToComponent(setting.config?.AuthScheme)"
:form-data="formData"
:auth-scheme="setting.config?.AuthScheme"
@updateFormData="updateFormData"
/>

<additional-driver-connection-properties
:driver-key="setting?.key"
Expand All@@ -145,20 +68,20 @@
data-cy="cancel-button"
@click="onCancel"
>
{{ $t('Cancel') }}
{{ $t("Cancel") }}
</button>
<button
type="button"
class="btn btn-secondary ml-3"
data-cy="authorize-button"
:disabled="isButtonDisabled"
@click="onSave"
>
{{ $t('Authorize') }}
{{ $t("Authorize") }}
</button>
</div>
</b-modal>

<!-- Authorizing modal -->
<b-modal
v-model="showAuthorizingModal"
class="setting-object-modal"
Expand All@@ -169,26 +92,33 @@
no-fade
>
<div class="text-center">
<h3>{{ $t('Connecting Driver') }}</h3>
<h3>{{ $t("Connecting Driver") }}</h3>
<i class="fas fa-circle-notch fa-spin fa-3x p-0 text-primary" />
</div>
</b-modal>
</div>
</template>

<script>
// eslint-disable-next-line import/no-unresolved
import { FormErrorsMixin, Required } from "SharedComponents";
import settingMixin from "../mixins/setting";
import AdditionalDriverConnectionProperties from "./AdditionalDriverConnectionProperties.vue";
import OauthConnectionProperties from "./cdata/OauthConnectionProperties.vue";
import NoneConnectionProperties from "./cdata/NoneConnectionProperties.vue";
import PasswordConnectionProperties from "./cdata/PasswordConnectionProperties.vue";

export default {
components: { AdditionalDriverConnectionProperties },
components: {
AdditionalDriverConnectionProperties,
OauthConnectionProperties,
NoneConnectionProperties,
PasswordConnectionProperties,
},
mixins: [settingMixin, FormErrorsMixin, Required],
props: {
setting: {
type: [Object, null],
default: null,
default: () => ({}),
},
value: {
type: Object,
Expand All@@ -198,19 +128,19 @@ export default {
data() {
return {
input: "",
formData: {
client_id: "",
client_secret: "",
callback_url: "",
},
formData: {},
selected: null,
showModal: false,
showAuthorizingModal: false,
transformed: null,
errors: {},
isInvalid: true,
type: "password",
resetData: true,
componentsMap: {
OAuth: "oauth-connection-properties",
None: "none-connection-properties",
Password: "password-connection-properties",
Basic: "password-connection-properties",
},
};
},
computed: {
Expand All@@ -227,57 +157,18 @@ export default {
}
return false;
},
changed() {
return JSON.stringify(this.formData) !== JSON.stringify(this.transformed);
},
icon() {
if (this.type === "password") {
return "fa-eye";
}
return "fa-eye-slash";
},
isButtonDisabled() {
return this.isInvalid || (this.isAuthorized && !this.changed);
},
},
watch: {
formData: {
handler() {
this.isInvalid = this.validateData();
},
deep: true,
},
},
mounted() {
if (this.value === null) {
this.resetFormData();
} else {
this.formData = this.value;
}
this.isInvalid = this.validateData();
this.transformed = this.copy(this.formData);
},
methods: {
onCopy() {
navigator.clipboard.writeText(this.formData.callback_url).then(() => {
ProcessMaker.alert(this.$t("The setting was copied to your clipboard."), "success");
}, () => {
ProcessMaker.alert(this.$t("The setting was not copied to your clipboard."), "danger");
});
},
togglePassword() {
if (this.type === "text") {
this.type = "password";
} else {
this.type = "text";
}
},
validateData() {
// Check if client_id and client_secret are empty
const clientIdEmpty = _.isEmpty(this.formData.client_id);
const clientSecretEmpty = _.isEmpty(this.formData.client_secret);

return _.isEmpty(this.formData) || clientIdEmpty || clientSecretEmpty;
authSchemeToComponent(scheme) {
return this.componentsMap[scheme] || null;
},
onCancel() {
this.showModal = false;
Expand All@@ -294,14 +185,48 @@ export default {
onModalHidden() {
this.resetFormData();
},
generateCallbackUrl(item) {
if (item.config.AuthScheme === "OAuth") {
const name = item.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
}
},
authorizeConnection() {
this.showAuthorizingModal = true;
this.showModal = false;
this.resetData = false;
ProcessMaker.apiClient.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)

if (this.setting.config.AuthScheme === "OAuth") {
this.authorizeOAuthConnection();
} else {
this.authorizeNoneConnection();
}
},
authorizeOAuthConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)
.then((response) => {
window.location = response.data?.url;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
})
.finally(() => {
this.showModal = true;
this.showAuthorizingModal = false;
});
},
authorizeNoneConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/authorize-driver`, this.formData)
.then((response) => {
window.location = response.data.url;
this.showModal = false;
this.showAuthorizingModal = true;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
Expand All@@ -310,18 +235,11 @@ export default {
});
},
onSave() {
const driver = this.setting.key.split("cdata.")[1];

this.formData.driver = driver;
this.formData.name = this.setting.config?.name;
this.formData.driver = this.setting.config?.driver;
this.transformed = { ...this.formData };
this.authorizeConnection();
},
generateCallbackUrl(data) {
const name = data.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
},
resetFormData() {
if (this.resetData) {
this.formData = {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,13 +13,17 @@ import ExcelConnectionProperties from "./cdata/ExcelConnectionProperties.vue";
import GithubConnectionProperties from "./cdata/GithubConnectionProperties.vue";
import DocusignConnectionProperties from "./cdata/DocusignConnectionProperties.vue";
import GmailConnectionProperties from "./cdata/GmailConnectionProperties.vue";
import BamboohrConnectionProperties from "./cdata/BamboohrConnectionProperties.vue";
import SapHanaConnectionProperties from "./cdata/SapHanaConnectionProperties.vue";

export default {
components: {
ExcelConnectionProperties,
GithubConnectionProperties,
DocusignConnectionProperties,
GmailConnectionProperties,
BamboohrConnectionProperties,
SapHanaConnectionProperties,
},
props: {
formData: {
Expand All@@ -38,6 +42,8 @@ export default {
"cdata.github": "github-connection-properties",
"cdata.docusign": "docusign-connection-properties",
"cdata.gmail": "gmail-connection-properties",
"cdata.BambooHR": "bamboohr-connection-properties",
"cdata.saphana": "sap-hana-connection-properties",
},
};
},
Expand Down
242 changes: 80 additions & 162 deletions resources/js/admin/settings/components/SettingDriverAuthorization.vue
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
<template>
<div>
<!-- Authorize badge -->
<div v-if="hasAuthorizedBadge">
<b-badge
pill
:variant="isAuthorized ? 'success' : 'warning'"
>
<span v-if="isAuthorized">{{ $t('Authorized') }}</span>
<span v-else>{{ $t('Not Authorized') }}</span>
<span v-if="isAuthorized">{{ $t("Authorized") }}</span>
<span v-else>{{ $t("Not Authorized") }}</span>
</b-badge>
</div>
<div v-else>
Empty
{{ $t("Empty") }}
</div>

<!-- Connection properties modal -->
<b-modal
v-model="showModal"
class="setting-object-modal"
Expand All@@ -24,110 +27,30 @@
class="d-block"
>
<div>
<h5
v-if="setting.name"
class="mb-0"
>
{{ $t(setting.name) }}
</h5>
<h5
v-else
class="mb-0"
>
{{ setting.key }}
<h5 class="mb-0">
<span v-if="setting.name">{{ $t(setting.name) }}</span>
<span v-else>{{ $t(setting.key) }}</span>
</h5>
<small class="form-text text-muted">{{ $t('Configure the driver connection properties.') }}</small>
<small class="form-text text-muted">
{{ $t("Configure the driver connection properties.") }}
</small>
</div>
<button
type="button"
:aria-label="$t('Close')"
class="close"
:aria-label="$t('Close')"
@click="onCancel"
>
×
&times;
</button>
</template>
<div>
<b-form-group
required
:label="$t('Client ID')"
:description="formDescription('The client ID assigned when you register your application.', 'client_id', errors)"
:invalid-feedback="errorMessage('client_id', errors)"
:state="errorState('client_id', errors)"
>
<b-form-input
v-model="formData.client_id"
required
autofocus
autocomplete="off"
:state="errorState('client_id', errors)"
name="client_id"
data-cy="client_id"
/>
</b-form-group>

<b-form-group
required
:label="$t('Client Secret')"
:description="formDescription('The client secret assigned when you register your application.', 'client_secret', errors)"
:invalid-feedback="errorMessage('client_secret', errors)"
:state="errorState('client_secret', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.client_secret"
required
autofocus
autocomplete="off"
trim
:type="type"
:state="errorState('client_secret', errors)"
name="client_secret"
data-cy="client_secret"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Toggle Show Password')"
variant="secondary"
@click="togglePassword"
>
<i
class="fas"
:class="icon"
/>
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>

<b-form-group
required
:label="$t('Redirect URL')"
:description="formDescription('This value must match the callback URL you specify in your app settings.', 'callback_url', errors)"
:invalid-feedback="errorMessage('callback_url', errors)"
:state="errorState('callback_url', errors)"
>
<b-input-group>
<b-form-input
v-model="formData.callback_url"
autofocus
readonly
autocomplete="off"
:state="errorState('callback_url', errors)"
name="callback_url"
data-cy="callback_url"
/>
<b-input-group-append>
<b-button
:aria-label="$t('Copy')"
variant="secondary"
@click="onCopy"
>
<i class="fas fa-copy" />
</b-button>
</b-input-group-append>
</b-input-group>
</b-form-group>
<component
:is="authSchemeToComponent(setting.config?.AuthScheme)"
:form-data="formData"
:auth-scheme="setting.config?.AuthScheme"
@updateFormData="updateFormData"
/>

<additional-driver-connection-properties
:driver-key="setting?.key"
Expand All@@ -145,20 +68,20 @@
data-cy="cancel-button"
@click="onCancel"
>
{{ $t('Cancel') }}
{{ $t("Cancel") }}
</button>
<button
type="button"
class="btn btn-secondary ml-3"
data-cy="authorize-button"
:disabled="isButtonDisabled"
@click="onSave"
>
{{ $t('Authorize') }}
{{ $t("Authorize") }}
</button>
</div>
</b-modal>

<!-- Authorizing modal -->
<b-modal
v-model="showAuthorizingModal"
class="setting-object-modal"
Expand All@@ -169,26 +92,33 @@
no-fade
>
<div class="text-center">
<h3>{{ $t('Connecting Driver') }}</h3>
<h3>{{ $t("Connecting Driver") }}</h3>
<i class="fas fa-circle-notch fa-spin fa-3x p-0 text-primary" />
</div>
</b-modal>
</div>
</template>

<script>
// eslint-disable-next-line import/no-unresolved
import { FormErrorsMixin, Required } from "SharedComponents";
import settingMixin from "../mixins/setting";
import AdditionalDriverConnectionProperties from "./AdditionalDriverConnectionProperties.vue";
import OauthConnectionProperties from "./cdata/OauthConnectionProperties.vue";
import NoneConnectionProperties from "./cdata/NoneConnectionProperties.vue";
import PasswordConnectionProperties from "./cdata/PasswordConnectionProperties.vue";

export default {
components: { AdditionalDriverConnectionProperties },
components: {
AdditionalDriverConnectionProperties,
OauthConnectionProperties,
NoneConnectionProperties,
PasswordConnectionProperties,
},
mixins: [settingMixin, FormErrorsMixin, Required],
props: {
setting: {
type: [Object, null],
default: null,
default: () => ({}),
},
value: {
type: Object,
Expand All@@ -198,19 +128,19 @@ export default {
data() {
return {
input: "",
formData: {
client_id: "",
client_secret: "",
callback_url: "",
},
formData: {},
selected: null,
showModal: false,
showAuthorizingModal: false,
transformed: null,
errors: {},
isInvalid: true,
type: "password",
resetData: true,
componentsMap: {
OAuth: "oauth-connection-properties",
None: "none-connection-properties",
Password: "password-connection-properties",
Basic: "password-connection-properties",
},
};
},
computed: {
Expand All@@ -227,57 +157,18 @@ export default {
}
return false;
},
changed() {
return JSON.stringify(this.formData) !== JSON.stringify(this.transformed);
},
icon() {
if (this.type === "password") {
return "fa-eye";
}
return "fa-eye-slash";
},
isButtonDisabled() {
return this.isInvalid || (this.isAuthorized && !this.changed);
},
},
watch: {
formData: {
handler() {
this.isInvalid = this.validateData();
},
deep: true,
},
},
mounted() {
if (this.value === null) {
this.resetFormData();
} else {
this.formData = this.value;
}
this.isInvalid = this.validateData();
this.transformed = this.copy(this.formData);
},
methods: {
onCopy() {
navigator.clipboard.writeText(this.formData.callback_url).then(() => {
ProcessMaker.alert(this.$t("The setting was copied to your clipboard."), "success");
}, () => {
ProcessMaker.alert(this.$t("The setting was not copied to your clipboard."), "danger");
});
},
togglePassword() {
if (this.type === "text") {
this.type = "password";
} else {
this.type = "text";
}
},
validateData() {
// Check if client_id and client_secret are empty
const clientIdEmpty = _.isEmpty(this.formData.client_id);
const clientSecretEmpty = _.isEmpty(this.formData.client_secret);

return _.isEmpty(this.formData) || clientIdEmpty || clientSecretEmpty;
authSchemeToComponent(scheme) {
return this.componentsMap[scheme] || null;
},
onCancel() {
this.showModal = false;
Expand All@@ -294,14 +185,48 @@ export default {
onModalHidden() {
this.resetFormData();
},
generateCallbackUrl(item) {
if (item.config.AuthScheme === "OAuth") {
const name = item.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
}
},
authorizeConnection() {
this.showAuthorizingModal = true;
this.showModal = false;
this.resetData = false;
ProcessMaker.apiClient.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)

if (this.setting.config.AuthScheme === "OAuth") {
this.authorizeOAuthConnection();
} else {
this.authorizeNoneConnection();
}
},
authorizeOAuthConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/get-oauth-url`, this.formData)
.then((response) => {
window.location = response.data?.url;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
})
.finally(() => {
this.showModal = true;
this.showAuthorizingModal = false;
});
},
authorizeNoneConnection() {
ProcessMaker.apiClient
.post(`settings/${this.setting.id}/authorize-driver`, this.formData)
.then((response) => {
window.location = response.data.url;
this.showModal = false;
this.showAuthorizingModal = true;
})
.catch((error) => {
const errorMessage = error.response?.data?.message || error.message;
ProcessMaker.alert(errorMessage, "danger");
Expand All@@ -310,18 +235,11 @@ export default {
});
},
onSave() {
const driver = this.setting.key.split("cdata.")[1];

this.formData.driver = driver;
this.formData.name = this.setting.config?.name;
this.formData.driver = this.setting.config?.driver;
this.transformed = { ...this.formData };
this.authorizeConnection();
},
generateCallbackUrl(data) {
const name = data.key.split("cdata.")[1];
const appUrl = document.head.querySelector("meta[name=\"app-url\"]").content;

this.formData.callback_url = `${appUrl}/external-integrations/${name}`;
},
resetFormData() {
if (this.resetData) {
this.formData = {
Expand Down
Loading