Skip to content

Commit 7655d8e

Browse files
authored
Merge pull request #391 from oasisprotocol/lw/grant-logs
Grant logs permission on a machine and restart
2 parents 9b30bdb + 1a2a99f commit 7655d8e

6 files changed

Lines changed: 216 additions & 25 deletions

File tree

.changelog/391.feature.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Grant logs permission on a machine and restart

src/backend/api.ts

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -788,17 +788,13 @@ export function useMachineExecuteRestartCmd() {
788788
: oasis.misc.fromHex('000000000000000000000000000000000000000000000000a6d1e3ebf60dff6c')
789789
const roflmarket = new oasisRT.roflmarket.Wrapper(sapphireRuntimeId)
790790

791-
const restartRequest = {
792-
wipe_storage: false,
793-
}
794-
795-
const command = {
791+
const encodedCommand = oasis.misc.toCBOR({
796792
// https://github.com/oasisprotocol/cli/blob/b6894a1bb6ea7918a9b2ba3efe30b1911388e2f6/build/rofl/scheduler/commands.go#L9-L42
797793
method: 'Restart',
798-
args: restartRequest,
799-
}
800-
801-
const encodedCommand = oasis.misc.toCBOR(command)
794+
args: {
795+
wipe_storage: false,
796+
},
797+
})
802798

803799
return await sendTransactionAsync(
804800
roflmarket
@@ -830,17 +826,13 @@ export function useMachineExecuteStopCmd() {
830826
: oasis.misc.fromHex('000000000000000000000000000000000000000000000000a6d1e3ebf60dff6c')
831827
const roflmarket = new oasisRT.roflmarket.Wrapper(sapphireRuntimeId)
832828

833-
const restartRequest = {
834-
wipe_storage: false,
835-
}
836-
837-
const command = {
829+
const encodedCommand = oasis.misc.toCBOR({
838830
// https://github.com/oasisprotocol/cli/blob/b6894a1bb6ea7918a9b2ba3efe30b1911388e2f6/build/rofl/scheduler/commands.go#L9-L42
839831
method: 'Terminate',
840-
args: restartRequest,
841-
}
842-
843-
const encodedCommand = oasis.misc.toCBOR(command)
832+
args: {
833+
wipe_storage: false,
834+
},
835+
})
844836

845837
return await sendTransactionAsync(
846838
roflmarket
@@ -912,6 +904,14 @@ export function useMachineTopUp() {
912904
'net.oasis.deployment.orc.ref': (machine.deployment.metadata as any)[
913905
'net.oasis.deployment.orc.ref'
914906
],
907+
// Copy permissions like log.view
908+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
909+
...((machine.deployment.metadata as any)['net.oasis.scheduler.permissions'] && {
910+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
911+
'net.oasis.scheduler.permissions': (machine.deployment.metadata as any)[
912+
'net.oasis.scheduler.permissions'
913+
],
914+
}),
915915
},
916916
},
917917
term: duration.term,
@@ -927,3 +927,74 @@ export function useMachineTopUp() {
927927
},
928928
})
929929
}
930+
931+
export function useGrantLogsPermission() {
932+
const { sendTransactionAsync } = useSendTransaction()
933+
return useMutation<
934+
`0x${string}`,
935+
AxiosError<unknown>,
936+
{
937+
machine: RoflMarketInstance
938+
provider: string
939+
network: 'mainnet' | 'testnet'
940+
evmAddress: `0x${string}`
941+
}
942+
>({
943+
mutationFn: async ({ machine, provider, network, evmAddress }) => {
944+
const sapphireRuntimeId =
945+
network === 'mainnet'
946+
? oasis.misc.fromHex('000000000000000000000000000000000000000000000000f80306c9858e7279')
947+
: oasis.misc.fromHex('000000000000000000000000000000000000000000000000a6d1e3ebf60dff6c')
948+
const roflmarket = new oasisRT.roflmarket.Wrapper(sapphireRuntimeId)
949+
950+
const encodedCommand = oasis.misc.toCBOR({
951+
// https://github.com/oasisprotocol/cli/blob/b6894a1bb6ea7918a9b2ba3efe30b1911388e2f6/build/rofl/scheduler/commands.go#L9-L42
952+
method: 'Deploy',
953+
args: {
954+
deployment: {
955+
app_id: oasisRT.rofl.fromBech32(machine.deployment.app_id as string),
956+
manifest_hash: oasis.misc.fromHex(machine.deployment.manifest_hash as string),
957+
metadata: {
958+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
959+
...(machine.deployment.metadata as any),
960+
// https://github.com/oasisprotocol/oasis-sdk/blob/b38b693/rofl-scheduler/src/types.rs#L49-L53
961+
'net.oasis.scheduler.permissions': oasis.misc.toBase64(
962+
oasis.misc.toCBOR({
963+
// Keep other permissions
964+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
965+
...((machine.deployment.metadata as any)?.['net.oasis.scheduler.permissions'] &&
966+
(oasis.misc.fromCBOR(
967+
oasis.misc.fromBase64(
968+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
969+
(machine.deployment.metadata as any)?.['net.oasis.scheduler.permissions'],
970+
),
971+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
972+
) as any)),
973+
'log.view': [
974+
oasis.staking.addressFromBech32(
975+
oasisRT.address.toBech32(getOasisAddressBytesFromEvm(evmAddress)),
976+
),
977+
],
978+
}),
979+
),
980+
},
981+
},
982+
wipe_storage: false,
983+
},
984+
})
985+
986+
return await sendTransactionAsync(
987+
roflmarket
988+
.callInstanceExecuteCmds()
989+
.setBody({
990+
provider: oasis.staking.addressFromBech32(provider),
991+
id: oasis.misc.fromHex(machine.id) as oasisRT.types.MachineID,
992+
cmds: [encodedCommand],
993+
})
994+
.toSubcall(),
995+
)
996+
// Doesn't wait for transaction receipt
997+
// Takes about 1 minute to complete after transaction receipt.
998+
},
999+
})
1000+
}

