8000 feat(install): install with the defaults in one click; the API token form opens itself by adeelahmad · Pull Request #331 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content
Merged
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
88 changes: 24 additions & 64 deletions ci/clicks/baseline.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"surveyed": "2026-10-10T21:42:56.433Z",
"surveyed": "2026-10-10T22:25:49.567Z",
"commit": "",
"viewport": "1400x900",
"journeys": [
{
"module": "Setup",
"feature": "Install with the web installer (defaults, local model found)",
"note": "The install line in a terminal, then the browser installer. The browser opens on its own on a desktop; on a server the link is opened by hand (counted). A computer missing Docker also asks for its password.",
"clicks": 7,
"clicks": 3,
"keys": 2,
"prompts": 0,
"steps": [
Expand All @@ -30,39 +30,7 @@
{
"page": "installer:/",
"kind": "click",
"what": "Continue (this computer)",
"clicks": 1,
"keys": 0,
"prompts": 0
},
{
"page": "installer:/",
"kind": "click",
"what": "Continue (how it runs)",
"clicks": 1,
"keys": 0,
"prompts": 0
},
{
"page": "installer:/",
"kind": "click",
"what": "Continue (AI model)",
"clicks": 1,
"keys": 0,
"prompts": 0
},
{
"page": "installer:/",
"kind": "click",
"what": "Continue (extras)",
"clicks": 1,
"keys": 0,
"prompts": 0
},
{
"page": "installer:/",
"kind": "click",
"what": "Install Lens",
"what": "Install with the defaults",
"clicks": 1,
"keys": 0,
"prompts": 0
Expand All @@ -77,7 +45,7 @@
}
],
"id": "install",
"ms": 1749,
"ms": 2081,
"ok": true
},
{
Expand Down Expand Up @@ -114,7 +82,7 @@
}
],
"id": "first-admin",
"ms": 409,
"ms": 475,
"ok": true
},
{
Expand Down Expand Up @@ -159,7 +127,7 @@
}
],
"id": "setup-wizard",
"ms": 572,
"ms": 659,
"ok": true
},
{
Expand Down Expand Up @@ -188,7 +156,7 @@
}
],
"id": "first-question",
"ms": 1175,
"ms": 2225,
"ok": true
},
{
Expand Down Expand Up @@ -233,7 +201,7 @@
}
],
"id": "import-paste",
"ms": 4120,
"ms": 4139,
"ok": true
},
{
Expand Down Expand Up @@ -262,7 +230,7 @@
}
],
"id": "library-open",
"ms": 2598,
"ms": 2621,
"ok": true
},
{
Expand Down Expand Up @@ -299,7 +267,7 @@
}
],
"id": "search-word",
"ms": 3202,
"ms": 3173,
"ok": true
},
{
Expand Down Expand Up @@ -336,7 +304,7 @@
}
],
"id": "jump-to",
"ms": 2312,
"ms": 2385,
"ok": true
},
{
Expand Down Expand Up @@ -373,7 +341,7 @@
}
],
"id": "chat-ask",
"ms": 2750,
"ms": 3552,
"ok": true
},
{
Expand Down Expand Up @@ -418,7 +386,7 @@
}
],
"id": "note-create",
"ms": 3328,
"ms": 3366,
"ok": true
},
{
Expand Down Expand Up @@ -463,7 +431,7 @@
}
],
"id": "topic-add",
"ms": 3367,
"ms": 3487,
"ok": true
},
{
Expand Down Expand Up @@ -508,7 +476,7 @@
}
],
"id": "pipeline-create",
"ms": 2676,
"ms": 2694,
"ok": true
},
{
Expand Down Expand Up @@ -553,7 +521,7 @@
}
],
"id": "routine-create",
"ms": 2854,
"ms": 2647,
"ok": true
},
{
Expand Down Expand Up @@ -590,7 +558,7 @@
}
],
"id": "extension-add",
"ms": 2571,
"ms": 2554,
"ok": true
},
{
Expand Down Expand Up @@ -627,7 +595,7 @@
}
],
"id": "sensor-webhook",
"ms": 2608,
"ms": 2684,
"ok": true
},
{
Expand Down Expand Up @@ -672,7 +640,7 @@
}
],
"id": "settings-change",
"ms": 3279,
"ms": 3491,
"ok": true
},
{
Expand Down Expand Up @@ -733,14 +701,14 @@
}
],
"id": "person-add",
"ms": 2964,
"ms": 3863,
"ok": true
},
{
"module": "Account",
"feature": "Make an AP 8 I token& 10BC8 quot;,
"note": "",
"clicks": 4,
"clicks": 3,
"keys": 6,
"prompts": 0,
"steps": [
Expand All @@ -760,14 +728,6 @@
"keys": 0,
"prompts": 0
},
{
"page": "/account/tokens",
"kind": "click",
"what": "New token",
"clicks": 1,
"keys": 0,
"prompts": 0
},
{
"page": "/account/tokens",
"kind": "type",
Expand All @@ -786,7 +746,7 @@
}
],
"id": "api-token",
"ms": 2797,
"ms": 3140,
"ok": true
},
{
Expand Down Expand Up @@ -815,7 +775,7 @@
}
],
"id": "namespace-switch",
"ms": 2314,
"ms": 2280,
"ok": true
}
],
Expand Down Expand Up @@ -1010,7 +970,7 @@
"page": "/settings/ai",
"label": "Settings › AI assistant",
"reach": 2,
"controls": 53
"controls": 56
},
{
"page": "/settings/search",
Expand Down
38 changes: 24 additions & 14 deletions ci/clicks/journeys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,25 @@ export const JOURNEYS = [
meter.record("terminal", "paste the install line and press Enter", 0, 2);
meter.openLink("open the installer link");
await page.goto(`${INSTALLER}/?t=survey-token`);
await meter.click(page.locator("#next"), "Continue (this computer)");
await page.getByRole("heading", { name: "How Lens runs" }).waitFor();
await meter.click(page.locator("#next"), "Continue (how it runs)");
const found = page.getByRole("button", { name: /Ollama · \d+ models/ });
await found.waitFor();
// picked already when it's the only one found
if (!(await found.evaluate((el) => el.classList.contains("on"))))
await meter.click(found, "pick the model server found on this computer");
await meter.click(page.locator("#next"), "Continue (AI model)");
await page.getByRole("heading", { name: "Extras" }).waitFor();
await meter.click(page.locator("#next"), "Continue (extras)");
await page.getByRole("heading", { name: "Ready to install" }).waitFor();
await meter.click(page.locator("#next"), "Install Lens");
const quick = page.locator("#quick");
await page.locator("#next").waitFor();
if (await quick.count()) {
await meter.click(quick, "Install with the defaults");
} else {
await meter.click(page.locator("#next"), "Continue (this computer)");
await page.getByRole("heading", { name: "How Lens runs" }).waitFor();
await meter.click(page.locator("#next"), "Continue (how it runs)");
const found = page.getByRole("button", { name: /Ollama · \d+ models/ });
await found.waitFor();
// picked already when it's the only one found
if (!(await found.evaluate((el) => el.classList.contains("on"))))
await meter.click(found, "pick the model server found on this computer");
await meter.click(page.locator("#next"), "Continue (AI model)");
await page.getByRole("heading", { name: "Extras" }).waitFor();
await meter.click(page.locator("#next"), "Continue (extras)");
await page.getByRole("heading", { name: "Ready to install" }).waitFor();
await meter.click(page.locator("#next"), "Install Lens");
}
const open = page.getByRole("button", { name: "Open Lens" });
await open.waitFor({ timeout: 60000 });
await meter.click(open, "Open Lens");
Expand Down Expand Up @@ -291,8 +297,12 @@ export const JOURNEYS = [
async run({ page, meter }) {
await meter.click(page.getByRole("button", { name: "Account menu" }), "Account menu");
await meter.click(page.getByRole("menuitem", { name: /API tokens/ }), "API tokens");
await meter.click(page.getByRole("main").getByRole("button", { name: "New token" }).first(), "New token");
const d = page.getByRole("dialog");
await page.waitForURL(/\/account\/tokens/);
await page.waitForTimeout(500);
// the form is open already when there are no tokens yet
if (!(await d.isVisible()))
await meter.click(page.getByRole("main").getByRole("button", { name: "New token" }).first(), "New token");
await meter.type(d.getByRole("textbox", { name: "Name" }), "script", "name");
await meter.click(d.getByRole("button", { name: "Create token" }), "Create token");
await page.getByText(/copy/i).first().waitFor();
Expand Down
17 changes: 16 additions & 1 deletion installer/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,22 @@ <h1>Install Lens</h1>
f.actions.length ? el("div", {}, el("h3", {}, "Other things to do"),
f.actions.map((a) => el("button", { class: "small", title: a.detail, onclick: () => runAction(a) }, a.label))) : null,
el("div", { id: "msg", class: "msg" }),
nav(() => go(1), { disabled: !f.supported || (!f.curl && !f.windows) }));
quickNav(!f.supported || (!f.curl && !f.windows), !ex));
}
// The first page's way out: install now with the defaults (and the model server found here, if there's one), or
// go through the choices. A first install only; going on from an existing one goes through the steps.
function quickNav(blocked, quick) {
if (!quick) return nav(() => go(1), { disabled: blocked });
return el("div", { class: "nav" },
el("button", { onclick: () => go(1), disabled: blocked, id: "next" }, "Choose options"),
el("button", { class: "primary", onclick: quickInstall, disabled: blocked, id: "quick" }, "Install with the defaults"));
}
async function quickInstall() {
if (needsPassword()) { go(1); return msg("info", "Installing what's missing needs your password first."); }
$("#quick").disabled = true;
if (S.servers === null) await detect();
await install();
if (S.step !== 5 && $("#quick")) $("#quick").disabled = false;
}
async function runAction(a) {
try { await api("/api/action", { id: a.id }); S.step = 5; render(); poll(); } catch (e) { msg("err", e.message); }
Expand Down
4 changes: 3 additions & 1 deletion nextjs-frontend/components/account/tokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Check, Copy, KeyRound, Plus } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { useState } from "react";

import { Tokens } from "@/app/openapi-client";
Expand Down Expand Up @@ -36,7 +37,8 @@ export function TokensPage() {
queryKey: ["tokens"],
queryFn: () => data(Tokens.listTokens({ client })),
});
const [creating, setCreating] = useState(false);
// ?new=1 (the account menu, when there are no tokens yet): the new token's form is open on arrival
const [creating, setCreating] = useState(useSearchParams().get("new") === "1");
const [created, setCreated] = useState<Created | null>(null);
const [revoking, setRevoking] = useState<ApiToken | null>(null);

Expand Down
2 changes: 1 addition & 1 deletion nextjs-frontend/components/app-shell/account-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export function AccountMenu({ name, email }: { name?: string | null; email: stri
</Link>
</MenuItem>
<MenuItem asChild className={ITEM}>
<Link href="/account/tokens">
<Link href={tokens.data?.length === 0 ? "/account/tokens?new=1" : "/account/tokens"}>
<KeyRound />
<span className="flex-1">API tokens</span>
{tokens.data && <span className="tabular text-[12px] font-normal text-fg-muted">{tokens.data.length}</span>}
Expand Down
Loading
0