-
-
-
+
+
+
+
+
+
+
-
{{ page.name }}
+ {{ page.name }}
+
+
{{ page.url }}
diff --git a/components/apps/setting/permission/PermissionRow.vue b/components/apps/setting/permission/PermissionRow.vue
new file mode 100644
index 0000000..d762f5c
--- /dev/null
+++ b/components/apps/setting/permission/PermissionRow.vue
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
mdi-subdirectory-arrow-right
+
+
+
+
+
+ mdi-shield-account
+
+
+
+
+
+
+
{{ item.name }}
+
+
{{ item.url }}
+
+
+
+
+ onToggle('read', Boolean(v))"
+ />
+
+
+ onToggle('create', Boolean(v))"
+ />
+
+
+ onToggle('update', Boolean(v))"
+ />
+
+
+ onToggle('delete', Boolean(v))"
+ />
+
+
+
+
+
+
+
+
+
+
diff --git a/components/apps/setting/permission/RoleList.vue b/components/apps/setting/permission/RoleList.vue
new file mode 100644
index 0000000..f58d4f0
--- /dev/null
+++ b/components/apps/setting/permission/RoleList.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+ mdi-shield-account
+
+
+
+
+
+
{{ role.name }}
+
+
{{ formatDateTime(role.created_at) }}
+
+
+
+
+
+ mdi-account-cog
+
+
+
+
+
+
+
+ mdi-pencil
+
+
+
+
+
+
+
+ mdi-delete
+
+
+
+
+
+
+
+
+
+
diff --git a/components/apps/setting/permission/RoleModal.vue b/components/apps/setting/permission/RoleModal.vue
new file mode 100644
index 0000000..ae04170
--- /dev/null
+++ b/components/apps/setting/permission/RoleModal.vue
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+ {{ titleText }}
+
+ mdi-close
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ active ? 'Aktif' : 'Nonaktif' }}
+
+
+
+
+
+
+
+
+
+
+ {{ submitLabel }}
+
+
+
+
+
+
diff --git a/components/layout/full/vertical-sidebar/sidebarItem.ts b/components/layout/full/vertical-sidebar/sidebarItem.ts
index 144bc70..3586b75 100644
--- a/components/layout/full/vertical-sidebar/sidebarItem.ts
+++ b/components/layout/full/vertical-sidebar/sidebarItem.ts
@@ -33,6 +33,10 @@ const sidebarItem: menu[] = [
{
title: 'Menu',
to: '/apps/setting/pages',
+ },
+ {
+ title: 'Role Permission',
+ to: '/apps/setting/permission',
}
]
},
diff --git a/components/shared/AppRightDialog.vue b/components/shared/AppRightDialog.vue
new file mode 100644
index 0000000..c38b9eb
--- /dev/null
+++ b/components/shared/AppRightDialog.vue
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
diff --git a/components/shared/DialogConfrim.vue b/components/shared/DialogConfrim.vue
index 5464bbb..93d625b 100644
--- a/components/shared/DialogConfrim.vue
+++ b/components/shared/DialogConfrim.vue
@@ -76,6 +76,7 @@ const onConfirm = () => {
:color="props.cancelColor"
variant="tonal"
flat
+ prepend-icon="mdi-cancel"
:disabled="props.loading"
@click="onCancel"
>
@@ -86,6 +87,7 @@ const onConfirm = () => {
:color="props.confirmColor"
variant="tonal"
flat
+ prepend-icon="mdi-check"
:loading="props.loading"
:disabled="props.loading"
@click="onConfirm"
diff --git a/pages/apps/setting/pages/index.vue b/pages/apps/setting/pages/index.vue
index e34d956..b9d3b89 100644
--- a/pages/apps/setting/pages/index.vue
+++ b/pages/apps/setting/pages/index.vue
@@ -1,9 +1,10 @@
+
+
+
+
+
+
+ mdi-arrow-left
+
+
+
Role : {{ roleName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/apps/setting/permission/index.vue b/pages/apps/setting/permission/index.vue
new file mode 100644
index 0000000..bbbb11e
--- /dev/null
+++ b/pages/apps/setting/permission/index.vue
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
Role Permission
+
+
+
+ mdi-plus
+ Tambah Role
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/auth/Verify.vue b/pages/auth/Verify.vue
new file mode 100644
index 0000000..c379f37
--- /dev/null
+++ b/pages/auth/Verify.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
Verifying your session...
+
+ {{ errorMessage }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/redirect.html b/redirect.html
new file mode 100644
index 0000000..9dd570f
--- /dev/null
+++ b/redirect.html
@@ -0,0 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/server/api/auth/keycloak-callback.get.ts b/server/api/auth/keycloak-callback.get.ts
index 2163d14..a238cf8 100644
--- a/server/api/auth/keycloak-callback.get.ts
+++ b/server/api/auth/keycloak-callback.get.ts
@@ -154,7 +154,7 @@ export default defineEventHandler(async (event) => {
},
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
- // idToken: tokens.id_token,
+ idToken: tokens.id_token,
expiresAt: (nowInSeconds + sessionDurationSeconds) * 1000,
};
diff --git a/server/api/auth/sessionUser.get.ts b/server/api/auth/sessionUser.get.ts
index 4b2a6cc..cd59be6 100644
--- a/server/api/auth/sessionUser.get.ts
+++ b/server/api/auth/sessionUser.get.ts
@@ -43,7 +43,11 @@ export default defineEventHandler(async (event) => {
});
}
- return session;
+ return {
+ ...session,
+ payloadIdToken : session.idToken ? parseJwtPayload(session.idToken) : null,
+ payloadAccessToken: accessPayload
+ };
} catch (error: any) {
if (error?.statusCode) {
throw error;
diff --git a/types/setting/roleMaster.ts b/types/setting/roleMaster.ts
new file mode 100644
index 0000000..64678d2
--- /dev/null
+++ b/types/setting/roleMaster.ts
@@ -0,0 +1,28 @@
+export interface PaginationMeta {
+ page: number;
+ limit: number;
+ total: number;
+ total_pages: number;
+}
+
+export interface RoleMaster {
+ id: number;
+ name: string;
+ active: boolean;
+ created_at: string | null;
+ updated_at: string | null;
+ select?: boolean | null;
+}
+
+export interface RoleMasterResponse {
+ status: string;
+ message: string;
+ data: RoleMaster;
+}
+
+export interface RoleMasterListResponse {
+ status: string;
+ message: string;
+ data: RoleMaster[];
+ meta: PaginationMeta;
+}
diff --git a/types/setting/rolePermission.ts b/types/setting/rolePermission.ts
new file mode 100644
index 0000000..95adf84
--- /dev/null
+++ b/types/setting/rolePermission.ts
@@ -0,0 +1,32 @@
+export interface RoleAccessPermission {
+ id: number | null;
+ create: boolean;
+ read: boolean;
+ update: boolean;
+ delete: boolean;
+ disable: boolean;
+}
+
+export interface RoleAccessItem {
+ id: number;
+ name: string;
+ icon: string;
+ url: string;
+ group: string;
+ level: number;
+ sort: number;
+ active: boolean;
+ permission: RoleAccessPermission;
+ children?: RoleAccessItem[];
+}
+
+export interface RolePermissionData {
+ role: string;
+ access: RoleAccessItem[];
+}
+
+export interface RolePermissionResponse {
+ status: string;
+ message: string;
+ data: RolePermissionData;
+}
diff --git a/utils/api.ts b/utils/api.ts
index 436bdf9..eabb14b 100644
--- a/utils/api.ts
+++ b/utils/api.ts
@@ -1,6 +1,8 @@
import axios from 'axios'
+import { useAuth } from '~/composables/useAuth';
const config = useRuntimeConfig()
+const { sessionData } = useAuth();
const api = axios.create({
baseURL: config.public.baseUrl
@@ -33,7 +35,7 @@ const refreshAccessToken = async (): Promise
=> {
method: 'POST',
body: JSON.stringify({
refresh_token: refreshToken,
- provider: 'keycloak',
+ provider: sessionData.value?.user?.auth_provider || 'keycloak'
}),
})
diff --git a/utils/datetime.ts b/utils/datetime.ts
new file mode 100644
index 0000000..d8b3794
--- /dev/null
+++ b/utils/datetime.ts
@@ -0,0 +1,31 @@
+type DateInput = Date | string | number | null | undefined;
+
+const pad2 = (value: number) => String(value).padStart(2, '0');
+
+const toDate = (input: DateInput): Date | null => {
+ if (input == null) return null;
+
+ if (input instanceof Date) {
+ return Number.isNaN(input.getTime()) ? null : input;
+ }
+
+ const date = new Date(input);
+ return Number.isNaN(date.getTime()) ? null : date;
+};
+
+/**
+ * Format datetime to `d-m-Y H:i` (e.g. `07-04-2026 14:05`).
+ * Uses local time.
+ */
+export const formatDateTime = (input: DateInput, fallback = '-') => {
+ const date = toDate(input);
+ if (!date) return fallback;
+
+ const day = pad2(date.getDate());
+ const month = pad2(date.getMonth() + 1);
+ const year = String(date.getFullYear());
+ const hours = pad2(date.getHours());
+ const minutes = pad2(date.getMinutes());
+
+ return `${day}-${month}-${year} ${hours}:${minutes}`;
+};