mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-05 16:03:47 +00:00
feat(cli): Login via Frontend (#956)
* CLI login page * Add Success Page * Change redirect on decline * Center & Size Loading Windmill Icon * Fix goto debug * fix spinner * Workspace from URL * Handle login --token & --workspace Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
co-authored by
Ruben Fiszel
parent
b2324fa51d
commit
2c31a9cbdf
+58
-29
@@ -1,44 +1,74 @@
|
||||
import { Command } from "https://deno.land/x/cliffy@v0.25.4/command/mod.ts";
|
||||
import {
|
||||
Input,
|
||||
Secret,
|
||||
} from "https://deno.land/x/cliffy@v0.25.4/prompt/mod.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
import {
|
||||
setClient,
|
||||
UserService,
|
||||
} from "https://deno.land/x/windmill@v1.50.0/mod.ts";
|
||||
import { colors } from "https://deno.land/x/cliffy@v0.25.4/ansi/colors.ts";
|
||||
import { getStore } from "./store.ts";
|
||||
import { getDefaultRemote, getRemote } from "./remote.ts";
|
||||
import { getDefaultRemote } from "./remote.ts";
|
||||
import { getAvailablePort } from "https://deno.land/x/port@1.0.0/mod.ts";
|
||||
|
||||
export type Options = GlobalOptions;
|
||||
|
||||
async function login(
|
||||
{ remote, baseUrl }: Options,
|
||||
email?: string,
|
||||
password?: string
|
||||
) {
|
||||
if (remote) {
|
||||
baseUrl = baseUrl ?? (await getRemote(remote))?.baseUrl;
|
||||
}
|
||||
async function login({
|
||||
baseUrl,
|
||||
token: existingToken,
|
||||
workspace: existingWorkspace,
|
||||
}: Options) {
|
||||
baseUrl = baseUrl ?? (await getDefaultRemote())?.baseUrl;
|
||||
baseUrl = baseUrl ?? "https://app.windmill.dev";
|
||||
setClient("no-token", baseUrl);
|
||||
const urlStore = await getStore(baseUrl);
|
||||
email = email ?? (await Input.prompt({ message: "Input your Email" }));
|
||||
password =
|
||||
password ?? (await Secret.prompt({ message: "Input your Password" }));
|
||||
|
||||
const token = await UserService.login({
|
||||
requestBody: {
|
||||
email: email,
|
||||
password: password,
|
||||
},
|
||||
});
|
||||
if (existingToken) {
|
||||
await Deno.writeTextFile(urlStore + "token", existingToken);
|
||||
|
||||
if (existingWorkspace) {
|
||||
await Deno.writeTextFile(
|
||||
urlStore + "default_workspace_id",
|
||||
existingWorkspace!
|
||||
);
|
||||
console.log(
|
||||
colors.bold.underline.green(
|
||||
"Successfully logged in & switched to workspace " + existingWorkspace
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log(colors.bold.underline.green("Successfully logged in"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Start listening on port 8080 of localhost.
|
||||
const port = await getAvailablePort();
|
||||
if (port == undefined) {
|
||||
console.log(colors.red.underline("failed to aquire port"));
|
||||
return;
|
||||
}
|
||||
|
||||
const server = Deno.listen({ transport: "tcp", port });
|
||||
console.log(`Login by going to ${baseUrl}/user/cli?port=${port}`);
|
||||
const firstConnection = await server.accept();
|
||||
const httpFirstConnection = Deno.serveHttp(firstConnection);
|
||||
const firstRequest = (await httpFirstConnection.nextRequest())!;
|
||||
const params = new URL(firstRequest.request.url!).searchParams;
|
||||
const token = params.get("token");
|
||||
const workspace = params.get("workspace");
|
||||
await firstRequest?.respondWith(
|
||||
Response.redirect(baseUrl + "/user/cli-success", 302)
|
||||
);
|
||||
|
||||
if (token === undefined || token === null) {
|
||||
console.log(colors.red.underline("Invalid Request. Failed to log in."));
|
||||
return;
|
||||
}
|
||||
|
||||
await Deno.writeTextFile(urlStore + "token", token);
|
||||
console.log(colors.bold.underline.green("Successfully logged in!"));
|
||||
await Deno.writeTextFile(urlStore + "default_workspace_id", workspace!);
|
||||
console.log(
|
||||
colors.bold.underline.green(
|
||||
"Successfully logged in & switched to workspace " + workspace
|
||||
)
|
||||
);
|
||||
|
||||
httpFirstConnection.close();
|
||||
server.close();
|
||||
}
|
||||
|
||||
export async function getToken(baseUrl: string): Promise<string> {
|
||||
@@ -59,7 +89,6 @@ const command = new Command()
|
||||
.description(
|
||||
"Log into windmill. The credentials are not stored, but the token they are exchanged for will be."
|
||||
)
|
||||
.arguments("[email:string] [password:string]")
|
||||
.action(login as any);
|
||||
|
||||
export default command;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import CenteredModal from '$lib/components/CenteredModal.svelte'
|
||||
import WindmillIcon from '$lib/components/icons/WindmillIcon.svelte'
|
||||
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
|
||||
sendUserToast('Sucess. You can return to your terminal now.')
|
||||
goto('/')
|
||||
</script>
|
||||
|
||||
<CenteredModal title="Redirecting to Home...">
|
||||
<div class="w-full ">
|
||||
<div class="block m-auto w-20">
|
||||
<WindmillIcon class="animate-[spin_6s_linear_infinite]" height="80px" width="80px" />
|
||||
</div>
|
||||
</div>
|
||||
</CenteredModal>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
|
||||
import { UserService } from '$lib/gen'
|
||||
import { page } from '$app/stores'
|
||||
import CenteredModal from '$lib/components/CenteredModal.svelte'
|
||||
import { Button } from '$lib/components/common'
|
||||
import { workspaceStore, usersWorkspaceStore } from '$lib/stores'
|
||||
|
||||
let port = Number($page.url.searchParams.get('port'))
|
||||
port = port == 0 || port == NaN ? 80 : port
|
||||
|
||||
async function authorizeToken(): Promise<void> {
|
||||
const newToken = await UserService.createToken({
|
||||
requestBody: {
|
||||
label: 'External Tool Token',
|
||||
expiration: undefined
|
||||
}
|
||||
})
|
||||
|
||||
await goto('http://localhost:' + port + '?token=' + newToken + '&workspace=' + $workspaceStore)
|
||||
}
|
||||
</script>
|
||||
|
||||
<CenteredModal title="Authorize this request for a Token">
|
||||
<p class="text-center text-lg">
|
||||
Token will be posted to your local machine to port {port}
|
||||
</p>
|
||||
<div class="flex flex-row justify-between pt-4 gap-x-1">
|
||||
<Button variant="border" size="sm" href="/">Decline</Button>
|
||||
<button
|
||||
class="place-items-end bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 border rounded"
|
||||
type="button"
|
||||
on:click={authorizeToken}
|
||||
>
|
||||
Authorize
|
||||
</button>
|
||||
</div>
|
||||
</CenteredModal>
|
||||
Reference in New Issue
Block a user