Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
530e96f
feat(APP-942): finish permissions graph view
thekidnamedkd Jul 14, 2026
bca64b9
feat(APP-942): finish permissions graph view
thekidnamedkd Jul 15, 2026
28692f6
test(APP-942): cover permissions graph data paths
thekidnamedkd Jul 15, 2026
b96f324
chore(APP-942): add changeset
thekidnamedkd Jul 15, 2026
d784af2
fix(APP-942): show unrecognized graph conditions
thekidnamedkd Jul 15, 2026
751552a
fix(APP-942): address permissions review feedback
thekidnamedkd Jul 15, 2026
716b143
feat(APP-942): refine permissions graph interactions
thekidnamedkd Jul 15, 2026
10b82fc
fix(APP-942): polish graph node details
thekidnamedkd Jul 15, 2026
32d635e
fix(APP-942): align graph detail panels
thekidnamedkd Jul 15, 2026
16aa8e0
fix(APP-942): simplify graph address cards
thekidnamedkd Jul 15, 2026
77bea0f
fix(APP-942): filter noisy permission rows
thekidnamedkd Jul 16, 2026
157fddf
fix(APP-942): align subplugin filter with backend fields
thekidnamedkd Jul 17, 2026
5e758c5
fix(APP-942): detect subplugin permission addresses
thekidnamedkd Jul 17, 2026
9fddbd5
fix(APP-942): simplify permission graph modes
thekidnamedkd Jul 17, 2026
7e7c97c
fix(APP-942): disable inactive permission filters
thekidnamedkd Jul 17, 2026
8744b1c
fix(APP-942): align other graph layout
thekidnamedkd Jul 17, 2026
9f93552
fix(APP-942): preserve permission graph controls in URL
thekidnamedkd Jul 17, 2026
c1121df
fix(APP-942): straighten self permission edges
thekidnamedkd Jul 17, 2026
0b08ddc
fix(APP-942): align permission stack handles
thekidnamedkd Jul 17, 2026
56f228c
fix(APP-942): resolve rebase type check
thekidnamedkd Jul 17, 2026
c170830
feat(APP-1003): iterate on graph view from permissions feedback
thekidnamedkd Jul 23, 2026
7007c88
Merge remote-tracking branch 'origin/main' into app-942-permissions-g…
thekidnamedkd Jul 24, 2026
e7d48be
fix(APP-1003): align spp simulation mock shape
thekidnamedkd Jul 24, 2026
ac7f94f
Refine permissions graph frontend copy
thekidnamedkd Jul 27, 2026
b76f685
Rename supporting permissions switch
thekidnamedkd Jul 27, 2026
0cd32eb
Prefer enriched permission entities
thekidnamedkd Jul 28, 2026
9df90e6
Mark historical graph plugins
thekidnamedkd Jul 28, 2026
690304b
Restore permissions graph trident layout
thekidnamedkd Jul 28, 2026
c4364a5
Filter inactive permission graph plugins
thekidnamedkd Jul 28, 2026
624681c
Refine permission graph routing
thekidnamedkd Jul 28, 2026
4333ac5
Fix permissions graph routing feedback
thekidnamedkd Jul 29, 2026
089da6a
Merge remote-tracking branch 'origin/main' into app-942-permissions-g…
thekidnamedkd Jul 30, 2026
c1f622e
Add proposal-creator nodes to permissions graph
thekidnamedkd Jul 30, 2026
b9d4f73
Subsume specific create-proposal creators when Anyone can propose
thekidnamedkd Jul 30, 2026
58220ba
feat(APP-942): Show members icon for Anyone entities in permissions list
thekidnamedkd Jul 30, 2026
84881a3
refactor(APP-942): Reuse kit definition-list affordances and simplify…
thekidnamedkd Jul 31, 2026
91e7e6b
fix(APP-942): Render condition details address-first with explorer li…
thekidnamedkd Jul 31, 2026
78adf04
refactor(APP-942): Extract shared draggable panel, info tooltip, and …
thekidnamedkd Jul 31, 2026
675a7f9
fix(APP-942): Show the same permission rows in list and graph
thekidnamedkd Jul 31, 2026
2dabc2b
fix(APP-942): Gate page behind flag and reduce filter to two explicit…
thekidnamedkd Jul 31, 2026
69d6059
refactor(APP-942): Lazy-load graph bundle and drop vestigial edge pas…
thekidnamedkd Jul 31, 2026
d842b73
refactor(APP-942): Key governing-body actor nodes per who-where so on…
thekidnamedkd Jul 31, 2026
337b172
refactor(APP-942): Single DAO fetch, direct toggle-availability check…
thekidnamedkd Jul 31, 2026
757d2bf
refactor(APP-942): Prefix permission-condition slot id with SETTINGS_
thekidnamedkd Jul 31, 2026
6f7bfbc
refactor(APP-942): Share permission-row resolution across list breakp…
thekidnamedkd Jul 31, 2026
61dfa3b
refactor(APP-942): Source execute-permission name from the shared reg…
thekidnamedkd Jul 31, 2026
1836f44
refactor(APP-942): Name permission entity unions as exported domain t…
thekidnamedkd Jul 31, 2026
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
5 changes: 5 additions & 0 deletions .changeset/app-1003-iterate-graph-view.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@aragon/app": minor
---

