Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Settings: Garden permissions #860

Open
wants to merge 8 commits into
base: staging
Choose a base branch
from
Open
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
51 changes: 39 additions & 12 deletions src/components/Garden/Preferences/GlobalPreferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,48 @@ import {
useTheme,
useViewport,
} from '@1hive/1hive-ui'
import styled from 'styled-components'
import { Transition, animated } from 'react-spring/renderprops'
import { useConnectedGarden } from '@providers/ConnectedGarden'
import { useWallet } from '@/providers/Wallet'
import { useEsc } from '../../../hooks/useKeyboardArrows'

import AppsAddresses from './AppsAddresses'
import EVMExecutor from './EVMExecutor'
import Permissions from './Permissions'


const LayoutWrapper = styled(Layout)`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 🔥

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah loved that too

z-index: 2;
height: 100%;
`
const ParentDiv = styled.div`
display: flex;
height: 100%;
outline: 0;
`

const RootWrapper = styled(Root.Provider)`
display: flex;
flex-direction: column;
height: 100%;
`

const HeaderWrapper = styled(Header)`
padding-top: ${(props) => !props.compact ? 10 * GU : 0}px;
`

const SECTIONS = new Map([
['generalInfo', 'General Info'],
['evmExecutor', 'EVM Executor'],
['permissions', 'Permissions']
])
const PATHS = Array.from(SECTIONS.keys())
const VALUES = Array.from(SECTIONS.values())

const GENERAL_INFO_INDEX = 0
const EVM_EXECUTOR_INDEX = 1
const PERMISSIONS_INDEX = 2

const AnimatedDiv = animated.div

Expand Down Expand Up @@ -65,17 +90,18 @@ function GlobalPreferences({ compact, onClose, onNavigation, sectionIndex }) {
getEvmCrispr()
}, [account, connectedGarden, ethers, isSafari])

console.log('sectionIndex ', sectionIndex)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe remove the console log here


return (
<div ref={container} tabIndex="0" css="outline: 0">
<Layout css="z-index: 2">
<ParentDiv ref={container} tabIndex="0">
<LayoutWrapper>
<Close compact={compact} onClick={onClose} />
<Header
primary="Global preferences"
css={`
padding-top: ${!compact ? 10 * GU : 0}px;
`}
/>
<Root.Provider>
<HeaderWrapper
compact={compact}
>
<Header primary="Global preferences" />
</HeaderWrapper>
<RootWrapper>
<React.Fragment>
<Tabs
items={VALUES}
Expand All @@ -87,10 +113,11 @@ function GlobalPreferences({ compact, onClose, onNavigation, sectionIndex }) {
{sectionIndex === EVM_EXECUTOR_INDEX && (
<EVMExecutor evmcrispr={evmcrispr} />
)}
{sectionIndex === PERMISSIONS_INDEX && <Permissions />}
</React.Fragment>
</Root.Provider>
</Layout>
</div>
</RootWrapper>
</LayoutWrapper>
</ParentDiv>
)
}

Expand Down
118 changes: 118 additions & 0 deletions src/components/Garden/Preferences/Permissions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* eslint-disable react/jsx-key */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the problem with names don't show up could be related to it. Same if it's not, I'll recommend remove it and set proper keys attributes to react render properly

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we really need this line also.

import React from 'react'
import { AppBadge, DataView, IdentityBadge, textStyle } from '@1hive/1hive-ui'
import { useGardenState } from '@providers/GardenState'
import { useRoles } from '@/hooks/useRoles'
import Loader from '@components/Loader'

const ENTRIES_PER_PAGE = 10

interface EntryProps {
appAddress: string,
appIcon: string,
appName: string,
description: string,
manager: any,
permissions: any
}


function Permissions() {
const [appRoles, loading] = useRoles()
const { installedApps } = useGardenState()

const fields = [
'Action',
'On app',
{ label: 'Assigned to entity', childStart: true },
'Managed by',
]

return (
<div css={`height: 100%;`}>
{loading ?
(

<Loader />
) :
<DataView
entriesPerPage={ENTRIES_PER_PAGE}
fields={fields}
entries={appRoles}
renderEntry={(entry: EntryProps) => renderEntry(entry, installedApps)}
renderEntryExpansion={(entry: EntryProps) => renderEntryExpansion(entry, installedApps)}
/>
}
</div>
)
}

interface EntryProps {
appAddress: string,
appIcon: string,
appName: string,
description: string,
manager: any,
permissions: any
}

function EntryEntities({ permissions, installedApps }: { permissions: any, installedApps: any }) {
const allowedEntities = permissions.filter((permission: any) => permission.allowed === true)

if (allowedEntities.length === 1) {
return <EntityBadge address={permissions[0].granteeAddress} name={permissions[0].granteeName} icon={permissions[0].granteeIcon} installedApps={installedApps} />
}

return <span
css={`
${textStyle('body2')}
`}
>
{allowedEntities.length} entities
</span>
}

function EntityBadge({ address, name, icon, installedApps }: { address: string, name: string, icon: string, installedApps: any }) {
if (installedApps.find((app: any) => app.address === address)) {

return <AppBadge appAddress={address} iconSrc={icon} label={name} entity={address} />
}

return <IdentityBadge label={name} entity={address} />
}


