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

Reply via email to