Skip to content

Commit 42f39d9

Browse files
cmsparksclaude
andcommitted
Address PR review feedback and add personal access tokens page
- Drop the skill card file count (and its per-card detail fetch) - Skip manage-only model provider queries for org members - Keep the integration settings tab in the URL (?tab=) - Restore the BYO "Connect machine" / "Share machines" header buttons - Update model, skill, and machine docs for the new UI - Split API tokens into org and personal access token pages - Model combobox and credential secret field layout tweaks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 9c69c13 commit 42f39d9

23 files changed

Lines changed: 176 additions & 115 deletions

‎docs/api/authentication.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ A missing or invalid token returns `401` with code `unauthorized`. A valid token
4040

4141
Tokens are minted where a human can approve them — creating one is deliberately *not* possible with another API key:
4242

43-
- **Dashboard** — with **All projects** selected in the project switcher, open **API Tokens** and click **New token**. The full token is shown once.
43+
- **Dashboard** — open your account menu at the bottom of the sidebar, choose **Personal access tokens**, and click **New token**. The full token is shown once.
4444
- **Device login** — for CLIs and dev tools, below.
4545

4646
Only a hash is stored server-side, so save the token when it is shown.
@@ -49,7 +49,7 @@ Only a hash is stored server-side, so save the token when it is shown.
4949

5050
Services shouldn't run as a person — when that person leaves or their access changes, the integration breaks. An org API key is its own principal: it holds an org role (`admin` or `member`) and per-project roles, granted exactly like a member's.
5151

52-
Create one in the dashboard: with **All projects** selected in the project switcher, open **API Tokens**, switch to the **Organization** tab, and under **Organization API tokens** click **New token**. Pick a name and org role; the `omnara_org_v1_…` token is shown once. Then grant the key project roles from its detail panel — a member-role key sees nothing until you do, same as a human member.
52+
Create one in the dashboard: with **All projects** selected in the project switcher, open **API Tokens** under **Access** (org admins only) and click **New token**. Pick a name and org role; the `omnara_org_v1_…` token is shown once. Then grant the key project roles from its detail panel — a member-role key sees nothing until you do, same as a human member.
5353

5454
Key management stays human-gated: creating, renaming, changing roles, and revoking all require a dashboard session, so one API key can never mint or escalate another. Listing and reading key metadata work with regular API auth ([List org API keys](/api-reference/endpoints/users-and-api-keys/list-org-api-keys)). Revocation is permanent and drops the key's org and project roles ([Revoke](/api-reference/endpoints/users-and-api-keys/revoke-an-org-api-key)).
5555

@@ -148,4 +148,4 @@ Clients without OAuth support can pass a personal access token or org API key in
148148

149149
## Managing tokens
150150

151-
The **API Tokens** page — or [List personal access tokens](/api-reference/endpoints/users-and-api-keys/list-the-authenticated-users-personal-access-tokens) — shows your tokens with creation and last-used times, metadata only: the secret is never shown again. `last_used_at` tells you which tokens are actually alive; prune the rest, and [revoke](/api-reference/endpoints/users-and-api-keys/revoke-a-personal-access-token) immediately if a token leaks — revocation is permanent.
151+
The **Personal access tokens** page in your account menu — or [List personal access tokens](/api-reference/endpoints/users-and-api-keys/list-the-authenticated-users-personal-access-tokens) — shows your tokens with creation and last-used times, metadata only: the secret is never shown again. `last_used_at` tells you which tokens are actually alive; prune the rest, and [revoke](/api-reference/endpoints/users-and-api-keys/revoke-a-personal-access-token) immediately if a token leaks — revocation is permanent.

‎docs/machines/connect.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ Machines are org-level; a project's agents can only target machines granted to t
6666

6767
<Tabs>
6868
<Tab title="Dashboard">
69-
If you didn't share the machine while connecting it, select **All projects** in the project switcher, find its row under **Machines**, click **Share with project**, and select the project.
69+
If you didn't share the machine while connecting it, select **All projects** in the project switcher, open **Machines**, and click **BYO Machines**. Open the machine's row actions, click **Share with project**, and select the project.
7070