function renderEntry(entry: EntryProps, installedApps: any) {
const { appAddress, appIcon, appName, description, manager, permissions } = entry

const cells = [
<span
css={`
${textStyle('body2')}
`}
>
{description}
</span>,
<AppBadge appAddress={appAddress} iconSrc={appIcon} label={appName} entity={appAddress} />,
<EntryEntities permissions={permissions} installedApps={installedApps} />,
<EntityBadge address={manager.address} name={manager.shortenedName} icon={manager.managerIcon} installedApps={installedApps} />

]
return cells
}

function renderEntryExpansion(entry: EntryProps, installedApps: any) {
const allowedEntities = entry.permissions.filter((permission: any) => permission.allowed === true)

return allowedEntities.length < 2
? null
: allowedEntities.map((entity: any) => (
<EntityBadge address={entity.granteeAddress} name={entity.granteeName} icon={entity.granteeIcon} installedApps={installedApps} />
))
}





export default Permissions
1 change: 1 addition & 0 deletions src/environment.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const DEFAULT_AGENT_APP_NAME = 'agent'
const DEFAULT_AGREEMENT_APP_NAME = 'agreement'
const DEFAULT_CONVICTION_APP_NAME = 'disputable-conviction-voting'
const DEFAULT_HOOKED_TOKEN_MANAGER = 'wrappable-hooked-token-manager'
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useGardenHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,4 @@ export function useTokenBalances(
}, [account, balances, timeout, tokenContract, token])

return balances
}
}
2 changes: 1 addition & 1 deletion src/hooks/usePromise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'
export default function usePromise(
fn: () => Promise<any>,
memoParams: Array<any>,
defaultValue: boolean
defaultValue: any
) {
const [result, setResult] = useState(defaultValue)

Expand Down
90 changes: 90 additions & 0 deletions src/hooks/useRoles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useEffect, useMemo, useState } from 'react'
import { useGardenState } from '@providers/GardenState'
import usePromise from '@hooks/usePromise'
import { getAppPresentationByAddress } from '@utils/app-utils'

const ANY_ADDRESS = "0xffffffffffffffffffffffffffffffffffffffff"

interface ManagerProps {
address: string,
shortenedName: string,
}

interface PermissionProps {
allowed: boolean,
appAddress: string,
granteeAddress: string,
granteeName: string | undefined,
params: Array<any>,
roleHash: string
}

interface RoleProps {
appAddress: string,
appName: string,
appIcon: string,
description: string | undefined, // TODO- check why this is happening proabably old ABI
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are still getting undefined here some times?

hash: string,
manager: Array<ManagerProps>,
name: string,
params: Array<any>,
permissions: Array<PermissionProps>
}

export function useRoles() {
const [loading, setLoading] = useState<boolean>(true)
const gardenState = useGardenState()

const appsWithPermissions = useMemo(() => {
if (!gardenState?.installedApps) {
return async () => { null }
}
return () => Promise.all(gardenState?.installedApps.map(async (app: any) => {
return {
...app,
roles: await app.roles()
}
}))
}, [gardenState])

const appsWithRolesResolved = usePromise(appsWithPermissions, [], [])

function isAnyAddress(address: string) {
return address === ANY_ADDRESS
}

const rolesWithEntitiesResolved: Array<RoleProps> = appsWithRolesResolved ? appsWithRolesResolved.map((app: any) => {
return app.roles.map((role: any) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible we get the correct type here in instead any, it could help track changes in the code below

const appPresentation = getAppPresentationByAddress(gardenState?.installedApps, app.address)

return {
...role,
appName: appPresentation?.shortenedName || appPresentation?.humanName,
appIcon: appPresentation?.iconSrc,
manager: {
address: role.manager,
shortenedName: getAppPresentationByAddress(gardenState?.installedApps, role.manager)?.shortenedName,
managerIcon: getAppPresentationByAddress(gardenState?.installedApps, role.manager)?.iconSrc
},
permissions: role.permissions.map((permission: any) => {
return {
...permission,
granteeName: isAnyAddress(permission.granteeAddress) ? 'Any account' : getAppPresentationByAddress(gardenState?.installedApps, permission.granteeAddress)?.shortenedName,
granteeIcon: getAppPresentationByAddress(gardenState?.installedApps, permission.granteeAddress)?.iconSrc
}
})
}
})
}) : null

useEffect(() => {
if (rolesWithEntitiesResolved) {
setLoading(false)
}
}, [rolesWithEntitiesResolved])



return [rolesWithEntitiesResolved ? rolesWithEntitiesResolved.flat() : [], loading]

}
2 changes: 1 addition & 1 deletion src/hooks/useStakes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,5 @@ export function useInactiveProposalsWithStake(account: string) {
)
})

return inactiveStakes
return inactiveStakes.flat()
}
2 changes: 2 additions & 0 deletions src/utils/app-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function getAppPresentation(app: AppType): {
humanName: string
iconSrc: string
name?: string
shortenedName?: string
} | null {
const { contentUri, name, manifest, appId } = app
// Get human readable name and icon from manifest if available
Expand All @@ -66,6 +67,7 @@ export function getAppPresentation(app: AppType): {
humanName,
iconSrc: iconPath ? getIpfsUrlFromUri(contentUri) + iconPath : '',
name,
shortenedName: SHORTENED_APPS_NAMES.get(name) || name,
}
}

Expand Down