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 84ef89c8ab9ae9b42ade4a882ebf28e8939586ba Author: mattia-tatone <[email protected]> AuthorDate: Thu Mar 7 10:14:04 2024 +0100 Login GUI - updated Login GUI - dynamic validators - code cleaning - error management --- src/extension.ts | 113 +++++++++++++++----------- src/panels/Login.ts | 45 ++++++++--- src/webview/main.ts | 219 ++++++++++++++++++++++++++++++++++++++++++++------- src/webview/main2.ts | 60 ++++++++++++++ 4 files changed, 351 insertions(+), 86 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index be20ac4..c4b0439 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,72 +9,93 @@ let nuvLoginParams: NuvolarisLoginParams = { nuvPassword: process.env.NUV_PASSWORD } export async function activate(context: vscode.ExtensionContext) { - - vscode.window.showInformationMessage('Credenziali ' + nuvLoginParams.nuvUser + ' ' + nuvLoginParams.nuvApiHost + ' ' + nuvLoginParams.nuvPassword); - - if (!validateNuvolarisCredentials()){ - LoginPanel.render(handleLogin, context.extensionUri); - } else { - launchLoginTerminal() + try { + Object.entries(COMMANDS).forEach(([name, command]) => + context.subscriptions.push(vscode.commands.registerCommand(`nuvolaris.${name.toLowerCase()}`, () => { + if (loginIfNotAlreadyLoggedIn(context)) launchTerminal(command) + } + ))); + } catch (error: any) { + printError(error); + throw error; } - - // Check ENV variables - - // Registering the commands from the COMMANDS ENUM. Attaching the launchTerminal event to each command. - Object.entries(COMMANDS).forEach(([name, command]) => - context.subscriptions.push(vscode.commands.registerCommand(`nuvolaris.${name.toLowerCase()}`, () => - launchTerminal(command) - ))); - - - - - -} -function handleLogin(username: string, password: string, apiHost: string) { - // Do something with the login details - storeNuvolarisCredentials(username, apiHost, password) } -function launchTerminal(command: string){ - // If a terminal named "Nuvolaris" already exists, launch the command inside that one. - const terminal = vscode.window.terminals.find(t=>t.name==="Nuvolaris") || vscode.window.createTerminal("Nuvolaris"); - terminal.show(); - terminal.sendText(command, true); +function loginIfNotAlreadyLoggedIn(context: vscode.ExtensionContext) { + try { + let validCredentials = validateNuvolarisCredentials(); + if (validCredentials) launchLoginTerminal(); + else LoginPanel.render(handleLogin, context.extensionUri); + return validCredentials; + } catch (error) { + printError(error); + throw error; + } } -function validateNuvolarisCredentials(){ - return nuvLoginParams.nuvUser && nuvLoginParams.nuvApiHost && nuvLoginParams.nuvPassword +function handleLogin(username: string, password: string, apiHost: string) { + try { + storeNuvolarisCredentials(username, apiHost, password); + } catch (error) { + printError(error); + throw error; + } } -function storeNuvolarisCredentials(nuvUser: string | undefined, nuvApiHost: string | undefined, nuvPassword: string | undefined){ - vscode.window.showInformationMessage('Salvo credenziali ' + nuvUser + ' ' + nuvApiHost + ' ' + nuvPassword); - - - updateNuvLoginParams(nuvUser, nuvApiHost, nuvPassword); - vscode.window.showInformationMessage('Credenziali OBJ ' + nuvLoginParams.nuvUser + ' ' + nuvLoginParams.nuvApiHost + ' ' + nuvLoginParams.nuvPassword); - vscode.window.showInformationMessage('Credenziali ENV' + process.env.NUV_USER + ' ' + process.env.NUV_APIHOST + ' ' + process.env.NUV_PASSWORD); - - launchLoginTerminal() - +function validateNuvolarisCredentials() { + try { + return nuvLoginParams.nuvUser && nuvLoginParams.nuvApiHost && nuvLoginParams.nuvPassword + } catch (error) { + printError(error); + throw error; + } } -function updateNuvLoginParams(nuvUser: string | undefined, nuvApiHost: string | undefined, nuvPassword: string | undefined){ +function storeNuvolarisCredentials(nuvUser: string | undefined, nuvApiHost: string | undefined, nuvPassword: string | undefined) { + try { + updateNuvolarisLoginParams(nuvUser, nuvApiHost, nuvPassword); + launchLoginTerminal(); + } catch (error) { + printError(error); + throw error; + } +} +function updateNuvolarisLoginParams(nuvUser: string | undefined, nuvApiHost: string | undefined, nuvPassword: string | undefined) { nuvLoginParams = { nuvUser: nuvUser, nuvApiHost: nuvApiHost, nuvPassword: nuvPassword } - process.env.NUV_USER = nuvUser; process.env.NUV_APIHOST = nuvApiHost; process.env.NUV_PASSWORD = nuvPassword } -function launchLoginTerminal(){ +function launchLoginTerminal() { // launchTerminal('nuv -login') - launchTerminal(`nuv -login ${nuvLoginParams.nuvApiHost} ${nuvLoginParams.nuvUser} ${nuvLoginParams.nuvPassword}`) + try { + launchTerminal(`nuv -login ${nuvLoginParams.nuvApiHost} ${nuvLoginParams.nuvUser} ${nuvLoginParams.nuvPassword}`) + } catch (error) { + printError(error); + throw error; + } +} + +function launchTerminal(command: string) { + try { + // If a terminal named "Nuvolaris" already exists, launch the command inside that one. + const terminal = vscode.window.terminals.find(t => t.name === "Nuvolaris") || vscode.window.createTerminal("Nuvolaris"); + terminal.show(); + terminal.sendText(command, true); + } catch (error) { + printError(error); + throw error; + } +} + -} \ No newline at end of file +function printError(error: any) { + return vscode.window.showErrorMessage("Si è verificato un errore", error.toString()); +} diff --git a/src/panels/Login.ts b/src/panels/Login.ts index b1448d2..4f1a17a 100644 --- a/src/panels/Login.ts +++ b/src/panels/Login.ts @@ -58,14 +58,13 @@ export class LoginPanel { const webviewUri = getUri(webview, extensionUri, ["out", "webview.js"]); const nonce = getNonce(); - return /*html*/ ` <!DOCTYPE html> <html lang="en"> <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';"> + <!--<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'nonce-${nonce}'; style-src 'self';">--> <title>Nuvolaris - Login!</title> <style> body { @@ -76,7 +75,7 @@ export class LoginPanel { .login-container { max-width: 400px; margin: 0 auto; - background-color: #fff; + background-color: #1da1ce; border-radius: 5px; padding: 20px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); @@ -96,24 +95,46 @@ export class LoginPanel { } .login-form vscode-button { padding: 10px; - background-color: #007bff; + background-color: #ffa944; color: #fff; border: none; border-radius: 3px; cursor: pointer; + font-weight: bolder; + } + + .logo-img { + background-color: #1da1ce; + } + + .input-error { + border: 2px solid red !important; + } + + .input-success { + border: 2px solid green !important; + } + + .error-message { + color: #ffa944; + font-size: 0.8em; + font-weight: 900; } </style> </head> - <body> + <body> <div class="login-container"> - <h2>Login to Nuvolaris</h2> - <form class="login-form" id="loginForm"> - <input type="text" id="username" placeholder="Username"> - <input type="password" id="password" placeholder="Password"> - <input type="text" id="apiHost" placeholder="API Host URL"> - <vscode-button id="login-submit" type="submit">Login</vscode-button> + <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"> + + <vscode-button id="login-button" type="submit" value="https://nuvolaris.dev/"> L O G I N </vscode-button> </form> - </div> + </div> <script type="module" nonce="${nonce}" src="${webviewUri}"></script> </body> </html> diff --git a/src/webview/main.ts b/src/webview/main.ts index b37de27..af3aec0 100644 --- a/src/webview/main.ts +++ b/src/webview/main.ts @@ -1,44 +1,207 @@ -import { Button, provideVSCodeDesignSystem, vsCodeButton } from "@vscode/webview-ui-toolkit"; +import { provideVSCodeDesignSystem, vsCodeButton } from "@vscode/webview-ui-toolkit"; provideVSCodeDesignSystem().register(vsCodeButton()); +const vscode = acquireVsCodeApi(); +interface ValidatorAssertion { + isValid: boolean, + message: string, + htmlElement: HTMLElement +} + +enum LoginPageIDs { + username = "username-input", + password = "password-input", + apiHost = "api-host-input", + loginButton = "login-button" +} + +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 } +} -const vscode = acquireVsCodeApi(); window.addEventListener("load", main); function main() { - // To get improved type annotations/IntelliSense the associated class for - // a given toolkit component can be imported and used to type cast a reference - // to the element (i.e. the `as Button` syntax) - const submitButton = document.getElementById("login-submit") as Button; + try { + const submitButton = LoginPage.getLoginButton(); + + submitButton?.addEventListener("click", handleLoginSubmit) + LoginPage.getUsername().addEventListener("change", validateUsername); + LoginPage.getPassword().addEventListener("change", validatePassword); + LoginPage.getApiHost().addEventListener("change", validateApiHost); + } catch (error: any) { + console.error(error); + sendError(error); + throw error; + } + - submitButton?.addEventListener("click", handleLoginSubmit) } function handleLoginSubmit() { - const username = document.getElementById('username') as HTMLInputElement; - const password = document.getElementById('password') as HTMLInputElement; - const apiHost = document.getElementById('apiHost') as HTMLInputElement; - - let credentials = { - username: username, - password: password, - apiHost: apiHost - } - if (username.value && password.value && apiHost.value) { - vscode.postMessage({ - command: "login", - username: `${username.value}`, - password: `${password.value}`, - apiHost: `${apiHost.value}`, - text: `${username.value} ${password.value} ${apiHost.value} PASS`, - }); - } else { - vscode.postMessage({ - command: "login", - text: `${username.value} ${password.value} ${apiHost.value} NOT PASS`, + try { + const username = LoginPage.getUsername(); + const password = LoginPage.getPassword(); + const apiHost = LoginPage.getApiHost(); + + validateLogin(); + if (validateLogin()) { + vscode.postMessage({ + command: "login", + username: `${username.value}`, + password: `${password.value}`, + apiHost: `${apiHost.value}`, + text: `${username.value} ${password.value} ${apiHost.value} PASS`, + }); + } + /*else { + vscode.postMessage({ + command: "login", + text: `${username.value} ${password.value} ${apiHost.value} NOT PASS`, + }); + }*/ + } catch (error: any) { + console.error(error); + sendError(error); + throw error; + } +} + + +const Validators = { + minChars: (htmlElement: HTMLInputElement, min: number, fieldName: string | null = null): ValidatorAssertion => { + let 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.` + 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); + return { isValid, message, htmlElement }; + } + +} + + +function createErrorMessages(assertions: ValidatorAssertion[]) { + try { + assertions.filter(a => a.isValid).forEach((assertion: ValidatorAssertion, i: number) => { + let { htmlElement } = assertion; + htmlElement.classList.remove("input-error"); + htmlElement.classList.add("input-success"); + + let oldErrorMessageElements = Array.from(document.getElementsByClassName("error-message")); + oldErrorMessageElements.forEach((errorMessageElement) => { + 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"); + + 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); + }) + } catch (error: any) { + sendError(error); + throw error; + } +} + +function validateUsername() { + try { + let fieldName = "Username"; + let input = LoginPage.getUsername(); + let assertions = []; + let shouldBeMin6 = Validators.minChars(input, 6, fieldName); + let shouldBeMax12 = Validators.maxChars(input, 12, fieldName); + assertions.push(shouldBeMin6); + assertions.push(shouldBeMax12); + createErrorMessages(assertions); + return assertions.filter(a => !a.isValid).length === 0; + } catch (error: any) { + sendError(error); + throw error; + } + +} + +function validatePassword() { + try { + let fieldName = "Password"; + let input = LoginPage.getPassword(); + let assertions = []; + let shouldBeMin8 = Validators.minChars(input, 8, fieldName); + let shouldBeMax30 = Validators.maxChars(input, 30, fieldName); + assertions.push(shouldBeMin8); + assertions.push(shouldBeMax30); + createErrorMessages(assertions); + return assertions.filter(a => !a.isValid).length === 0; + } catch (error: any) { + sendError(error); + throw error; + } +} + +function validateApiHost() { + try { + let fieldName = "Api Host"; + let input = LoginPage.getApiHost(); + let assertions = []; + let shouldBeUrl = Validators.validUrl(input, fieldName); + /*if(shouldBeUrl.isValid && !/^https?:\/\//i.test(input.value)){ + input.value = "https://" + input.value; + }*/ + assertions.push(shouldBeUrl); + createErrorMessages(assertions); + return assertions.filter(a => !a.isValid).length === 0; + } catch (error: any) { + sendError(error); + throw error; + } + +} + +function validateLogin() { + try { + const loginSubmitButton = LoginPage.getLoginButton(); + let isValid = validateUsername() && validatePassword() && validateApiHost(); + loginSubmitButton.disabled = !isValid; + return isValid; + } catch (error: any) { + sendError(error); + throw error; } +} + +function sendError(error: any) { + return vscode.postMessage({ + type: "error", + text: `Si è verificato un errore: ${error.toString()}`, + }); } \ No newline at end of file diff --git a/src/webview/main2.ts b/src/webview/main2.ts new file mode 100644 index 0000000..a74e8dc --- /dev/null +++ b/src/webview/main2.ts @@ -0,0 +1,60 @@ +import { Button, provideVSCodeDesignSystem, vsCodeButton } from "@vscode/webview-ui-toolkit"; + +provideVSCodeDesignSystem().register(vsCodeButton()); +const vscode = acquireVsCodeApi(); +window.addEventListener("load", main); + +function main() { + try { + const submitButton = document.getElementById("login-submit") as Button; + submitButton?.addEventListener("click", handleLoginSubmit) + breakpoint("1"); + } catch (error: any) { + breakpoint(error.toString()); + console.error(error); + throw error; + } + // To get improved type annotations/IntelliSense the associated class for + // a given toolkit component can be imported and used to type cast a reference + // to the element (i.e. the `as Button` syntax) + +} + +function breakpoint(title: string) { + const submitButton = document.getElementById("login-submit") as Button; + submitButton.textContent = "Breakpoint " + title; +} + +function handleLoginSubmit() { + try { + breakpoint("2"); + const username = document.getElementById('username') as HTMLInputElement; + const password = document.getElementById('password') as HTMLInputElement; + const apiHost = document.getElementById('api-host') as HTMLInputElement; + + breakpoint("3"); + + if (username.value.trim() && password.value.trim() && apiHost.value.trim()) { + breakpoint("4A"); + vscode.postMessage({ + command: "login", + username: `${username.value}`, + password: `${password.value}`, + apiHost: `${apiHost.value}`, + text: `${username.value} ${password.value} ${apiHost.value} PASS`, + }); + breakpoint("5A"); + } else { + breakpoint("4B"); + vscode.postMessage({ + command: "login", + text: `${username.value} ${password.value} ${apiHost.value} NOT PASS`, + }); + breakpoint("5B"); + } + } catch (error: any) { + breakpoint(error?.toString()); + console.error(error); + throw error; + } +} \ No newline at end of file
