diff --git a/docs/json/process-builder/processDefinition.json b/docs/json/process-builder/processDefinition.json
index 7594382..7c199c0 100644
--- a/docs/json/process-builder/processDefinition.json
+++ b/docs/json/process-builder/processDefinition.json
@@ -101,7 +101,8 @@
"inputMappings": [],
"assignmentType": "roles",
"outputMappings": [
- { "formField": "kategori_asnaf", "processVariable": "kategoriAsnaf" }
+ { "formField": "kategori_asnaf", "processVariable": "kategoriAsnaf" },
+ { "formField": "nama_asnaf", "processVariable": "namaAsnaf" }
],
"fieldConditions": [],
"assignmentVariable": "",
@@ -121,7 +122,10 @@
{
"id": "api-1752550319410",
"data": {
- "body": { "data": "{ \"title\" : \"{todoTitle}\"}", "type": "raw" },
+ "body": {
+ "data": "{ \"kategori_asnaf\" : \"{kategoriAsnaf}\", \"nama_asnaf\" : \"{namaAsnaf}\"}",
+ "type": "raw"
+ },
"label": "API Call",
"shape": "rectangle",
"apiUrl": "https://jsonplaceholder.typicode.com/posts",
@@ -146,7 +150,7 @@
"id": "script-1752550430989",
"data": {
"label": "Script Task",
- "scriptCode": "// Assign API response title to process variable\nprocessVariables.todoTitle = processVariables.apiResponse?.title || '';\n// You can add more logic here\n",
+ "scriptCode": "// Map API response to process variables\nconst api = processVariables.apiResponse || {};\nprocessVariables.todoTitle = api.kategori_asnaf || '';\nprocessVariables.namaAsnaf = api.nama_asnaf || '';\nprocessVariables.todoStatus = api.id > 100; // true if id > 100, otherwise false\n\n// New logic: Calculate a score\nconst katLen = (api.kategori_asnaf || '').length;\nconst namaLen = (api.nama_asnaf || '').length;\nprocessVariables.asnafScore = katLen * 10 + namaLen * 5;\n\n// New logic: Add a timestamp\nprocessVariables.resultTimestamp = new Date().toISOString();\n\n// New logic: Create a summary string\nprocessVariables.resultSummary = `Asnaf: ${processVariables.todoTitle}, Nama: ${processVariables.namaAsnaf}, Score: ${processVariables.asnafScore}, Time: ${processVariables.resultTimestamp}`;\n",
"description": "Execute JavaScript code",
"inputVariables": ["apiResponse"],
"scriptLanguage": "javascript",
@@ -155,6 +159,31 @@
"name": "todoTitle",
"type": "string",
"description": "Title from API response"
+ },
+ {
+ "name": "namaAsnaf",
+ "type": "string",
+ "description": "Nama Asnaf from API response"
+ },
+ {
+ "name": "todoStatus",
+ "type": "boolean",
+ "description": "Todo Status from API response"
+ },
+ {
+ "name": "asnafScore",
+ "type": "number",
+ "description": "Calculated Asnaf Score"
+ },
+ {
+ "name": "resultTimestamp",
+ "type": "string",
+ "description": "Result Timestamp"
+ },
+ {
+ "name": "resultSummary",
+ "type": "string",
+ "description": "Result Summary"
}
]
},
@@ -166,12 +195,16 @@
"id": "html-1752550500000",
"data": {
"label": "Show Result",
- "jsCode": "",
- "cssCode": ".result-card { background: #f9fafb; border: 1px solid #ddd; border-radius: 8px; padding: 16px; max-width: 400px; margin: 24px auto; }",
- "htmlCode": "
\n
API Result
\n
Todo Title: {{ processVariables.todoTitle }}
\n
",
+ "shape": "rectangle",
+ "jsCode": "const completed = \"{{todoStatus}}\" === 'true' || \"{{todoStatus}}\" === true;\ndocument.getElementById('todo-title').innerText = \"{{todoTitle}}\";\ndocument.getElementById('todo-status').innerText = completed ? 'Completed ✅' : 'Not Completed ❌';\ndocument.getElementById('todo-status').className = completed ? 'done' : 'not-done';",
+ "cssCode": ".result-box {\n background-color: #ffffff;\n border-radius: 12px;\n box-shadow: 0 8px 16px rgba(0,0,0,0.1);\n padding: 30px 40px;\n max-width: 600px;\n text-align: center;\n }\n .result-box h2 {\n color: #333;\n margin-bottom: 20px;\n }\n .result-box p {\n font-size: 1.2em;\n color: #555;\n }\n .done {\n color: green;\n font-weight: bold;\n }\n .not-done {\n color: red;\n font-weight: bold;\n }",
+ "htmlCode": "\n
Asnaf Result
\n
Title: {{todoTitle}}
\n
Status: {{todoStatus}}
\n
Nama: {{namaAsnaf}}
\n
Score: {{asnafScore}}
\n
Timestamp: {{resultTimestamp}}
\n
Summary: {{resultSummary}}
\n
",
+ "textColor": "#333333",
"autoRefresh": true,
+ "borderColor": "#dddddd",
"description": "Display the todo title from API",
- "inputVariables": ["todoTitle"],
+ "inputVariables": ["todoTitle", "todoStatus"],
+ "backgroundColor": "#ffffff",
"outputVariables": [],
"allowVariableAccess": true
},
@@ -206,8 +239,8 @@
}
],
"viewport": {
- "x": -118.4524312896406,
- "y": 314.4180761099366,
- "zoom": 0.6437632135306554
+ "x": -193.044397463002,
+ "y": 197.8681289640592,
+ "zoom": 1.049154334038055
}
}
diff --git a/docs/json/process-builder/processVariables.json b/docs/json/process-builder/processVariables.json
index b59e613..ad70996 100644
--- a/docs/json/process-builder/processVariables.json
+++ b/docs/json/process-builder/processVariables.json
@@ -6,6 +6,18 @@
"value": null,
"description": "API error from API Call"
},
+ "namaAsnaf": {
+ "name": "namaAsnaf",
+ "type": "string",
+ "scope": "global",
+ "description": ""
+ },
+ "todoTitle": {
+ "name": "todoTitle",
+ "type": "string",
+ "scope": "global",
+ "description": "Title from API response"
+ },
"apiResponse": {
"name": "apiResponse",
"type": "object",
@@ -19,10 +31,28 @@
"scope": "global",
"description": ""
},
- "todoTitle": {
- "name": "todoTitle",
+ "todoStatus": {
+ "name": "todoStatus",
+ "type": "boolean",
+ "scope": "global",
+ "description": "Completion status (true if id > 100)"
+ },
+ "asnafScore": {
+ "name": "asnafScore",
+ "type": "number",
+ "scope": "global",
+ "description": "Calculated score based on input lengths"
+ },
+ "resultTimestamp": {
+ "name": "resultTimestamp",
"type": "string",
"scope": "global",
- "description": "Title from API response"
+ "description": "Timestamp when script ran"
+ },
+ "resultSummary": {
+ "name": "resultSummary",
+ "type": "string",
+ "scope": "global",
+ "description": "Summary string for result"
}
}
diff --git a/navigation/index.js b/navigation/index.js
index 7747978..034cfbd 100644
--- a/navigation/index.js
+++ b/navigation/index.js
@@ -12,6 +12,23 @@ export default [
},
],
},
+ // {
+ // header: "BPM",
+ // description: "Manage your BPM application",
+ // child: [
+ // {
+ // title: "Form",
+ // icon: "material-symbols:dynamic-form",
+ // path: "http://localhost:3000/workflow/7f024ce2-ce5d-43af-a18e-8e10d390e32b",
+ // external: true,
+ // },
+ // ],
+ // meta: {
+ // auth: {
+ // role: ["Developer"],
+ // },
+ // },
+ // },
{
header: "Design & Build",
description: "Create and design your workflows and forms",
diff --git a/pages/devtool/menu-editor/index.vue b/pages/devtool/menu-editor/index.vue
index edcbce9..d8c3760 100644
--- a/pages/devtool/menu-editor/index.vue
+++ b/pages/devtool/menu-editor/index.vue
@@ -33,6 +33,7 @@ const showModalEditForm = ref({
name: "",
path: "",
guardType: "",
+ external: false,
});
// const showModalEditEl = ref(null);
@@ -41,6 +42,7 @@ const showModalAddForm = ref({
title: "",
name: "",
path: "",
+ external: false,
});
const systemPages = [
@@ -120,6 +122,7 @@ const openModalEdit = (menu) => {
showModalEditForm.value.path = menu.path;
}
+ showModalEditForm.value.external = menu.external === true;
showModalEditPath.value = menu.path;
showModalEdit.value = true;
@@ -140,6 +143,33 @@ const saveEditMenu = async () => {
showModalEditForm.value.title = showModalEditForm.value.title.trim();
showModalEditForm.value.name = showModalEditForm.value.name.trim();
+ // Path validation and formatting
+ let path = showModalEditForm.value.path.trim();
+ if (showModalEditForm.value.external) {
+ // Validate as URL
+ if (!/^https?:\/\//.test(path)) {
+ nuxtApp.$swal.fire({
+ title: "Error",
+ text: "External URL must start with http:// or https://",
+ icon: "error",
+ });
+ return;
+ }
+ } else {
+ // Internal path validation
+ if (!/^[a-z0-9/-]+$/.test(path)) {
+ nuxtApp.$swal.fire({
+ title: "Error",
+ text: "Path contains invalid characters or spacing before or after. Only letters, numbers, dashes, and underscores are allowed.",
+ icon: "error",
+ });
+ return;
+ }
+ if (!path.startsWith("/")) {
+ path = "/" + path;
+ }
+ }
+
const res = await useFetch("/api/devtool/menu/edit", {
method: "POST",
initialCache: false,
@@ -148,9 +178,9 @@ const saveEditMenu = async () => {
formData: {
title: showModalEditForm.value.title || "",
name: showModalEditForm.value.name || "",
- path: "/" + showModalEditForm.value.path || "",
+ path: path || "",
+ external: showModalEditForm.value.external === true,
},
- // formData: showModalEditForm.value,
}),
});
@@ -175,6 +205,7 @@ const openModalAdd = () => {
showModalAddForm.value.title = "";
showModalAddForm.value.name = "";
showModalAddForm.value.path = "";
+ showModalAddForm.value.external = false;
showModalAdd.value = true;
};
@@ -194,6 +225,33 @@ const saveAddMenu = async () => {
showModalAddForm.value.title = showModalAddForm.value.title.trim();
showModalAddForm.value.name = showModalAddForm.value.name.trim();
+ // Path validation and formatting
+ let path = showModalAddForm.value.path.trim();
+ if (showModalAddForm.value.external) {
+ // Validate as URL
+ if (!/^https?:\/\//.test(path)) {
+ nuxtApp.$swal.fire({
+ title: "Error",
+ text: "External URL must start with http:// or https://",
+ icon: "error",
+ });
+ return;
+ }
+ } else {
+ // Internal path validation
+ if (!/^[a-z0-9/-]+$/.test(path)) {
+ nuxtApp.$swal.fire({
+ title: "Error",
+ text: "Path contains invalid characters or spacing before or after. Only letters, numbers, dashes, and underscores are allowed.",
+ icon: "error",
+ });
+ return;
+ }
+ if (!path.startsWith("/")) {
+ path = "/" + path;
+ }
+ }
+
const res = await useFetch("/api/devtool/menu/add", {
method: "POST",
initialCache: false,
@@ -201,9 +259,9 @@ const saveAddMenu = async () => {
formData: {
title: showModalAddForm.value.title || "",
name: showModalAddForm.value.name || "",
- path: "/" + showModalAddForm.value.path || "",
+ path: path || "",
+ external: showModalAddForm.value.external === true,
},
- // formData: showModalAddForm.value
}),
});
@@ -691,8 +749,8 @@ watch(
}"
v-model="showModalEditForm.title"
/>
-
+
+
Cancel
@@ -742,8 +818,8 @@ watch(
}"
v-model="showModalAddForm.title"
/>
-
+
+
Cancel
diff --git a/pages/process-builder/manage.vue b/pages/process-builder/manage.vue
index c950cee..ef9f909 100644
--- a/pages/process-builder/manage.vue
+++ b/pages/process-builder/manage.vue
@@ -2,6 +2,7 @@
import { ref, computed, onMounted, watch, onUnmounted } from 'vue';
import { useProcessBuilderStore } from '~/stores/processBuilder';
import { useRouter } from 'vue-router';
+import { useToast } from '~/composables/useToast';
// Define page meta
definePageMeta({
@@ -15,6 +16,7 @@ definePageMeta({
// Initialize the store and router
const processStore = useProcessBuilderStore();
const router = useRouter();
+const toast = useToast();
// State
const searchQuery = ref('');
@@ -334,6 +336,17 @@ onMounted(async () => {
onUnmounted(() => {
clearTimeout(searchTimeout);
});
+
+// Copy workflow run link to clipboard
+const copyWorkflowLink = async (processId) => {
+ try {
+ const link = `${window.location.origin}/workflow/${processId}`;
+ await navigator.clipboard.writeText(link);
+ toast.success('Run link copied to clipboard!');
+ } catch (err) {
+ toast.error('Failed to copy link');
+ }
+};
@@ -651,14 +664,15 @@ onUnmounted(() => {
-
+
diff --git a/pages/workflow/[id].vue b/pages/workflow/[id].vue
index ea896dd..07fda72 100644
--- a/pages/workflow/[id].vue
+++ b/pages/workflow/[id].vue
@@ -1,5 +1,5 @@