Skip to content

Commit 7af61bb

Browse files
committed
fix: require token for storage related operation
1 parent 63e2a19 commit 7af61bb

File tree

6 files changed

+264
-238
lines changed

6 files changed

+264
-238
lines changed

packages/devtools-kit/src/_types/rpc.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ export interface ServerFunctions {
4242
// Storage
4343
getStorageMounts(): Promise<StorageMounts>
4444
getStorageKeys(base?: string): Promise<string[]>
45-
getStorageItem(key: string): Promise<StorageValue>
46-
setStorageItem(key: string, value: StorageValue): Promise<void>
47-
removeStorageItem(key: string): Promise<void>
45+
getStorageItem(token: string, key: string): Promise<StorageValue>
46+
setStorageItem(token: string, key: string, value: StorageValue): Promise<void>
47+
removeStorageItem(token: string, key: string): Promise<void>
4848

4949
// Analyze
5050
getAnalyzeBuildInfo(): Promise<AnalyzeBuildsInfo>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script setup lang="ts">
2+
onMounted(() => {
3+
if (!isDevAuthed.value)
4+
rpc.requestForAuth()
5+
})
6+
</script>
7+
8+
<template>
9+
<NPanelGrids v-if="!isDevAuthed">
10+
<NCard flex="~ col gap-2" mxa items-center p6 text-center>
11+
<h3 class="mb2 text-lg font-medium leading-6" flex="~ items-center gap-1" text-orange>
12+
<span class="i-carbon-information-square" /> Permissions required
13+
</h3>
14+
<p>
15+
This operation requires permissions for running command and access files from the browser.
16+
</p>
17+
<p>
18+
A request is sent to the server.<br>
19+
Please check your terminal for the instructions and then come back.
20+
</p>
21+
<div flex="~ gap-3" mt2 justify-end>
22+
<NButton disabled icon="i-carbon-time">
23+
Waiting for authorization...
24+
</NButton>
25+
</div>
26+
</NCard>
27+
</NPanelGrids>
28+
<template v-else>
29+
<slot />
30+
</template>
31+
</template>
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
<script setup lang="ts">
2+
import JsonEditorVue from 'json-editor-vue'
3+
4+
const nuxtApp = useNuxtApp()
5+
const router = useRouter()
6+
const searchString = ref('')
7+
const newKey = ref('')
8+
const currentStorage = computed({
9+
get(): string | undefined {
10+
return useRoute().query?.storage as string | undefined
11+
},
12+
set(storage: string | undefined): void {
13+
router.replace({ query: { storage } })
14+
},
15+
})
16+
const currentItem = ref()
17+
const fileKey = computed(() => useRoute().query?.key as string | undefined)
18+
19+
const { data: storageMounts } = await useAsyncData('storageMounts', () => rpc.getStorageMounts())
20+
const { data: storageKeys, refresh: refreshStorageKeys } = await useAsyncData('storageKeys', async () => {
21+
if (currentStorage.value)
22+
return await rpc.getStorageKeys(currentStorage.value)
23+
return []
24+
})
25+
26+
const closeWatcher = nuxtApp.hook('storage:key:update' as any, async (key: string, event: any) => {
27+
if (!currentStorage.value || key.split(':')[0] !== currentStorage.value)
28+
return
29+
await refreshStorageKeys()
30+
if (fileKey.value === key) {
31+
if (event === 'remove')
32+
return router.replace({ query: { storage: currentStorage.value } })
33+
await fetchItem(fileKey.value)
34+
}
35+
})
36+
37+
onUnmounted(closeWatcher)
38+
39+
watch(currentStorage, refreshStorageKeys)
40+
41+
watchEffect(async () => {
42+
if (!fileKey.value) {
43+
currentItem.value = null
44+
return
45+
}
46+
fetchItem(fileKey.value)
47+
})
48+
49+
// Save on Ctrl/Cmd + S
50+
useEventListener('keydown', (e) => {
51+
if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
52+
saveCurrentItem()
53+
e.preventDefault()
54+
}
55+
})
56+
57+
function keyName(key: string) {
58+
return key.replace(`${currentStorage.value}:`, '')
59+
}
60+
61+
const filteredKeys = computed(() => {
62+
if (!storageKeys.value)
63+
return []
64+
return storageKeys.value.filter(key => key.includes(searchString.value))
65+
})
66+
67+
async function fetchItem(key: string) {
68+
const content = await rpc.getStorageItem(await ensureDevAuthToken(), key)
69+
currentItem.value = {
70+
key,
71+
updatedKey: keyName(key),
72+
editingKey: false,
73+
content,
74+
updatedContent: content,
75+
}
76+
}
77+
78+
async function saveNewItem() {
79+
if (!newKey.value || !currentStorage.value)
80+
return
81+
// If does not exists
82+
const key = `${currentStorage.value}:${newKey.value}`
83+
if (!storageKeys.value?.includes(key))
84+
await rpc.setStorageItem(await ensureDevAuthToken(), key, '')
85+
86+
router.replace({ query: { storage: currentStorage.value, key } })
87+
newKey.value = ''
88+
}
89+
90+
async function saveCurrentItem() {
91+
if (!currentItem.value)
92+
return
93+
await rpc.setStorageItem(await ensureDevAuthToken(), currentItem.value.key, currentItem.value.updatedContent)
94+
await fetchItem(currentItem.value.key)
95+
}
96+
97+
async function removeCurrentItem() {
98+
if (!currentItem.value || !currentStorage.value)
99+
return
100+
await rpc.removeStorageItem(await ensureDevAuthToken(), currentItem.value.key)
101+
currentItem.value = null
102+
}
103+
104+
async function renameCurrentItem() {
105+
if (!currentItem.value || !currentStorage.value)
106+
return
107+
const renamedKey = `${currentStorage.value}:${currentItem.value.updatedKey}`
108+
const token = await ensureDevAuthToken()
109+
await rpc.setStorageItem(token, renamedKey, currentItem.value.updatedContent)
110+
await rpc.removeStorageItem(token, currentItem.value.key)
111+
router.replace({ query: { storage: currentStorage.value, key: renamedKey } })
112+
}
113+
</script>
114+
115+
<template>
116+
<PanelLeftRight v-if="currentStorage" storage-key="tab-storage">
117+
<template #left>
118+
<div class="h-[48px] flex items-center justify-between gap1 px-3">
119+
<NIconButton icon="carbon-chevron-left" ml--1 @click="currentStorage = ''" />
120+
<div class="w-full text-sm">
121+
<NSelect v-model="currentStorage" n="primary" icon="carbon-data-base">
122+
<option v-for="(_storage, name) of storageMounts" :key="name" :value="name">
123+
{{ name }}
124+
</option>
125+
</NSelect>
126+
</div>
127+
</div>
128+
<NTextInput
129+
v-model="searchString"
130+
icon="carbon-search"
131+
placeholder="Search..."
132+
n="primary sm"
133+
border="y x-none base! rounded-0"
134+
class="w-full py2 ring-0!"
135+
/>
136+
<template v-for="key of filteredKeys" :key="key">
137+
<NuxtLink
138+
block truncate px2 py1 font-mono text-sm
139+
:to="{ query: { key, storage: currentStorage } }"
140+
:class="key === currentItem?.key ? 'text-primary n-bg-active' : 'text-secondary hover:n-bg-hover'"
141+
>
142+
{{ keyName(key) }}
143+
</NuxtLink>
144+
<div x-divider />
145+
</template>
146+
<NTextInput
147+
v-model="newKey"
148+
icon="carbon-add"
149+
placeholder="key"
150+
n="sm"
151+
border="t-none x-none base! rounded-0"
152+
class="w-full py2 font-mono ring-0!"
153+
@keyup.enter="saveNewItem"
154+
/>
155+
</template>
156+
157+
<template #right>
158+
<div v-if="currentItem?.key" h-full of-hidden flex="~ col">
159+
<div border="b base" class="h-[49px] flex flex-none items-center justify-between px-4 text-sm">
160+
<div class="flex items-center gap-4">
161+
<NTextInput v-if="currentItem.editingKey" v-model="currentItem.updatedKey" @keyup.enter="renameCurrentItem" />
162+
<code v-else>{{ keyName(currentItem.key) }} <NIcon icon="carbon-edit" class="cursor-pointer op50 hover:op100" @click="currentItem.editingKey = true" /></code>
163+
<NButton v-if="!currentItem.editingKey" n="green xs" :disabled="currentItem.content === currentItem.updatedContent" :class="{ 'border-green': currentItem.content !== currentItem.updatedContent }" @click="saveCurrentItem">
164+
Save
165+
</NButton>
166+
</div>
167+
<div>
168+
<NButton n="red xs" @click="removeCurrentItem">
169+
Delete
170+
</NButton>
171+
</div>
172+
</div>
173+
<JsonEditorVue
174+
v-if="typeof currentItem.content === 'object'"
175+
v-model="currentItem.updatedContent"
176+
:class="[$colorMode.value === 'dark' ? 'jse-theme-dark' : 'light']"
177+
class="json-editor-vue h-full of-auto text-sm outline-none"
178+
v-bind="$attrs" mode="text" :navigation-bar="false" :indentation="2" :tab-size="2"
179+
/>
180+
<textarea
181+
v-else v-model="currentItem.updatedContent"
182+
placeholder="Item value..."
183+
class="h-full of-auto p-4 font-mono text-sm outline-none"
184+
@keyup.ctrl.enter="saveCurrentItem"
185+
/>
186+
</div>
187+
<NPanelGrids v-else>
188+
<NCard px6 py4>
189+
Select one key to start.<br>Learn more about <NLink href="https://nitro.unjs.io/guide/storage" n="orange" target="_blank">
190+
Nitro storage
191+
</NLink>
192+
</NCard>
193+
</NPanelGrids>
194+
</template>
195+
</PanelLeftRight>
196+
<NPanelGrids v-else>
197+
<p v-if="Object.keys(storageMounts as any).length" op50>
198+
Select one storage to start:
199+
</p>
200+
<p v-else>
201+
No custom storage defined in <code>nitro.storage</code>.<br>
202+
Learn more about <NLink href="https://nitro.unjs.io/guide/storage" n="orange" target="_blank">
203+
Nitro storage
204+
</NLink>
205+
</p>
206+
<NCard
207+
v-for="(storage, name) of storageMounts"
208+
:key="name"
209+
min-w-80 cursor-pointer p-4 text-left
210+
hover="border-green"
211+
@click="currentStorage = name as string"
212+
>
213+
<span font-bold>{{ name }}</span><br>
214+
<span text-sm>{{ storage.driver }} driver</span><br>
215+
<FilepathItem v-if="storage.base" text-xs :filepath="storage.base" />
216+
</NCard>
217+
</NPanelGrids>
218+
</template>

packages/devtools/client/pages/modules/custom-[name].vue

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,7 @@ onMounted(() => {
4343
</NPanelGrids>
4444
</template>
4545
<template v-if="tab.requireAuth && !isDevAuthed">
46-
<NPanelGrids>
47-
<NCard flex="~ col gap-2" mxa items-center p6 text-center>
48-
<h3 class="mb2 text-lg font-medium leading-6" flex="~ items-center gap-1" text-orange>
49-
<span class="i-carbon-information-square" /> Permissions required
50-
</h3>
51-
<p>
52-
This operation requires permissions for running command and access files from the browser.
53-
</p>
54-
<p>
55-
A request is sent to the server.<br>
56-
Please check your terminal for the instructions and then come back.
57-
</p>
58-
<div flex="~ gap-3" mt2 justify-end>
59-
<NButton disabled icon="i-carbon-time">
60-
Waiting for authorization...
61-
</NButton>
62-
</div>
63-
</NCard>
64-
</NPanelGrids>
46+
<AuthRequiredPanel />
6547
</template>
6648
<template v-else-if="tab.view.type === 'iframe'">
6749
<IframeView :tab="tab" />

0 commit comments

Comments
 (0)