This is an automated email from the ASF dual-hosted git repository. msciabarra pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/openserverless-vscode-extension.git
commit 1adcf3a40b66893f01424b7a07a210b8e69b670e Author: AntonioPiga <[email protected]> AuthorDate: Tue Sep 10 11:14:33 2024 +0200 refactor: clean code methods validate and delete polling login --- src/extension.ts | 169 ++++++++++++++++++++++++---------------------- src/login/LoginPanel.ts | 60 ++++++++-------- src/login/login.ts | 69 ++++++++++--------- src/utilities/validate.ts | 166 ++++++++++++++++++++++----------------------- 4 files changed, 239 insertions(+), 225 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 3db737a..8d770ec 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,106 +1,115 @@ -import * as vscode from 'vscode'; -import { NuvolarisTerminalName } from './constants'; -import { LoginPanel } from './login/LoginPanel'; -import { CliCommands } from './enumerators'; -import * as fs from "fs"; -import * as os from "os"; -import { exec, execSync } from 'child_process'; -import { stderr } from 'process'; +import * as vscode from "vscode"; +import { NuvolarisTerminalName } from "./constants"; +import { LoginPanel } from "./login/LoginPanel"; +import { CliCommands } from "./enumerators"; +import { execSync } from "child_process"; let context: vscode.ExtensionContext; export async function activate(ctx: vscode.ExtensionContext) { - try { - - // Necessary for Theia compatibility of welcome screen. See https://github.com/eclipse-theia/theia/issues/9361 - vscode.window.registerTreeDataProvider( - "command-palette", - new EmptyTreeDataProvider() - ); - // - - context = ctx; - if (!isLoggedIn()) { - LoginPanel.render(handleLogin, context.extensionUri); - } - - Object.entries(CliCommands).forEach(([name, command]) => - context.subscriptions.push(vscode.commands.registerCommand(`nuvolaris.${name.toLowerCase()}`, () => { - if (!isLoggedIn()) { - LoginPanel.render(handleLogin, context.extensionUri); - return; - } - launchTerminal(command); - } - ))); - - context.subscriptions.push(vscode.commands.registerCommand('nuvolaris.login', () => { - LoginPanel.render(handleLogin, context.extensionUri); - })); - } catch (error: any) { - printError(error); - throw error; - } + try { + // Necessary for Theia compatibility of welcome screen. See https://github.com/eclipse-theia/theia/issues/9361 + vscode.window.registerTreeDataProvider( + "command-palette", + new EmptyTreeDataProvider() + ); + // + + context = ctx; + if (!isLoggedIn()) { + LoginPanel.render(handleLogin, context.extensionUri); + } + + Object.entries(CliCommands).forEach(([name, command]) => + context.subscriptions.push( + vscode.commands.registerCommand( + `nuvolaris.${name.toLowerCase()}`, + () => { + if (!isLoggedIn()) { + LoginPanel.render(handleLogin, context.extensionUri); + return; + } + launchTerminal(command); + } + ) + ) + ); + + context.subscriptions.push( + vscode.commands.registerCommand("nuvolaris.login", () => { + LoginPanel.render(handleLogin, context.extensionUri); + }) + ); + } catch (error: any) { + printError(error); + throw error; + } } function isLoggedIn(): boolean { - try { - execSync('nuv -wsk namespace list'); - return true; - } catch (error) { - return false; - } - + try { + execSync("nuv -wsk namespace list"); + return true; + } catch (error) { + return false; + } } function handleLogin(username: string, password: string, apiHost: string) { - const loginOutput = vscode.window.createOutputChannel('Nuvolaris Login'); - loginOutput.show(); - try { - loginOutput.appendLine('Launching login script...'); - const execLogin = execSync(`nuv ide login "${username}" "${apiHost.endsWith('/') ? apiHost.slice(0, -1) : apiHost}"`, { - env: {...process.env, NUV_PASSWORD: password} - }); - loginOutput.appendLine(execLogin.toString()); - printInfo("You successfully logged in. You can now use the Nuvolaris command palette.") - } catch (error: any) { - loginOutput.appendLine(error); - printError("An error occurred in the login process. Check the output window for further details.") - throw error; - } + const loginOutput = vscode.window.createOutputChannel("Nuvolaris Login"); + loginOutput.show(); + try { + loginOutput.appendLine("Launching login script..."); + const execLogin = execSync( + `nuv ide login "${username}" "${ + apiHost.endsWith("/") ? apiHost.slice(0, -1) : apiHost + }"`, + { + env: { ...process.env, NUV_PASSWORD: password }, + } + ); + loginOutput.appendLine(execLogin.toString()); + printInfo( + "You successfully logged in. You can now use the Nuvolaris command palette." + ); + } catch (error: any) { + loginOutput.appendLine(error); + printError( + "An error occurred in the login process. Check the output window for further details." + ); + throw error; + } } - function launchTerminal(command: string): void { - try { - const terminal = vscode.window.createTerminal(NuvolarisTerminalName); - terminal.show(); - terminal.sendText(command, true); - } catch (error) { - printError(error); - throw error; - } + try { + const terminal = vscode.window.createTerminal(NuvolarisTerminalName); + terminal.show(); + terminal.sendText(command, true); + } catch (error) { + printError(error); + throw error; + } } function printError(error: any) { - return vscode.window.showErrorMessage(error.toString()); + return vscode.window.showErrorMessage(error.toString()); } function printInfo(info: string) { - return vscode.window.showInformationMessage(info); - + return vscode.window.showInformationMessage(info); } export function deactivate() {} // Necessary for Theia compatibility of welcome screen. See https://github.com/eclipse-theia/theia/issues/9361 export class EmptyTreeDataProvider implements vscode.TreeDataProvider<any> { - constructor() {} + constructor() {} - getTreeItem(element: any): vscode.TreeItem { - return {}; - } + getTreeItem(element: any): vscode.TreeItem { + return {}; + } - getChildren(element?: any): Thenable<any[]> { - return new Promise((resolve) => resolve([])); - } -} \ No newline at end of file + getChildren(element?: any): Thenable<any[]> { + return new Promise((resolve) => resolve([])); + } +} diff --git a/src/login/LoginPanel.ts b/src/login/LoginPanel.ts index 225f267..27a444d 100644 --- a/src/login/LoginPanel.ts +++ b/src/login/LoginPanel.ts @@ -1,4 +1,3 @@ - import * as vscode from "vscode"; import { getUri } from "../utilities/getUri"; import { getNonce } from "../utilities/getNonce"; @@ -11,21 +10,11 @@ export class LoginPanel { private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { this._panel = panel; this._panel.onDidDispose(() => this.dispose(), null, this._disposables); - this._panel.webview.html = this._getWebviewContent(this._panel.webview, extensionUri); + this._panel.webview.html = this._getWebviewContent( + this._panel.webview, + extensionUri + ); this._setWebviewMessageListener(this._panel.webview); - // this._panel.webview.onDidReceiveMessage(message => { - // switch (message.command) { - // case 'login': - // // Handle login form submission - // const { username, password, apiHost } = message; - // console.log('Submitted login details:', username, password, apiHost); - // // Pass the login details back to the extension - // if (this._onLoginCallback) { - // this._onLoginCallback(username, password, apiHost); - // } - // break; - // } - // }, undefined, this._disposables); } private _setWebviewMessageListener(webview: vscode.Webview) { @@ -48,7 +37,10 @@ export class LoginPanel { this._disposables ); } - private _getWebviewContent(webview: vscode.Webview, extensionUri: vscode.Uri) { + private _getWebviewContent( + webview: vscode.Webview, + extensionUri: vscode.Uri + ) { const webviewUri = getUri(webview, extensionUri, ["out", "login.js"]); const nonce = getNonce(); return /*html*/ ` @@ -57,7 +49,6 @@ export class LoginPanel { <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <!--<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${nonce}'; style-src 'self';">--> <title>Nuvolaris - Login!</title> <style> body { @@ -116,27 +107,38 @@ export class LoginPanel { <img src="https://assets-global.website-files.com/64b64691257b91236b0e7482/64bfcab6bd343008e30e5d06_Nuvolaris%20logo%20white.png" class="logo-img"></img> <h2>Login</h2> <form class="login-form" id="login-form"> - <input type="text" id="username-input" placeholder="Username"> - <input type="password" id="password-input" placeholder="Password"> - <input type="text" id="api-host-input" placeholder="API Host URL" value="https://nuvolaris.dev"> - <vscode-button id="login-button" type="submit" > L O G I N </vscode-button> + <input type="text" id="username-input" name="username" placeholder="Username" autocomplete="username"> + <input type="password" id="password-input" name="password" placeholder="Password" autocomplete="current-password"> + <input type="text" id="api-host-input" name="apiHost" placeholder="API Host URL" value="https://nuvolaris.dev"> + <vscode-button id="login-button" type="submit"> L O G I N </vscode-button> </form> </div> <script type="module" nonce="${nonce}" src="${webviewUri}"></script> </body> </html> - `; + `; } - private _onLoginCallback: ((username: string, password: string, apiHost: string) => void) | undefined; - public static render(onLogin: (username: string, password: string, apiHost: string) => void, extensionUri: vscode.Uri) { + + private _onLoginCallback: + | ((username: string, password: string, apiHost: string) => void) + | undefined; + public static render( + onLogin: (username: string, password: string, apiHost: string) => void, + extensionUri: vscode.Uri + ) { if (LoginPanel.currentPanel) { LoginPanel.currentPanel._panel.reveal(vscode.ViewColumn.One); } else { - const panel = vscode.window.createWebviewPanel("login-panel", "Login", vscode.ViewColumn.One, { - enableScripts: true, - // Restrict the webview to only load resources from the `out` directory - localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'out')] - }); + const panel = vscode.window.createWebviewPanel( + "login-panel", + "Login", + vscode.ViewColumn.One, + { + enableScripts: true, + // Restrict the webview to only load resources from the `out` directory + localResourceRoots: [vscode.Uri.joinPath(extensionUri, "out")], + } + ); LoginPanel.currentPanel = new LoginPanel(panel, extensionUri); } LoginPanel.currentPanel._onLoginCallback = onLogin; diff --git a/src/login/login.ts b/src/login/login.ts index 667c66f..c2bd1e0 100644 --- a/src/login/login.ts +++ b/src/login/login.ts @@ -1,31 +1,33 @@ -import { provideVSCodeDesignSystem, vsCodeButton } from "@vscode/webview-ui-toolkit"; +import { + provideVSCodeDesignSystem, + vsCodeButton, +} from "@vscode/webview-ui-toolkit"; import { LoginPageIDs } from "../enumerators"; -import { validateApiHost, validatePassword, validateUsername } from "../utilities/validate"; +import { validateNotEmptyField } from "../utilities/validate"; provideVSCodeDesignSystem().register(vsCodeButton()); const vscode = acquireVsCodeApi(); window.addEventListener("load", main); const LoginPage = { - getUsername: (): HTMLInputElement => { return document.getElementById(LoginPageIDs.Username) as HTMLInputElement }, - getPassword: (): HTMLInputElement => { return document.getElementById(LoginPageIDs.Password) as HTMLInputElement }, - getApiHost: (): HTMLInputElement => { return document.getElementById(LoginPageIDs.ApiHost) as HTMLInputElement }, - getLoginButton: (): HTMLButtonElement => { return document.getElementById(LoginPageIDs.LoginButton) as HTMLButtonElement } -} + getUsername: (): HTMLInputElement => { + return document.getElementById(LoginPageIDs.Username) as HTMLInputElement; + }, + getPassword: (): HTMLInputElement => { + return document.getElementById(LoginPageIDs.Password) as HTMLInputElement; + }, + getApiHost: (): HTMLInputElement => { + return document.getElementById(LoginPageIDs.ApiHost) as HTMLInputElement; + }, + getLoginButton: (): HTMLButtonElement => { + return document.getElementById( + LoginPageIDs.LoginButton + ) as HTMLButtonElement; + }, +}; function main() { try { - LoginPage.getLoginButton() - .addEventListener("click", handleLoginSubmit); - - LoginPage.getUsername() - .addEventListener("input", validateLogin); - - LoginPage.getPassword() - .addEventListener("input", validateLogin) - - LoginPage.getApiHost() - .addEventListener("input", validateLogin); - + LoginPage.getLoginButton().addEventListener("click", handleLoginSubmit); } catch (error: any) { sendError(error); throw error; @@ -38,7 +40,6 @@ function handleLoginSubmit() { const password = LoginPage.getPassword(); const apiHost = LoginPage.getApiHost(); - validateLogin(); if (validateLogin()) { vscode.postMessage({ command: "login", @@ -47,8 +48,7 @@ function handleLoginSubmit() { apiHost: `${apiHost.value}`, text: `${username.value} ${password.value} ${apiHost.value} PASS`, }); - } - else { + } else { vscode.postMessage({ command: "login", text: `${username.value} ${password.value} ${apiHost.value} NOT PASS`, @@ -60,21 +60,26 @@ function handleLoginSubmit() { } } - function validateLogin() { try { - const loginSubmitButton = LoginPage.getLoginButton(); - let isUsernameValid = validateUsername(LoginPage.getUsername()); - let isPasswordValid = validatePassword(LoginPage.getPassword()); - let isApiHostValid = validateApiHost(LoginPage.getApiHost()); - let isValid = isUsernameValid && isPasswordValid && isApiHostValid; - loginSubmitButton.disabled = !isValid; - return isValid; + const isUsernameValid = validateNotEmptyField( + "username", + LoginPage.getUsername() + ); + const isPasswordValid = validateNotEmptyField( + "password", + LoginPage.getPassword() + ); + const isApiHostValid = validateNotEmptyField( + "Api Host", + LoginPage.getApiHost() + ); + + return isUsernameValid && isPasswordValid && isApiHostValid; } catch (error: any) { sendError(error); throw error; } - } function sendError(error: any) { @@ -82,4 +87,4 @@ function sendError(error: any) { type: "error", text: `Error occurred: ${error.toString()}`, }); -} \ No newline at end of file +} diff --git a/src/utilities/validate.ts b/src/utilities/validate.ts index 3855527..1e5df74 100644 --- a/src/utilities/validate.ts +++ b/src/utilities/validate.ts @@ -1,90 +1,83 @@ import { ValidatorAssertion } from "../interfaces"; export const Validators = { - minChars: (htmlElement: HTMLInputElement, min: number, fieldName: string | null = null): ValidatorAssertion => { - let message = `Field '${fieldName || htmlElement.id}' should contain minimum ${min} chars.` + minChars: ( + htmlElement: HTMLInputElement, + min: number, + fieldName: string | null = null + ): ValidatorAssertion => { + const message = `Field '${ + fieldName || htmlElement.id + }' should contain minimum ${min} chars.`; let isValid = true; isValid = htmlElement.value.length >= min; return { isValid, message, htmlElement }; }, - maxChars: (htmlElement: HTMLInputElement, max: number, fieldName: string | null = null): ValidatorAssertion => { - let message = `Field '${fieldName || htmlElement.id}' should contain maximum ${max} chars.` + maxChars: ( + htmlElement: HTMLInputElement, + max: number, + fieldName: string | null = null + ): ValidatorAssertion => { + const message = `Field '${ + fieldName || htmlElement.id + }' should contain maximum ${max} chars.`; let isValid = true; isValid = htmlElement.value.length <= max; return { isValid, message, htmlElement }; }, - validUrl: (htmlElement: HTMLInputElement, fieldName: string | null = null): ValidatorAssertion => { - let message = `Field '${fieldName || htmlElement.id}' should be a valid URL.` - let pattern = new RegExp('^(https?:\\/\\/)?' + // protocol (optional) - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain - '((\\d{1,3}\\.){3}\\d{1,3}))' + // or IP address - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path (optional) - '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string - '(\\#[-a-z\\d_]*)?$', 'i'); // anchor (optional) - let isValid = pattern.test(htmlElement.value); + validUrl: ( + htmlElement: HTMLInputElement, + fieldName: string | null = null + ): ValidatorAssertion => { + const message = `Field '${ + fieldName || htmlElement.id + }' should be a valid URL.`; + const pattern = new RegExp( + "^(https?:\\/\\/)?" + // protocol (optional) + "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain + "((\\d{1,3}\\.){3}\\d{1,3}))" + // or IP address + "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path (optional) + "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string + "(\\#[-a-z\\d_]*)?$", + "i" + ); // anchor (optional) + const isValid = pattern.test(htmlElement.value); return { isValid, message, htmlElement }; }, - isNotNullOrUndefined: (htmlElement: HTMLInputElement, fieldName: string | null = null): ValidatorAssertion => { - let message = `Field '${fieldName || htmlElement.id}' should not be null or undefined.`; - let isValid = htmlElement.value !== null && htmlElement.value !== undefined; - return { isValid, message, htmlElement } + isNotNullOrUndefined: ( + htmlElement: HTMLInputElement, + fieldName: string | null = null + ): ValidatorAssertion => { + const message = `Field '${ + fieldName || htmlElement.id + }' should not be null or undefined.`; + const isValid = + htmlElement.value !== null && htmlElement.value !== undefined; + return { isValid, message, htmlElement }; }, - isNot: (htmlElement: HTMLInputElement, value: any, fieldName: string | null = null): ValidatorAssertion => { - let message = `Field '${fieldName || htmlElement.id}' should not be ${value}.`; - let isValid = htmlElement.value !== value; - return { isValid, message, htmlElement } - } - -} - -export function validateUsername(input: HTMLInputElement) { - try { - let fieldName = "Username"; - let assertions = []; - let isNotEmpty = Validators.minChars(input, 1, fieldName); - // let shouldBeMax12 = Validators.maxChars(input, 12, fieldName); - assertions.push(isNotEmpty); - // assertions.push(shouldBeMax12); - createErrorMessages(assertions); - return assertions.filter(a => !a.isValid).length === 0; - } catch (error: any) { - throw error; - } - -} - -export function validatePassword(input: HTMLInputElement) { - try { - let fieldName = "Password"; - let assertions = []; - let isNotEmpty = Validators.minChars(input, 1, fieldName); - // let shouldBeMax30 = Validators.maxChars(input, 30, fieldName); - assertions.push(isNotEmpty); - // assertions.push(shouldBeMax30); - createErrorMessages(assertions); - return assertions.filter(a => !a.isValid).length === 0; - } catch (error: any) { - throw error; - } -} + isNot: ( + htmlElement: HTMLInputElement, + value: any, + fieldName: string | null = null + ): ValidatorAssertion => { + const message = `Field '${ + fieldName || htmlElement.id + }' should not be ${value}.`; + const isValid = htmlElement.value !== value; + return { isValid, message, htmlElement }; + }, +}; -export function validateApiHost(input: HTMLInputElement) { - try { - let fieldName = "Api Host"; - let assertions = []; - let isNotEmpty = Validators.minChars(input, 1, fieldName); - /*if(shouldBeUrl.isValid && !/^https?:\/\//i.test(input.value)){ - input.value = "https://" + input.value; - }*/ - assertions.push(isNotEmpty); - createErrorMessages(assertions); - return assertions.filter(a => !a.isValid).length === 0; - } catch (error: any) { - throw error; - } +export function validateNotEmptyField( + fieldName: string, + input: HTMLInputElement +) { + const assertions = []; + assertions.push(Validators.minChars(input, 1, fieldName)); + createErrorMessages(assertions); + return assertions.filter((a) => !a.isValid).length === 0; } - export function createErrorMessages(assertions: ValidatorAssertion[]) { try { assertions.forEach((assertion: ValidatorAssertion, i: number) => { @@ -92,25 +85,30 @@ export function createErrorMessages(assertions: ValidatorAssertion[]) { htmlElement.classList.remove("input-error"); htmlElement.classList.add("input-success"); - let oldErrorMessageElements = Array.from(document.getElementsByClassName("error-message")); + let oldErrorMessageElements = Array.from( + document.getElementsByClassName("error-message") + ); oldErrorMessageElements.forEach((errorMessageElement) => { - if (errorMessageElement.id === `${htmlElement.id}-error-${i + 1}`) errorMessageElement.remove(); - }) + if (errorMessageElement.id === `${htmlElement.id}-error-${i + 1}`) + errorMessageElement.remove(); + }); }); - assertions.filter(a => !a.isValid).forEach((assertion: ValidatorAssertion, i: number) => { - let { htmlElement, message } = assertion; - htmlElement.classList.add("input-error"); - htmlElement.classList.remove("input-success"); + assertions + .filter((a) => !a.isValid) + .forEach((assertion: ValidatorAssertion, i: number) => { + let { htmlElement, message } = assertion; + htmlElement.classList.add("input-error"); + htmlElement.classList.remove("input-success"); - let errorMessageElement = document.createElement("small"); - errorMessageElement.id = `${htmlElement.id}-error-${i + 1}`; - errorMessageElement.classList.add("error-message"); + let errorMessageElement = document.createElement("small"); + errorMessageElement.id = `${htmlElement.id}-error-${i + 1}`; + errorMessageElement.classList.add("error-message"); - errorMessageElement.textContent = message; - htmlElement.parentNode?.insertBefore(errorMessageElement, htmlElement); - }) + errorMessageElement.textContent = message; + htmlElement.parentNode?.insertBefore(errorMessageElement, htmlElement); + }); } catch (error: any) { throw error; } -} \ No newline at end of file +}
