Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# Changelog

## [Unreleased]
## [3.9.2] - 2026-06-02

## [3.9.0] - 2026-06-29
### Added
- Implemetned new role for dashboard access permission

## [3.9.0] - 2026-05-29

### Added
- Implemetned new design for role permission
- Implemented tenant access list

## [3.8.0] - 2026-06-29
## [3.8.0] - 2026-05-29

### Fixed
- Handled permission denied issue properly
Expand All @@ -19,7 +23,7 @@
- Implemented remove authenticator
- Implemented categorized role

## [3.7.32] - 2026-06-20
## [3.7.32] - 2026-05-20


### Fixed
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "entity-developer-dashboard",
"version": "3.9.1",
"version": "3.10.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --mode production",
Expand Down
4 changes: 4 additions & 0 deletions src/components/settings/UserProfile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,10 @@ export default {
text = "ID Service";
break;
}
case config.SERVICE_TYPES.DASHBOARD: {
text = "Dashboard Service";
break;
}
case config.SERVICE_TYPES.QUEST: {
text = "Quest Service";
break;
Expand Down
167 changes: 154 additions & 13 deletions src/components/teams/AdminTeams.vue
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,52 @@
</div>
</div>
</b-tab>
<b-tab v-if="hasDashboardService" title="Dashboard Service">
<div class="role-cards-grid">
<button
v-for="role in DASHBOARD_PREDEFINED_ROLES"
:key="role.key"
type="button"
class="role-card"
:class="{ 'role-card-selected': selectedRoles.dashboard === role.key }"
@click="selectPredefinedRole(role, 'dashboard')"
>
<div class="role-card-header">
<div class="role-card-title">
<v-icon small class="role-card-icon">{{ role.icon }}</v-icon>
{{ role.name }}
</div>
<b-badge variant="info">{{ role.badge }}</b-badge>
</div>
<div class="role-card-desc">{{ role.description }}</div>
<div class="role-card-footer">
<small>{{ role.recommendedFor }}</small>
<small>{{ role.permissions.length }} permissions</small>
</div>
</button>
</div>
<div v-if="selectedRoles.dashboard === 'custom'" class="perm-scroll-box">
<div v-for="eachService in dashboardServices" :key="eachService.id" class="perm-service-block">
<div class="perm-service-header">{{ eachService.name }}</div>
<div v-for="group in getPermSubGroups(eachService.id, eachService.accessList)" :key="group.label" class="perm-group-block">
<div class="perm-group-label">{{ group.label }}</div>
<div class="form-check perm-item" v-for="perm in group.permissions" :key="perm">
<input
class="form-check-input"
type="checkbox"
:value="{ serviceType: eachService.id, access: perm }"
v-on:change="onCheck($event, eachService)"
:checked="checkIfAccessIsThereInThatService(perm, eachService.id)"
:disabled="isReadForcedByWrite(perm, eachService.id) || isWriteForcedByHigherPerm(perm, eachService.id) || isUpdateForcedByDelete(perm, eachService.id)"
>
<label class="form-check-label" :class="{ 'perm-forced': isReadForcedByWrite(perm, eachService.id) || isWriteForcedByHigherPerm(perm, eachService.id) || isUpdateForcedByDelete(perm, eachService.id) }">
<code>{{ perm }}</code>
</label>
</div>
</div>
</div>
</div>
</b-tab>
</b-tabs>
</b-form-group>

Expand Down Expand Up @@ -338,6 +384,45 @@ const SSI_PREDEFINED_ROLES = [
}
];

const DASHBOARD_PREDEFINED_ROLES = [
{
key: "dashboard_viewer",
name: "Viewer",
icon: "mdi-eye-outline",
description: "Read-only access to Dashboard service operations.",
badge: "Read-only",
recommendedFor: "Stakeholders who need Dashboard visibility",
permissions: ["READ_SERVICE"]
},
{
key: "dashboard_editor",
name: "Editor",
icon: "mdi-pencil-outline",
description: "Access to read and modify Dashboard service configuration.",
badge: "Edit access",
recommendedFor: "Operations or product teams",
permissions: ["READ_SERVICE", "WRITE_SERVICE", "UPDATE_SERVICE", "DELETE_SERVICE"]
},
{
key: "dashboard_admin",
name: "Admin",
icon: "mdi-shield-star-outline",
description: "Full Dashboard service access.",
badge: "Full access",
recommendedFor: "Dashboard administrators",
permissions: ["ALL"]
},
{
key: "custom",
name: "Custom Role",
icon: "mdi-tune-variant",
description: "Start with no permissions and configure manually.",
badge: "Advanced",
recommendedFor: "Custom enterprise setups",
permissions: []
}
];