71-
From a project, open its **Machines** page and click **Share machines**. The page's **Shared machines** table lists existing shares; click **Stop sharing** to revoke one.
71+
From a project, open its **Machines** page and click **Share machines**. To see existing shares, click **BYO Machines**; open a row's actions and click **Stop sharing** to revoke one.
7272
</Tab>
7373
<Tab title="API">
7474
If you didn't pass `project_ids` while connecting the machine, grant it afterwards:

‎docs/organization/model-providers.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ A configured model (`mdl_…`) gives a provider model a name and runtime setting
7878
<Tab title="Dashboard">
7979
In **Add models**, select the detected models you want, or click **Skip for now**. The detected slug is used as the configured model name. Agent YAML must use the configured model name exactly.
8080

81-
To add models later, select **All projects** in the project switcher, open **Models**, and click **New model**. In **Add models**, choose the provider, then search the provider's models and check the ones you want, or type a slug that isn't listed and click its **Add … as a custom model** row. Click **Edit** on a selected row to set its **Name**, **Context**, and **Max output**; a row that still needs input shows **Needs details**. Optionally pick projects under **Share with**, then click the add button, which shows the count (for example, **Add 2 models**).
81+
To add models later, select **All projects** in the project switcher, open **Models**, expand the provider's models, and click **Add model**; the provider's own page has the same button above its model list. In **Add models**, the provider is preselected; search the provider's models and check the ones you want, or type a slug that isn't listed and click its **Add … as a custom model** row. Click **Edit** on a selected row to set its **Name**, **Context**, and **Max output**; a row that still needs input shows **Needs details**. Optionally pick projects under **Share with**, then click the add button, which shows the count (for example, **Add 2 models**).
8282

8383
A provider can have several configured models for the same slug — for example, two names with different limits. Models the provider already has are marked **Added**; check one again to add another configuration under a different name.
8484

85-
To set the optional **Default output** allowance or change other settings later, open the model's row actions on the **Models** page and click **Edit**.
85+
To set the optional **Default output** allowance or change other settings later, open the model's row actions under its provider on the **Models** page and click **Edit**.
8686
</Tab>
8787
</Tabs>
8888

‎docs/quickstart.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Help me setup Omnara by fetching the Skill file here: https://github.com/omnara-
3535

3636
<Steps>
3737
<Step title="Set up">
38-
The CLI signs in through your browser; if you have not logged in yet, the first command that needs your account starts the login for you. The REST API and the SDK authenticate with an API token. In the web browser, select **All projects** in the project switcher, open **API Tokens**, click **New token**, and save the value.
38+
The CLI signs in through your browser; if you have not logged in yet, the first command that needs your account starts the login for you. The REST API and the SDK authenticate with an API token. In the web browser, open your account menu at the bottom of the sidebar, choose **Personal access tokens**, click **New token**, and save the value.
3939

4040
<ApiSetup />
4141
</Step>

‎docs/tools/skills.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ Each update creates the next revision; the skill's name cannot change.
9292
The CLI has no update command. Running `omnara skills create` again with the same owner and skill name uploads the next revision.
9393
</Tab>
9494
<Tab title="Dashboard">
95-
Open the skill's row menu and click **Edit**. The **SKILL.md** tab edits the instructions in place and keeps the other files; **Upload archive** replaces every file. Frontmatter errors are marked in the editor, and **Save** stays disabled until they are fixed.
95+
Click the skill's name to open its page. Edit `SKILL.md` in place and click **Save**; the other files are kept. Frontmatter errors are marked in the editor, and **Save** stays disabled until they are fixed. To replace every file, click **Upload new version** and choose a new archive; the `SKILL.md` name must stay the same.
9696
</Tab>
9797
</Tabs>
9898