src/pages/Dashboard/AppDetails/RemoveAppButton.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ import { fromBaseUnits } from '../../../utils/number-utils'
1717
type RemoveAppButtonProps = {
1818
onConfirm: () => void
1919
stakedAmount: string
20+
disabled?: boolean
2021
}
2122

22-
export const RemoveAppButton: FC<RemoveAppButtonProps> = ({ stakedAmount, onConfirm }) => {
23+
export const RemoveAppButton: FC<RemoveAppButtonProps> = ({ stakedAmount, onConfirm, disabled }) => {
2324
const ticker = useTicker()
2425
return (
2526
<Dialog>
2627
<DialogTrigger asChild>
27-
<Button variant="outline" size="icon">
28+
<Button variant="outline" size="icon" disabled={disabled}>
2829
<Trash2 className="h-4 w-4" />
2930
</Button>
3031
</DialogTrigger>

src/pages/Dashboard/AppDetails/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { RemoveAppButton } from './RemoveAppButton'
1919
import { Dialog, DialogContent } from '@oasisprotocol/ui-library/src/components/ui/dialog'
2020
import { HelpWidget } from '../../CreateApp/HelpWidget'
2121
import { AddSecret } from '../../../components/SecretsTable/AddSecret'
22+
import { useAccount } from 'wagmi'
2223

2324
function setDefaultMetadataViewState(metadata: RoflAppMetadata | undefined = {}): ViewMetadataState {
2425
return {
@@ -43,6 +44,7 @@ function setDefaultSecretsViewState(secrets: RoflAppSecrets | undefined = {}): V
4344
}
4445

4546
export const AppDetails: FC = () => {
47+
const account = useAccount()
4648
const [viewMetadataState, setViewMetadataState] = useState({
4749
...setDefaultMetadataViewState(),
4850
})
@@ -63,7 +65,7 @@ export const AppDetails: FC = () => {
6365
)
6466
const { data: readme } = useDownloadArtifact(`${id}-readme-md`, token)
6567

66-
const editEnabled = !!token && !roflApp?.removed
68+
const editEnabled = !!token && !roflApp?.removed && roflApp?.admin_eth === account.address
6769
const removeApp = useRemoveApp()
6870
const updateApp = useUpdateApp()
6971

@@ -159,6 +161,7 @@ export const AppDetails: FC = () => {
159161
<div className="flex items-center gap-8">
160162
{roflApp && !roflApp?.removed && (
161163
<RemoveAppButton
164+
disabled={!editEnabled}
162165
stakedAmount={roflApp.stake}
163166
onConfirm={async () => {
164167
if (roflApp.metadata['net.oasis.roflapp.template'] === 'hl-copy-trader') {
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { FC } from 'react'
2+
import { useState } from 'react'
3+
import { Button } from '@oasisprotocol/ui-library/src/components/ui/button'
4+
import {
5+
Dialog,
6+
DialogClose,
7+
DialogContent,
8+
DialogDescription,
9+
DialogFooter,
10+
DialogHeader,
11+
DialogTitle,
12+
DialogTrigger,
13+
} from '@oasisprotocol/ui-library/src/components/ui/dialog'
14+
import { z } from 'zod'
15+
import { zodResolver } from '@hookform/resolvers/zod'
16+
import { useForm } from 'react-hook-form'
17+
import { InputFormField } from '../../../components/InputFormField'
18+
import { FileText } from 'lucide-react'
19+
import { isAddress } from 'viem'
20+
21+
const formSchema = z.object({
22+
evmAddress: z
23+
.string()
24+
.regex(/^0x[a-fA-F0-9]{40}$/, {
25+
message: 'Please enter a valid Sapphire address (0x followed by 40 hex characters)',
26+
})
27+
.refine(val => isAddress(val, { strict: true }) as boolean, { message: 'Address checksum is invalid' }),
28+
})
29+
30+
type GrantLogsPermissionDialogProps = {
31+
onConfirm: (evmAddress: `0x${string}`) => void
32+
disabled?: boolean
33+
}
34+
35+
export const GrantLogsPermissionDialog: FC<GrantLogsPermissionDialogProps> = ({ onConfirm, disabled }) => {
36+
const [open, setOpen] = useState(false)
37+
const form = useForm<z.infer<typeof formSchema>>({
38+
resolver: zodResolver(formSchema),
39+
defaultValues: {
40+
evmAddress: '0x',
41+
},
42+
})
43+
44+
const onSubmit = (values: z.infer<typeof formSchema>) => {
45+
onConfirm(values.evmAddress as `0x${string}`)
46+
setOpen(false)
47+
}
48+
49+
return (
50+
<Dialog open={open} onOpenChange={setOpen}>
51+
<DialogTrigger asChild>
52+
<Button variant="outline" className="w-full md:w-auto" disabled={disabled}>
53+
<FileText />
54+
Grant logs permission
55+
</Button>
56+
</DialogTrigger>
57+
<DialogContent className="sm:max-w-[425px]">
58+
<DialogHeader>
59+
<DialogTitle>Grant logs view permission</DialogTitle>
60+
<DialogDescription>
61+
Enter an address to grant logs view permission. The machine will restart after granting
62+
permission.
63+
</DialogDescription>
64+
</DialogHeader>
65+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
66+
<div className="space-y-4 py-4">
67+
<InputFormField control={form.control} name="evmAddress" label="Address" placeholder="0x..." />
68+
</div>
69+
<DialogFooter>
70+
<DialogClose asChild>
71+
<Button type="button" variant="outline" onClick={() => form.reset()}>
72+
Cancel
73+
</Button>
74+
</DialogClose>
75+
<Button type="submit" variant="destructive">
76+
Grant permission and restart
77+
</Button>
78+
</DialogFooter>
79+
</form>
80+
</DialogContent>
81+
</Dialog>
82+
)
83+
}

src/pages/Dashboard/MachinesDetails/index.tsx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,30 @@ import { formatDistanceToNow, parseISO, isFuture } from 'date-fns'
66
import { MachineStatusIcon } from '../../../components/MachineStatusIcon'
77
import { DetailsSectionRow } from '../../../components/DetailsSectionRow'
88
import { MachineRestart } from './MachineRestart'
9+
import { GrantLogsPermissionDialog } from './GrantLogsPermissionDialog'
910
import { useNetwork } from '../../../hooks/useNetwork'
1011
import {
1112
useGetRuntimeRoflAppsId,
1213
useGetRuntimeRoflmarketProvidersAddressInstancesId,
1314
} from '../../../nexus/api'
1415
import { Skeleton } from '@oasisprotocol/ui-library/src/components/ui/skeleton'
1516
import { MachineResources } from '../../../components/MachineResources'
16-
import { useMachineExecuteRestartCmd, useMachineExecuteStopCmd } from '../../../backend/api'
17+
import {
18+
useGrantLogsPermission,
19+
useMachineExecuteRestartCmd,
20+
useMachineExecuteStopCmd,
21+
} from '../../../backend/api'
1722
import { Dialog, DialogContent } from '@oasisprotocol/ui-library/src/components/ui/dialog'
1823
import { MachineLogs } from './MachineLogs'
1924
import { Button } from '@oasisprotocol/ui-library/src/components/ui/button'
2025
import { MachineName } from '../../../components/MachineName'
2126
import { toastWithDuration } from '../../../utils/toastWithDuration'
2227
import { isMachineRemoved } from '../../../components/MachineStatusIcon/isMachineRemoved'
28+
import { useAccount } from 'wagmi'
29+
import { getEvmBech32Address } from '../../../utils/helpers'
2330

2431
export const MachinesDetails: FC = () => {
32+
const account = useAccount()
2533
const network = useNetwork()
2634
const { provider, id } = useParams()
2735
const roflMachinesQuery = useGetRuntimeRoflmarketProvidersAddressInstancesId(
@@ -40,8 +48,10 @@ export const MachinesDetails: FC = () => {
4048
const machine = data?.data
4149
const restartMachine = useMachineExecuteRestartCmd()
4250
const stopMachine = useMachineExecuteStopCmd()
51+
const grantLogsPermission = useGrantLogsPermission()
4352

44-
const showBlockingOverlay = restartMachine.isPending || stopMachine.isPending
53+
const showBlockingOverlay =
54+
restartMachine.isPending || stopMachine.isPending || grantLogsPermission.isPending
4555
return (
4656
<>
4757
<div>
@@ -77,7 +87,6 @@ export const MachinesDetails: FC = () => {
7787
</>
7888
)}
7989
</div>
80-
8190
{!isMachineRemoved(machine) && (
8291
<Button variant="outline" className="w-full md:w-auto" asChild>
8392
<Link to="./top-up">
@@ -87,7 +96,11 @@ export const MachinesDetails: FC = () => {
8796
</Button>
8897
)}
8998
<MachineRestart
90-
disabled={isMachineRemoved(machine)}
99+
disabled={
100+
isMachineRemoved(machine) ||
101+
!account.address ||
102+
machine.admin !== getEvmBech32Address(account.address)
103+
}
91104
onConfirm={async () => {
92105
await restartMachine.mutateAsync({
93106
machineId: machine.id,
@@ -97,6 +110,25 @@ export const MachinesDetails: FC = () => {
97110
toastWithDuration('Machine is restarting (~1min)', 1 * 60 * 1000)
98111
}}
99112
/>
113+
<GrantLogsPermissionDialog
114+
disabled={
115+
isMachineRemoved(machine) ||
116+
!account.address ||
117+
machine.admin !== getEvmBech32Address(account.address)
118+
}
119+
onConfirm={async evmAddress => {
120+
await grantLogsPermission.mutateAsync({
121+
machine: machine,
122+
provider: machine.provider,
123+
network,
124+
evmAddress,
125+
})
126+
toastWithDuration(
127+
'Logs view permission granted. Machine is restarting (~1min)',
128+
1 * 60 * 1000,
129+
)
130+
}}
131+
/>
100132
<TabsList className="w-full md:w-auto">
101133
<TabsTrigger value="details">Details</TabsTrigger>
102134
<TabsTrigger value="logs">Logs</TabsTrigger>

0 commit comments

Comments
 (0)