export default {
name: "AdminTeams",
components: {
Expand All @@ -352,6 +437,9 @@ export default {
SSI_PREDEFINED_ROLES() {
return SSI_PREDEFINED_ROLES;
},
DASHBOARD_PREDEFINED_ROLES() {
return DASHBOARD_PREDEFINED_ROLES;
},
categorizedServices() {
const ssiServices = this.localAllServices.filter(s => s.id === config.SERVICE_TYPES.SSI_API);
const idServices = this.localAllServices.filter(
Expand All @@ -371,17 +459,25 @@ export default {
hasSSIService() {
return this.localAllServices.some(s => s.id === config.SERVICE_TYPES.SSI_API);
},
hasDashboardService() {
return this.localAllServices.some(s => s.id === config.SERVICE_TYPES.DASHBOARD);
},
hasIDService() {
return this.localAllServices.some(
s => s.id !== config.SERVICE_TYPES.SSI_API && s.id !== config.SERVICE_TYPES.QUEST
s => s.id !== config.SERVICE_TYPES.SSI_API && s.id !== config.SERVICE_TYPES.QUEST && s.id !== config.SERVICE_TYPES.DASHBOARD
);
},
ssiServices() {
return this.localAllServices.filter(s => s.id === config.SERVICE_TYPES.SSI_API);
},
dashboardServices() {
return this.localAllServices.filter(s => s.id === config.SERVICE_TYPES.DASHBOARD);
},
idServices() {
return this.localAllServices.filter(
s => s.id !== config.SERVICE_TYPES.SSI_API && s.id !== config.SERVICE_TYPES.QUEST
s => s.id !== config.SERVICE_TYPES.SSI_API &&
s.id !== config.SERVICE_TYPES.QUEST &&
s.id !== config.SERVICE_TYPES.DASHBOARD
);
}
},
Expand Down Expand Up @@ -411,7 +507,8 @@ export default {
checked: true,
selectedRoles: {
id: 'viewer',
ssi: 'auditor'
ssi: 'auditor',
dashboard: 'custom'
}
}
},
Expand Down Expand Up @@ -450,9 +547,17 @@ export default {
{ label: 'Document', icon: 'mdi-file-document-outline', iconColor: '#92400e', keys: ['READ_DOCUMENT', 'VERIFY_DOCUMENT'] },
{ label: 'Compliance', icon: 'mdi-shield-check-outline', iconColor: '#1d4ed8', keys: ['READ_COMPLIANCE'] },
];
const groupDefs = serviceId === config.SERVICE_TYPES.SSI_API ? SSI_GROUPS : ID_GROUPS;
const DASHBOARD_GROUPS = [
{ label: 'General', icon: 'mdi-star-outline', iconColor: '#6366f1', keys: ['ALL'] },
{ label: 'Dashboard Controls', icon: 'mdi-view-dashboard-outline', iconColor: '#0f766e', keys: ['READ_SERVICE', 'WRITE_SERVICE', 'UPDATE_SERVICE', 'DELETE_SERVICE'] }
];
const groupDefs = serviceId === config.SERVICE_TYPES.SSI_API
? SSI_GROUPS
: serviceId === config.SERVICE_TYPES.DASHBOARD
? DASHBOARD_GROUPS
: ID_GROUPS;
const assignedKeys = new Set();
const result = [];
const result = [];
for (const group of groupDefs) {
const perms = group.keys.filter(k => allKeys.includes(k));
if (perms.length) {
Expand Down Expand Up @@ -491,6 +596,10 @@ export default {
const ssiDefault = SSI_PREDEFINED_ROLES.find(r => r.key === 'auditor');
if (ssiDefault) this.selectPredefinedRole(ssiDefault, 'ssi');
}
if (this.hasDashboardService) {
const dashboardDefault = DASHBOARD_PREDEFINED_ROLES.find(r => r.key === 'dashboard_viewer');
if (dashboardDefault) this.selectPredefinedRole(dashboardDefault, 'dashboard');
}
},


Expand All @@ -509,22 +618,45 @@ export default {
this.roleModel = { ...role };
this.selectedRoles.id = this.detectSelectedPredefinedRole('id');
this.selectedRoles.ssi = this.detectSelectedPredefinedRole('ssi');
this.selectedRoles.dashboard = this.detectSelectedPredefinedRole('dashboard');
this.$root.$emit("bv::toggle::collapse", "sidebar-right");
},
getServiceIdsByType(serviceType) {
return (serviceType === 'ssi' ? this.ssiServices : this.idServices).map(s => s.id);
if (serviceType === 'ssi') return this.ssiServices.map(s => s.id);
if (serviceType === 'dashboard') return this.dashboardServices.map(s => s.id);
return this.idServices.map(s => s.id);
},
getAllPermissionKeysForServiceType(serviceType) {
const targetServices = serviceType === 'ssi'
? this.ssiServices
: serviceType === 'dashboard'
? this.dashboardServices
: this.idServices;
const keys = new Set();
targetServices.forEach(service => {
if (!service?.accessList) return;
Object.keys(service.accessList).forEach(key => keys.add(key));
});
return Array.from(keys);
},
detectSelectedPredefinedRole(serviceType) {
const serviceIds = new Set(this.getServiceIdsByType(serviceType));
const currentPermissions = (this.roleModel.permissions || [])
.filter(p => serviceIds.has(p.serviceType))
.map(p => p.access);
const currentSet = new Set(currentPermissions);
const roles = (serviceType === 'ssi' ? SSI_PREDEFINED_ROLES : PREDEFINED_ROLES).filter(r => r.key !== 'custom');
const allServicePermissions = new Set(this.getAllPermissionKeysForServiceType(serviceType));
const roles = serviceType === 'ssi'
? SSI_PREDEFINED_ROLES
: serviceType === 'dashboard'
? DASHBOARD_PREDEFINED_ROLES
: PREDEFINED_ROLES;
const filteredRoles = roles.filter(r => r.key !== 'custom');
if (!currentSet.size) return 'custom';
const matched = roles.find(role => {
const matched = filteredRoles.find(role => {
if (role.permissions.includes('ALL')) {
return currentSet.has('ALL');
if (currentSet.has('ALL')) return true;
return Array.from(allServicePermissions).every(access => currentSet.has(access));
}
if (role.permissions.length !== currentSet.size) return false;
return role.permissions.every(p => currentSet.has(p));
Expand All @@ -545,11 +677,20 @@ export default {
},
buildPermissionsForRole(role, serviceType) {
const permissions = [];
const targetServices = serviceType === 'ssi' ? this.ssiServices : this.idServices;
const targetServices = serviceType === 'ssi'
? this.ssiServices
: serviceType === 'dashboard'
? this.dashboardServices
: this.idServices;

targetServices.forEach(service => {
if (role.permissions.includes('ALL') && service.accessList.ALL !== undefined) {
permissions.push({ serviceType: service.id, access: 'ALL' });
if (!service?.accessList) return;
if (role.permissions.includes('ALL')) {
Object.keys(service.accessList).forEach(access => {
if (service.accessList[access] !== undefined) {
permissions.push({ serviceType: service.id, access });
}
});
return;
}
role.permissions.forEach(access => {
Expand Down Expand Up @@ -725,7 +866,7 @@ export default {
],
"servicePermissions": []
}
this.selectedRoles = { id: 'viewer', ssi: 'auditor' };
this.selectedRoles = { id: 'viewer', ssi: 'auditor', dashboard: 'custom' };
this.edit = false;
},
},
Expand Down
10 changes: 10 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ config['SERVICE_TYPES'] = Object.freeze({
SSI_API: 'SSI_API',
CAVACH_API: 'CAVACH_API',
QUEST: 'QUEST',
DASHBOARD: 'DASHBOARD',
})

config['DASHBOARD_ACCESS'] = Object.freeze({
ALL: 'ALL',
READ_SERVICE: 'READ_SERVICE',
WRITE_SERVICE: 'WRITE_SERVICE',
UPDATE_SERVICE: 'UPDATE_SERVICE',
DELETE_SERVICE: 'DELETE_SERVICE',

})

config['GRANT_TYPES_ENUM'] = Object.freeze({
Expand Down
1 change: 0 additions & 1 deletion src/store/mainStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,6 @@ const mainStore = {
let resp = await RequestHandler(url, 'GET', {}, headers)

if (resp) {
resp = resp.filter(x => !(x.id == 'DASHBOARD'))
commit('insertAllServices', resp);
} else {
return null
Expand Down
Loading