‎frontend/apps/web/src/components/agents/AgentConfigModelField.tsx‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,20 @@ interface ModelChoice extends ConfiguredModelSummary {
3636
const ModelCombobox = createResourceCombobox<ModelChoice>({
3737
itemKey: (model) => model.id,
3838
itemLabel: (model) => `${model.name} · ${model.provider_config}`,
39+
// Name gets the full first line; pricing only appears on the highlighted row so it
40+
// never crowds the provider.
3941
renderItem: (model) => (
4042
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
41-
<span className="flex min-w-0 items-baseline gap-1.5">
42-
<span className="truncate">{model.name}</span>
43-
<span className="text-muted-foreground truncate text-xs">{model.provider_config}</span>
44-
</span>
45-
<span className="text-muted-foreground text-xs tabular-nums">
46-
<ModelPricingSummary pricing={model.pricing} /> per 1M tokens
43+
<span className="truncate">{model.name}</span>
44+
<span className="text-muted-foreground flex min-w-0 items-baseline gap-3 text-xs">
45+
<span className="truncate">{model.provider_config}</span>
46+
<span className="in-data-highlighted:inline ml-auto hidden shrink-0 tabular-nums">
47+
<ModelPricingSummary pricing={model.pricing} /> per 1M tokens
48+
</span>
4749
</span>
4850
</span>
4951
),
52+
contentClassName: 'min-w-[min(36rem,var(--available-width))]',
5053
placeholder: 'Search shared models…',
5154
emptyMessage: 'No shared models found.',
5255
})

‎frontend/apps/web/src/components/app-shell/NavUser.tsx‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useMe } from '@omnara/react'
22
import { Link } from '@tanstack/react-router'
33
import { useState } from 'react'
44