Add the DAO settings permissions page with list and graph views of the DAO's on-chain permissions, filterable by the hide-DAO-grants and hide-governing-body toggles.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ next-env.d.ts
.vscode
.zed
.tempor
.omp/
.agents/**
!.agents/shared/
!.agents/shared/**
Expand Down
2 changes: 1 addition & 1 deletion apps/app/docs/slots/settings-slots.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ The settings module defines 5 slots for plugin-specific governance settings, upd
| `SETTINGS_BUILD_PREPARE_PLUGIN_UPDATE_DATA` | Function | Builds the transaction data for preparing a plugin update | Token Voting, Multisig, Lock To Vote |
| `SETTINGS_GET_UNINSTALL_HELPERS` | Function | Returns helper data needed for uninstalling the plugin | Token Voting, Multisig, Lock To Vote, SPP |
| `SETTINGS_PLUGIN_TO_FORM_DATA` | Function | Converts plugin settings to form-compatible data for the settings editor | Token Voting, Lock To Vote |
| `PERMISSION_CONDITION` | Component | Renders the condition-specific detail for a permission grant (routed by condition type) | Condition registry |
| `SETTINGS_PERMISSION_CONDITION` | Component | SETTINGS_PERMISSION_CONDITION | Condition registry |
2 changes: 2 additions & 0 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@aragon/assistant-chat": "workspace:*",
"@aragon/assistant-contracts": "workspace:*",
"@aragon/gov-ui-kit": "catalog:",
"@dagrejs/dagre": "^3.0.0",
"@floating-ui/react": "^0.27.20",
"@number-flow/react": "^0.6.1",
"@plausible-analytics/tracker": "^0.4.5",
Expand All @@ -50,6 +51,7 @@
"@vercel/speed-insights": "^2.0.0",
"@walletconnect/core": "^2.23.10",
"@walletconnect/utils": "^2.23.10",
"@xyflow/react": "^12.11.2",
"classnames": "catalog:",
"deepmerge-ts": "^7.1.5",
"framer-motion": "^12.42.2",
Expand Down
62 changes: 56 additions & 6 deletions apps/app/src/assets/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3560,15 +3560,64 @@
"title": "Permissions",
"description": "All permissions configured in this DAO — who has access, to which contract, and under what conditions."
},
"error": {
"title": "Unable to load permissions",
"description": "We couldn't load this DAO's permissions. Please try again later.",
"action": "Back to settings"
},
"view": {
"list": "List",
"graph": "Graph"
},
"filters": {
"hideDaoPermissions": "Hide permissions granted to DAO",
"hideDaoPermissionsTooltip": "Hides permissions where the selected DAO appears under Who, including DAO-managed internal contracts such as clocks.",
"hideDaoPermissionsTooltipLabel": "About permissions granted to DAO",
"hideGoverningBodyPermissions": "Hide permissions on governing bodies",
"hideGoverningBodyPermissionsTooltip": "Hides permissions whose target is a governing body — a plugin nested inside another (subplugin).",
"hideGoverningBodyPermissionsTooltipLabel": "About governing body permissions"
},
"graphView": {
"node": {
"dao": "Primary DAO",
"linkedDao": "Linked DAO",
"plugin": "Aragon OSx Plugin",
"historicalPlugin": "Historical plugin",
"uninstalledPlugin": "Uninstalled plugin",
"actor": "Address",
"who": "Who",
"where": "Where"
},
"edge": {
"condition": "if {{condition}}"
},
"detail": {
"address": "Address",
"close": "Close",
"type": "Type",
"anyone": {
"title": "Open to anyone",
"description": "Any wallet or contract can act here — these permission flows have no on-chain requirement for who calls them."
}
},
"empty": {
"heading": "No permissions",
"description": "This view has no indexed permissions to visualize."
},
"fullscreen": {
"open": "Expand graph",
"close": "Exit full screen"
}
}
},
"permissionsList": {
"header": {
"who": "Who",
"where": "Where",
"whoTooltip": "The actor that receives the permission and can call the target contract.",
"whoTooltipLabel": "About Who",
"whereTooltip": "The contract where the permission applies.",
"whereTooltipLabel": "About Where",
"permission": "Permission",
"condition": "Condition"
},
Expand All @@ -3577,11 +3626,10 @@
"who": "Who",
"where": "Where",
"permission": "Permission",
"condition": "Condition",
"noCondition": "No condition"
"condition": "Condition"
},
"condition": {
"heading": "Condition detail"
"heading": "Condition"
},
"expandAll": "Expand all",
"collapseAll": "Collapse all",
Expand All @@ -3590,9 +3638,9 @@
"description": "This account has no indexed permissions."
}
},
"noConditionSlot": {
"heading": "No condition",
"description": "Functions guarded by this permission can be called by the granted address directly."
"unrecognizedConditionSlot": {
"heading": "Unrecognized condition",
"description": "This permission references a condition contract, but its condition data could not be resolved."
},
"votingPowerConditionSlot": {
"token": "Token",
Expand All @@ -3601,10 +3649,12 @@
"executeSelectorConditionSlot": {
"description": "The ExecuteSelectorCondition authorizes the caller to execute actions only on explicitly authorized contract functions.",
"allowedActions": "Allowed actions",
"anySelector": "Any selector",
"noActions": "No allowed actions"
},
"membershipConditionSlot": {
"memberOfMultisig": "Member of multisig",
"minApprovals": "Minimum approvals",
"true": "True",
"false": "False"
},
Expand Down
3 changes: 1 addition & 2 deletions apps/app/src/backendApiMocks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { permissionsMocks } from './modules/settings/constants/permissionsMocks';
import type { IBackendApiMock } from './shared/types';

export const backendApiMocks: IBackendApiMock[] = [...permissionsMocks];
export const backendApiMocks: IBackendApiMock[] = [];
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import type { IAllowedActionDecoded } from './allowedActionDecoded';

export interface IAllowedAction {
/**
* Unique backend identifier of the allowed-action event.
*/
id: string;
/**
* Address of the condition contract that gates this action.
*/
conditionAddress: string;
/**
* Selector of the allowed action. `null` means native transfer.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use client';

import {
addressUtils,
ChainEntityType,
Link,
useBlockExplorer,
} from '@aragon/gov-ui-kit';
import { useTranslations } from '@/shared/components/translationsProvider';
import type { IAllowedActionView } from './executeSelectorConditionSlotUtils';
import { EMPTY_ALLOWED_ACTION_VALUE } from './executeSelectorConditionSlotUtils';

export interface IAllowedActionsListProps {
actions: IAllowedActionView[];
chainId?: number;
}

export const AllowedActionsList: React.FC<IAllowedActionsListProps> = ({
actions,
chainId,
}) => {
const { t } = useTranslations();
const { buildEntityUrl } = useBlockExplorer({ chainId });

return (
<div className="flex flex-col">
{actions.map((action) => (
<div
className="flex min-w-0 flex-col gap-1 border-neutral-100 border-b py-3 first:pt-0 last:border-b-0 last:pb-0"
key={action.id}
>
<div className="flex min-w-0 items-center gap-2.5 leading-normal">
<span className="truncate text-neutral-800">
{action.functionName ??
action.selector ??
t(
'app.settings.executeSelectorConditionSlot.anySelector',
)}
</span>
{action.selector != null && (
<span className="shrink-0 text-neutral-500">
{action.selector}
</span>
)}
</div>
<div className="flex min-w-0 items-center gap-2.5 text-neutral-500 leading-normal">
<span className="truncate">
{action.contractName ??
t(
'app.settings.executeSelectorConditionSlot.unknownContract',
)}
</span>
{action.target === EMPTY_ALLOWED_ACTION_VALUE ? (
<span className="shrink-0">
{EMPTY_ALLOWED_ACTION_VALUE}
</span>
) : (
<Link
className="w-fit shrink-0"
href={buildEntityUrl({
type: ChainEntityType.ADDRESS,
id: action.target,
})}
isExternal={true}
>
{addressUtils.truncateAddress(action.target)}
</Link>
)}
</div>
</div>
))}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client';

import { StateSkeletonBar } from '@aragon/gov-ui-kit';
import { useAllowedActions } from '@/modules/governance/api/executeSelectorsService';
import type { Network } from '@/shared/api/daoService';
import { AllowedActionsList } from './allowedActionsList';
import {
hasDecodedAllowedAction,
type IRawAllowedAction,
toAllowedActionViews,
} from './executeSelectorConditionSlotUtils';

interface IDecodedAllowedActionsListProps {
chainId?: number;
conditionAddress?: string;
network: Network;
pluginAddress: string;
rawAllowedActions: IRawAllowedAction[];
}

const AllowedActionsSkeleton: React.FC = () => (
<div className="flex flex-col gap-3">
<StateSkeletonBar width="70%" />
<StateSkeletonBar width="55%" />
<StateSkeletonBar width="65%" />
</div>
);

export const DecodedAllowedActionsList: React.FC<
IDecodedAllowedActionsListProps
> = ({
chainId,
conditionAddress,
network,
pluginAddress,
rawAllowedActions,
}) => {
const { data, isLoading } = useAllowedActions({
urlParams: { network, pluginAddress },
queryParams: { pageSize: 50 },
});
const decodedAllowedActions =
data?.pages
.flatMap((page) => page.data)
.filter((action) =>
hasDecodedAllowedAction(
action,
rawAllowedActions,
conditionAddress,
),
) ?? [];
const decodedAllowedActionViews = decodedAllowedActions.map((action) => ({
contractName: action.decoded.contractName,
functionName: action.decoded.functionName,
id: action.id,
selector: action.selector,
target: action.target,
}));

if (isLoading) {
return <AllowedActionsSkeleton />;
}

if (decodedAllowedActionViews.length > 0) {
return (
<AllowedActionsList
actions={decodedAllowedActionViews}
chainId={chainId}
/>
);
}

return (
<AllowedActionsList
actions={toAllowedActionViews(rawAllowedActions)}
chainId={chainId}
/>
);
};
Loading
Loading