You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openserverless.apache.org by ms...@apache.org on 2025/08/11 09:25:47 UTC
(openserverless-vscode-extension) 03/31: Login GUI
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 <ma...@hotmail.it>
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