5-
import { CircleHelp, LogOut, Monitor, Moon, Sun, UserIcon } from '@/components/icons'
5+
import { CircleHelp, Fingerprint, LogOut, Monitor, Moon, Sun, UserIcon } from '@/components/icons'
66
import {
77
DropdownMenu,
88
DropdownMenuContent,
@@ -119,6 +119,15 @@ export function NavUser() {
119119
<span className="translate-y-px">Account</span>
120120
</Link>
121121
</DropdownMenuItem>
122+
<DropdownMenuItem
123+
asChild
124+
className="hover:bg-primary/15 focus:bg-primary/15 [&_svg]:!text-foreground"
125+
>
126+
<Link to="/user/api-tokens">
127+
<Fingerprint />
128+
<span className="translate-y-px">Personal access tokens</span>
129+
</Link>
130+
</DropdownMenuItem>
122131
<DropdownMenuItem
123132
asChild
124133
className="hover:bg-primary/15 focus:bg-primary/15 [&_svg]:!text-foreground"

‎frontend/apps/web/src/components/app-shell/OrganizationNav.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ import {
1515
Sparkles,
1616
Users,
1717
} from '@/components/icons'
18+
import { canManageOrg } from '@/lib/permissions'
19+
import { useActiveOrg } from '@/lib/use-active-org'
1820
import { useWebConfig } from '@/lib/web-config'
1921

2022
export function OrganizationNav() {
2123
const pathname = useRouterState({ select: (state) => state.location.pathname })
2224
const { data: webConfig } = useWebConfig()
25+
const { activeOrg } = useActiveOrg()
2326

2427
function item(
2528
to: LinkProps['to'] & string,
@@ -65,7 +68,7 @@ export function OrganizationNav() {
6568
label="Access"
6669
items={[
6770
item('/members', 'Members', Users),
68-
item('/user/api-tokens', 'API Tokens', Fingerprint),
71+
...(canManageOrg(activeOrg.role) ? [item('/api-tokens', 'API Tokens', Fingerprint)] : []),
6972
]}
7073
/>
7174
</>

‎frontend/apps/web/src/components/overview/MachinePoolsSection.tsx‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ManagedLogo, OmnaraManagedTag } from '@/components/brand/OmnaraManaged'
1616
import { ResourceListToolbar } from '@/components/data-table/ResourceListToolbar'
1717
import { Monitor, Server } from '@/components/icons'
1818
import { SearchHeader } from '@/components/layout/SearchHeader'
19+
import { ConnectMachineDialog } from '@/components/org/ConnectMachineDialog'
1920
import { machinePoolProviderLabel } from '@/components/org/MachinePoolDialogState'
2021
import { MachinePoolProviderLogo } from '@/components/org/MachinePoolProviderLogo'
2122
import {
@@ -42,6 +43,19 @@ export function MachinePoolsSection() {
4243
const query = useMachinePools(activeOrg.id, { filters: list.apiFilters, sort: list.sort })
4344
const paged = usePagedQuery(query, list.queryKey)
4445
const [poolDialog, setPoolDialog] = useState<MachinePoolDialog>(null)
46+
const [connectOpen, setConnectOpen] = useState(false)
47+
48+
const connectButton = (
49+
<Button
50+
size="sm"
51+
variant="outline"
52+
onClick={() => {
53+
setConnectOpen(true)
54+
}}
55+
>
56+
Connect machine
57+
</Button>
58+
)
4559

4660
const newPoolButton = () =>
4761
canManage ? (
@@ -72,9 +86,10 @@ export function MachinePoolsSection() {
7286
/>
7387
}
7488
>
89+
{connectButton}
7590
{newPoolButton()}
7691
</SearchHeader>
77-
<ByoMachinesCard orgId={activeOrg.id} />
92+
<ByoMachinesCard orgId={activeOrg.id} emptyAction={connectButton} />
7893
<AgentCardList
7994
items={paged.rows}
8095
getId={(pool) => pool.id}
@@ -99,6 +114,7 @@ export function MachinePoolsSection() {
99114
emptyAction={newPoolButton()}
100115
/>
101116
</div>
117+
<ConnectMachineDialog open={connectOpen} onOpenChange={setConnectOpen} orgId={activeOrg.id} />
102118
{canManage && (
103119
<MachinePoolDialogs
104120
orgId={activeOrg.id}
@@ -207,7 +223,7 @@ export function PoolSubtitle({ pool }: { pool: MachinePool }) {
207223
)
208224
}
209225

210-
function ByoMachinesCard({ orgId }: { orgId: string }) {
226+
function ByoMachinesCard({ orgId, emptyAction }: { orgId: string; emptyAction: ReactNode }) {
211227
// One small page: enough for the preview, and one extra row tells us whether there are more.
212228
const preview = useMachines(orgId, {
213229
filters: { source_kind: 'byo' },
@@ -228,6 +244,7 @@ function ByoMachinesCard({ orgId }: { orgId: string }) {
228244
isPending={preview.isPending}
229245
isError={preview.isError}
230246
emptyMessage="No machines connected yet. Connect a machine you operate to run agents on it."
247+
emptyAction={emptyAction}
231248
viewAll={
232249
hasMore && (
233250
<Link to="/machines/byo" className={agentCardMoreLinkClass}>

‎frontend/apps/web/src/components/overview/MachinePreviewList.tsx‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ export function MachinePreviewList({
1111
isPending,
1212
isError,
1313
emptyMessage,
14+
emptyAction,
1415
viewAll,
1516
}: {
1617
machines: MachineSummary[]
1718
isPending: boolean
1819
isError: boolean
1920
emptyMessage: string
21+
/** Shown under the empty message, e.g. a button to add the first machine. */
22+
emptyAction?: ReactNode
2023
/** Link to the full list; omit when every machine is already shown. */
2124
viewAll?: ReactNode
2225
}) {
@@ -25,9 +28,12 @@ export function MachinePreviewList({
2528
{isError ? (
2629
<p className="text-muted-foreground px-2 py-1.5 text-sm">Couldn&rsquo;t load machines.</p>
2730
) : machines.length === 0 ? (
28-
<p className="text-muted-foreground px-2 py-1.5 text-sm">
29-
{isPending ? 'Loading machines…' : emptyMessage}
30-
</p>
31+
<div className="flex flex-col items-start gap-2 px-2 py-1.5">
32+
<p className="text-muted-foreground text-sm">
33+
{isPending ? 'Loading machines…' : emptyMessage}
34+
</p>
35+
{!isPending && emptyAction}
36+
</div>
3137
) : (
3238
<ul className="flex flex-col gap-0.5">
3339
{machines.map((machine) => {

0 commit comments

Comments
 (0)