commit 880e39de2e42086293e6381181ecfdf43a93f322 Author: Isaac Parenteau Date: Sat Jul 7 20:43:51 2018 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e2ed1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +.classpath +.idea +.project +.settings +portal_client/dist/ +portal_client/nbproject/private/ +portal_client/node/ +portal_client/node_modules/ +portal_client/test_out/ +portal_client/temp/ +portal_client/app/compiled-less +portal_client/app/index.html +portal_client/app/yarn_components/** +portal_webapp/.externalToolBuilders/ +portal_client/yarn.l* +portal_client/yarn-* +npm-debug.log +phantomjs +tags +*.DS_Store +*/bin/ +**/.classpath +**/.settings +**/*.swp +**/target +**/.tern-project +**/*.iml +**/git.properties +/filerepo_api/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2a20dd --- /dev/null +++ b/README.md @@ -0,0 +1,171 @@ +# SETUP - Development +--- +1. Clone the repository from BigMac: + - Add your public key to BigMac account. + - Clone the repository: `git clone "ssh://git@bigmac.northgrum.com:8010/saipt/portal-webapp.git"` +2. Create/Edit `settings.xml` in your `.m2` folder (see next section for example) +3. This project requires ngas-commons to be built prior to building this application + - Checkout `ngas-commons` (`"ssh://git@bigmac.northgrum.com:8010/saipt/ngas-commons.git"`) + - Change directories into `ngas-commons` and run `mvn clean install` +4. Change directories into the project and run `mvn clean install` (This might take awhile) +5. Once the server launches, you should be able to access the webapp by browsing to `http://localhost:8080/portal/` + +## Yarn +--- +Yarn is the the new package manager used with node.js in replacement of `npm` it uses the same package.json file. + +To download a new client side library use the command `yarn add --save`. This will add the files to the `node_modules` library. + +Next include the appropriate file into the `assets.js` file inside `portal_client` to make sure it will be injected into the `index.html` file during run time. + +The `assets.js` file specifies all the vendor libraries that have to be included into the client code during build. + + +## Settings.xml +--- +If developing on the NGGN the proxy settings are needed to download the libraries + +Sample: +```xml + + + + ngas-proxy + true + http + eastproxy.northgrum.com + 80 + (your MyID) + (your login password) + + + ngas-proxy2 + true + https + eastproxy.northgrum.com + 80 + (your MyID) + (your login password) + + + + + flyway-localhost + root + (mysql root password) + + + + tomcat-localhost + (manager-script username) + (manager-script password) + + + + + local-deploy + + C:\development\apache-tomcat-8.5.24-portal + 8.5.24 + http://localhost:8080 + + + + +``` + +## Configuring Tomcat +--- +Tomcat cannot run on port 80 or 443 unless it is is running as root (big security concern with that). It is suggested to use proxy services like `NGINX` or `Apache` to proxy 80 or 443 traffic to redirect to tomcat + +#### NGINX +1. In the default /etc/nginx/conf.d add a file called portal.conf + +2. Populated it with the values below +``` conf + proxy_cache_path /tmp/NGINX_cache/ keys_zone=backcache:10m; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + upstream tomcat { + # Use IP Hash for session persistence + ip_hash; + + # List of Tomcat application servers + server 127.0.0.1:8080; + } + + server { + listen 80; + server_name portal.viprcenter.com; + client_max_body_size 0; + + # Redirect all HTTP requests to HTTPS + location / { + return 301 https://$server_name$request_uri; + } + } + + server { + listen 443 ssl http2; + server_name portal.viprcenter.com; + client_max_body_size 0; + + ssl_certificate /etc/nginx/ssl/portal.viprcenter.com.crt; + ssl_certificate_key /etc/nginx/ssl/portal.viprcenter.com.key; + + ssl_session_cache shared:SSL:1m; + ssl_prefer_server_ciphers on; + + # WebSocket configuration + location / { + proxy_pass http://tomcat; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Host $remote_addr; + } + } +``` + +3. Save the file + +4. Modify `/etc/nginx/nginx.conf` + +5. In the http section of the conf file add `include /etc/nginx/conf.d/portal.conf` + +6. Restart NGINX + +7. Navigate to the url. It should redirect to 443 + + +#### Apache + +1. In apaches `httpd.conf` file add the following +``` conf + Listen 80 + Listen 443 + + + RewriteEngine On + RewriteCond %{HTTP_HOST} ^(.*)$ + RewriteRule ^(.*)$ https://%1$1 [R=Permanent,L,QSA] + + + + SSLEngine On + SSLCertificateFile /etc/apache/ssl/portal.viprcenter.com.crt + SSLCertificateKeyFile /etc/apache/ssl/portal.viprcenter.com.key + + ProxyPreserveHost On + ProxyPass / http://127.0.0.1:8080/ + ProxyPassReverse / http://127.0.0.1:8080/ + +``` +2. Restart and try navigating diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml new file mode 100644 index 0000000..8e5b9d7 --- /dev/null +++ b/bitbucket-pipelines.yml @@ -0,0 +1,28 @@ +pipelines: + default: + - step: + name: Commons build + image: maven:3.3.9 + caches: + - maven + script: + - git clone git@bitbucket.org:locus2k/commons.git + - mvn -B install -f commons/pom.xml + - step: + name: Portal build + image: maven:3.3.9 + caches: + - maven + script: + - cp pipeline-settings.xml $HOME/.m2/settings.xml + - mvn -B install # -B batch mode makes Maven less verbose + services: + - mysql + +definitions: + services: + mysql: + image: mysql:5.7 + environment: + MYSQL_DATABASE: 'portal' + MYSQL_ROOT_PASSWORD: 'let_me_in' diff --git a/docs/src/API.tex b/docs/src/API.tex new file mode 100644 index 0000000..c36cdcd --- /dev/null +++ b/docs/src/API.tex @@ -0,0 +1,40 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : API.tex +% SUBJECT : Document describing the api in Patch Repoistory. +% AUTHOR : (C) Copyright 2018 Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{API} +\label{api} + +The following documents the api calls for 3rd party applications to interact with \portal. + +The url to interact on would be in the format of \command{server:port/portal/endpoint\_url}. + +For example: \command{https://portal.viprcenter.com:443/portal/account/addUser.do}. + +Each connection is either a POST or a GET method. When it is described as being a POST method, the data is sent via the body of the request in \command{JSON} format, while a GET request sends the data as part of the URL. + +Parameter names are case sensitive and have to be verbatim as specified in the parameters section. Parameters marked in \textcolor{red}{\texttt{Red}} are required parameters in the request while parameters marked in \textcolor{gray}{\texttt{Gray}} are optional. + +All responses from the server are also in \command{JSON} format. The servers response raps the return data into a special ``success'' response in the format of: + +\begin{center} +\input{api/sample/SuccessResp} +\end{center} + +\command{success} section is either \command{false} or \command{true} and determines if the return was successful or not. The \command{body} section is the actual data being returned. All ``Sample Responses'' will be in the body section. + +Each section below is broken down to which portion the api calls belong to. + +\input{api/AccountAPI} +\input{api/AppLogAPI} +\input{api/ConfigurationAPI} +\input{api/CredentialsAPI} +\input{api/FileAPI} +\input{api/GitAPI} +\input{api/LoggerAPI} +\input{api/PermissionsAPI} +\input{api/ScriptAPI} +\input{api/SystemAPI} diff --git a/docs/src/Architecture.tex b/docs/src/Architecture.tex new file mode 100644 index 0000000..de41c23 --- /dev/null +++ b/docs/src/Architecture.tex @@ -0,0 +1,84 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : Architecture.tex +% SUBJECT : Document describing architecture issues in the entire Patch Repository system. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{Architecture} +\label{architecture} + +\section{Overview} +The overall architecture of the \portal system is shown in Figure~\ref{fig:arch-portal}. The server provides services for the entire system while the client allows users to interact with +the server to schedule tasks and checkout git repositories. + +\begin{figure}[htbp] + \centering + \scalebox{0.5}{\includegraphics*{figures/Overall-Architecture.png}} + \caption{Overall \portal Architecture} + \label{fig:arch-portal} +\end{figure} + +The server stores all non-volatile data about files in a back end database. This information includes account data, server configuration, hash types, permissions, log information, +schedules, git repositories, and file information. + +Users interact with the \portal system via the \Client\ which gives functionality (depending on permissions) such as adding users, viewing log files, changing server configuration, +changing seed value, modifying group permissions, schedule tasks and checkout repositories. + +\section{Security} +\portal is an online website and can be accessible by the public if placed on a public facing web server. It is the nature that malicious actors may attempt to deny, degrade or +disrupt data transmission form the client to server by manipulating the network over which the server is being conducted. It is therefor desirable for \portal to provide some degree +of security against these threats. This section outlines issues involved in \portal security. Specific information about how security related matters have been implemented is given in later chapters. + + \subsection{Threat Model} + The general environment in which a \portal website is hosted is assumed to be potentially hostile. Specifically the analysis of the \portal's security makes the following assumptions: + + \begin{enumerate} + \item \Server\ does not trust users or client programs acting on their behalf. + \item The network is hostile and in control of a Dolev-Yao attacker. Sucn an attacker has the following capabilities: + \begin{enumerate} + \item Able to read all packets everywhere simultaneously. + \item Able to modify any packet. + \item Able to inject new packets on any link. + \item Able to record arbitrary amounts of network data and replay it anywhere at any time. + \item Able to block, delay, or rearrange packets on any link + \end{enumerate} + \item The attacker has full access to the \portal source code (both client and server) and any predefined constant data stored by either client or server. + \end{enumerate} + + In order for a reasonable security system to be designed, some trust and some limitations on the attacker's capabilities must be assumed. Specifically: + + \begin{enumerate} + \item \Server\ trusts the users once authenticated + \item The Dolev-Yao attacker can not: + \begin{enumerate} + \item Read or modify computations done on any legitimate machine. + \item Break any cryptographic algorithms used. + \end{enumerate} + \end{enumerate} + + Note also the data transferred between client and server is not considered confidential (with the exception of password or other authentication information). Thus encrypting the data + on the network is is not necessary. + + There are several threats that \portal should counter. Specifically: + + \begin{enumerate} + \item A user should not be able to log in as another user. + \item A user should not be able to navigate to any other page if the user is not logged in (should default to the login page) + \item An unauthenticated user should not be able to upload files to the server + \item User's without proper permissions should not be able to delete files + \item It should not be possible to disrupt server transactions between a client and the server by introducing bogus packets on the network, or by replaying previous recorded packets. + \end{enumerate} + + Note that \portal does not need to protect itself against attacks involving blocked packets. A Dolev-Yao attacker will be able to disrupt tye system by causing network communication + failure. However, \portal clients and server should make an attempt to detect such a situation and alert the user accordingly. + + \subsection{Setup} + \portal can be configured to run on a single server. A typical setup is as follows: + \begin{enumerate} + \item Connections come on port 80 or 443 to either an \command{NGINX} or \command{apache} proxy + \item The proxy will direct connections to the \command{tomcat} server which hots the \portal application + \item \command{Tomcat} will interact with the \command{MySQL} database to store/retrieve information + \end{enumerate} + + \ No newline at end of file diff --git a/docs/src/Client.tex b/docs/src/Client.tex new file mode 100644 index 0000000..6ac1e26 --- /dev/null +++ b/docs/src/Client.tex @@ -0,0 +1,117 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : Client.tex +% SUBJECT : Document describing design issues in Patch Repoistory. +% AUTHOR : (C) Copyright 2018 Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{\Client} +\label{client} + +\section{Requirements} + +\Client\ is a set of javascript/html libraries that is executed for the user to interact with the \Server. The code uses various 3rd party libraries such as AngularJS, Angular-Material, JQuery, and inhouse +written code to render the web pages to the user. It sends REST commands to the server and waits for a response in which the information requested will be displayed. It also subscribes to the server on a +web socket to receive asynchronous messages from the server. + +\subsection{Functional Requirements} + +Once the user authenticates with the server, they are allowed to perform some session-only actions. These actions include (depending on permissions): +\begin{enumerate} + \item Add/Edit/Delete Scripts + \item Add/Edit/Delete Git Repositories + \item Add/Edit/Delete Credentials + \item Add/Edit/Delete Email Templates + \item Add/Edit/Delete Users. + \item Add/Edit/Delete Permission Groups + \item Add/Edit/Delete Environment Variables + \item View/Purge system logs + \item Modify the server configuration value + \item Change seed value + \item Change SMTP settings for mail + \item Change the log level of the server logger +\end{enumerate} + +The functions granted to the user are all determined in which permission group the user belongs to. This allows site admins to specify who gets access to what features. + +\subsection{Non-Functional Requirements} + +Since \Client\ is web based any platform can access the website provided they have the correct url to connect to. + +\subsubsection{Performance} + +Since this is a web based application, the connecting clients do not have strict performance requirements. + +The following is recommended for clients connecting to the server: + +\begin{itemize} + \item 8GB or more RAM + \item Dual Core processor or greater + \item Latest Chrome, Edge or Firefox browser +\end{itemize} + +The application renders best on Chrome, Edge or Firefox, and Internet Explorer should not be used. With that being said, if Internet Explorer is the only option, it should be the latest version (IE11). +It to be noted that there might be some rendering issues using IE11 as it handles rendering html/javascript differently than what is recommended by industry standards. + +\subsubsection{Security} + +Since \portal is a web based application, it is recommended that clients try to connected to the server via HTTPS connection to secure the data being sent over the network. If the server is +configured correctly, the client should redirect to HTTPS if trying to connect to normal HTTP. + +Once authenticated the user is assigned permissions based on the permission group the they belong to. These permissions determine which actions the user can take and which tabs the user can view. +The user should not be able to perform actions outside the permissions granted to them. + +\subsubsection{User Characteristics} + +Users of the \Client\ do not need to have programming experience as it is a web based applications. Users should reference Chapter~\ref{userGuide} on how to perform desired tasks. + +\subsubsection{Scale} + +Scaling the client is not necessary as each client creates individual sessions to the server and many clients can simultaniously connect. It is limited to how many connections the host server can +handled and its performance specifications. + +\subsubsection{Data Formats} + +Data is passed between the client and server as a json formatted string. + +\subsubsection{Internationalization} + +\Client\ javascript/html pages are all written in US English. There are no plans to change this currently. + +\section{Design \& Architecture} + +\Client\ makes use of various 3rd party and inhouse libraries to present the user with a rich experience. It takes advantage of current industrial standard libraries to render the pages +(AngularJS and Angular-Material). + +\subsection*{AngularJS} + +AngularJS is a JavaScript-based open-source front-end web application framework mainly maintained by Google and by a community of individuals and corporations to address many of the challenges encountered +in developing single-page applications. The JavaScript components complement Apache Cordova, a framework used for developing cross-platform mobile apps. It aims to simplify both the development and the +testing of such applications by providing a framework for client-side model–view–controller (MVC) and model–view–viewmodel (MVVM) architectures, along with components commonly used in rich Internet applications. + +The AngularJS framework works by first reading the HTML page, which has additional custom tag attributes embedded into it. Angular interprets those attributes as directives to bind input or output parts of +the page to a model that is represented by standard JavaScript variables. The values of those JavaScript variables can be manually set within the code, or retrieved from static or dynamic JSON resources. + +\subsubsection*{Angular-Material} + +AngularJS Material is both a UI Component framework and a reference implementation of Google's Material Design Specification. This project provides a set of reusable, well-tested, and accessible +UI components based on Material Design. + +Material Design is a specification for a unified system of visual, motion, and interaction design that adapts across different devices and different screen sizes. + +\section{Compiling} + +The client does not require compiling but instead all client code is incorperated into the final \filename{war} file by copying the contents into it during server build. The only special functionality +is during build, the client's \filename{index.html} is rewritten to included the 3rd party references located in the \filename{assets.js} file then copied over to the final product. + + + + + + + + + + + + diff --git a/docs/src/Deployment.tex b/docs/src/Deployment.tex new file mode 100644 index 0000000..f29737d --- /dev/null +++ b/docs/src/Deployment.tex @@ -0,0 +1,109 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : Deployment.tex +% SUBJECT : Document describing deployment issues in Patch Repository. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{Deployment} +\label{deployment} + +This chapter describes how to build and deploy a \portal system. The audience for this chapter is \portal administrators and power users. This chapter also contains information that +may be of use to \portal developers since developers will need to configure a working \portal system for testing and development purposes. + +\portal comes packaged as a \command{war} file. It is recommended to use \command{Tomcat} to host the web application. + +\portal interacts with a \command{MySQL} database. It is important to know the root username and password to the mysql server. + +Upon start up the server will copy \filename{portal.properties} to the tomcat conf directory. It might initially fail as the defaults could be incorrect +(see Appendix~\ref{sample-portal.properties}). It is possible to create this file in the conf directory before start up and change the configuration values accordingly. +The only value that should need changing is the MySQL root password. See Subsection~\ref{mysql-root-password} to encrypt the root password + +\section{Checking out and building web application} +Remote into the web server that is going to host the web application. It is recommended for the server that is going to host the application should have Tomcat, MySQL and Apache or +NGINX installed as services and configured as needed. While this is recommended, those services do not have to be all on the same machine. Configuration changes will need to be made +in the \filename{settings.xml} file (see~\ref{settings.xml}) to point to the proper tomcat and mysql instances. + +The following is a list of software that are required to be installed and on be on the system environment path or user path for the build process to work: +\begin{enumerate} + \item Maven \MavenVersion\ \cite{maven} + \item Java \JavaVersion\ \cite{java} + \item Ant \AntVersion\ \cite{ant} +\end{enumerate} + +Two projects need to be checkout out from the git repository. The \portal project and the ngas-commons project. + +Section~\ref{dev-setup} describes how to checkout the \portal project + +Section~\ref{ngas-commons} descrives how to checkout and build the \command{ngas-commons} library. This library is needed to build the main web application + +To build the project issue the command \command{mvn clean install antrun:run@warcopy} (if tomcat is not running) or \command{mvn clean install tomcat7:redeploy} +(if tomcat is currently running). This might take awhile as it will download the required libraries from the internet and deploy the war file to the tomcat server. + +Once the build has completed and deployed verify in the logs the application has come up with no errors. The log files can be found in the tomcat home directory under +\filename{logs/portal.out} + +\section{Encrypted MySQL Root Password} +\label{mysql-root-password} + +To encrypted the mysql root password first checkout and compile the \command{ngas-commons} library (See section~\ref{ngas-commons} to perform the task). + +Once the commons library has been downloaded and installed perform the following. + +\begin{legal} + \item Navigate to the \command{.m2} directory located in the home directory + \item Navigate to \filename{repository/com/ngas/ngas-commons/\ngasCommonsVersion} + \item Execute the following command\newline + \command{java -cp ngas-commons-\ngasCommonsVersion.jar net.locusworks.commons.crypto.AES }\newline + Where ``root-password'' is the MySQL root password +\end{legal} + +Once the properties file has been modified/created, start tomcat and the application should start up as normal. + +\section{Seed File} +\label{seed-file} + +All protected data such as passwords and private keys are encrypted using AES. To increase security, the AES key relies on a seed\footnotemark\ +file (see Appendex~\ref{sample-seed-file}). This file is loaded into java's \filename{SecureRandom} class to generate the AES key. + +The \filename{portal.properties} (see Appendex~\ref{sample-portal.properties}) specifies where this seed file is located. This file should be in a protected area with strict access. + +During the course of operations it might be necessary for the seed file to be changed. This can be done in the application provided the user has the proper permissions to do so +(See Chapter~\ref{aesSeed}). The process will update the seed file and all protected data using the new seed. + +Some legacy operating systems do not support setting seeds within a random number generator such as \filename{SecureRandom}; therefore, legacy systems running \portal will fall back +to using the key defined within the code itself. +This can pose a security so it is advise to upgrade the server in which the application runs on to a newer operating system. + +\footnotetext{A seed is a number or vector used to initialize a pseudorandom number generator. It will generate the same output every time if the same seed is used} + +\section{Configuring Tomcat} +\label{conf-tomcat} + +Tomcat cannot run on port 80 or 443 unless it is running as root which is not ideal. It is suggested to use a proxy service like \command{NGINX} or \command{Apache} to proxy 80 or 443 +traffic to redirect to tomcat. + +\subsection{NGINX} +NGINX can be configured to proxy 80 or 443 traffic to the tomcat server to host up the content. It can also be used to store the SSL/TLS certificates for https + +\begin{enumerate} + \item In the default \command{/etc/nginx/conf.d} directory add a file called \filename{portal.conf} + \item Populate it with the values that can be found in Appendix~\ref{sample-nginx.conf} + \item Save the file + \item Modify \filename{/etc/nginx.conf} + \item In the http section of the conf file add \command{include /etc/nginx/conf.d/portal.conf} + \item Restart NGINX + \item Navigate to the url. It should redirect to 443 and serve up the application content +\end{enumerate} + +\subsection{Apache} +Like NGINX apache can also be configured to proxy and or 443 traffic to the tomcat server and be used to store the SSL/TLS certificates. + +\begin{enumerate} + \item In the default \command{/etc/apache2/} directory add a file called \filename{httpd.conf} + \item Populate it with the values that can be found in Appendix~\ref{sample-httpd.conf} + \item Save the file + \item Restart Apache (httpd) + \item Navigate to the url. It should redirect to 443 and serve up the application content +\end{enumerate} + diff --git a/docs/src/Development.tex b/docs/src/Development.tex new file mode 100644 index 0000000..ec4a2c0 --- /dev/null +++ b/docs/src/Development.tex @@ -0,0 +1,123 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : Development.tex +% SUBJECT : Document describing development issues in Patch Repository. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{Development} +\label{development} + +This chapter describes how to develop, and build the \portal system. The audience for this chapter is \portal developers. + +\section{Setup} +\label{dev-setup} +This section describes how to setup the \portal environment to be able to build the web application + +\subsection{Development} +\begin{enumerate} + \item Clone the repository from BigMac + \begin{enumerate} + \item Add developers public key to BigMac account + \item Clone the repository \newline + \command{git clone ``ssh://git@bigmac.northgrum.com:8010/saipt/portal-webapp.git''} + \end{enumerate} + + \item Create/Edit \command{settings.xml} in the \command{.m2} located in the home directory. See Subsection~\ref{settings.xml} + \item \portal requires \command{ngas-commons} library to be built prior to building the web application. See Subsection~\ref{ngas-commons} + \item Change directories into the project and run \command{mvn clean install antrun:run@warcopy}. This could take some time to download the required libraries. + \item Start the tomcat server and navigate to \command{http://localhost:8080/portal/} to make sure the server comes up +\end{enumerate} + +\subsection{NGAS Commons} +\label{ngas-commons} +NGAS Commons is a common library of functions that other java applications can take advantage of. + +To checkout the library:\newline +\command{git clone ``ssh://git@bigmac.northgrum.com:8010/saipt/ngas-commons.git''} + +To build:\newline +\command{mvn clean install} + +Note: \command{ngas-commons} needs to be built prior to building the main application + +\section{Yarn} +\label{yarn} +Yarn is the new package manager used with \command{node.js} in replacement of \command{npm} it uses the same \command{pacakge.json} file that \command{npm} did. + +To download a new client side library use the command \command{yarn add --save}. This will add the file to the \command{node\_modules} library. + +Next include the appropriate file into the \command{assets.js} file inside \command{portal\_client} to make sure it will be injected into the \command{index.html} file during compile time. + +The \command{assets.js} file specifies all the vendor libraries that have to be included in the client html during build. + +\section{Grunt} +\label{grunt} +Grunt\cite{grunt} allows for live reloading of client code to reflect any changes done on the client javascript/html pages during development. This requires the application to be running +locally on port 8080. Grunt also has to be installed locally and on the users path to be able to execute properly. + +To execute Grunt change directories to the client project \filename{portal\_client} and issue the command \command{grunt onlyServe}. This will start the proxy server and open a web browser +that points to the proxy process on \command{127.0.0.1 port 9000}. Navigating to this url will load content from the local client content and not from the deployed client content. Making +changes and saving the client code will automatically cause the site to refresh and will reflect the changes. + +The deployed code is all minified and any javascript errors would not be easily debugged. Running grunt makes it reference the source material and allows the developer to debug the +javascript that was causing the errors and also show up properly in the development console within the browser. + +\section{Settings.xml} +\label{settings.xml} +Settings.xml allows for user specific keys to build the application. Each developer needs this file to build the application property + +Sample \command{settings.xml} file can be found in Appendex~\ref{sample-settings.xml} + +\section{IDE} + +Most modern IDE's such as Eclipse, NetBeans, IntelliJ, can import existing maven projects. Once the project has been checked out, import into the IDE an existin maven project. Eclipse +allows SCM checkout directly from the IDE. + +\portal was developed using Eclipse \EclipseVersion\ and is the recommended IDE of choice. + +\subsection{NetBeans} +\label{netbeans} +While the application was developed using Eclipse, the JPO\footnotemark\ classes were generated using NetBeans as they are well defined. +\footnotetext{Java Persistence Objects} + +To generate the JPO objects load at least the portal\_database project into netbeans. Right click on \filename{net.locusworks.portal.database.entities} package and choose +\command{New -> Entities Classes From Database} + +Set up the connect to connect to the portal database and choose Add All (but then remove \_flyway\_migration table) as shown in Figure~\ref{fig:netbeans-db} then click next + + +\begin{figure}[htbp] + \centering + \scalebox{0.5}{\includegraphics*{figures/netbeans-database.png}} + \caption{NetBeans Database Connection} + \label{fig:netbeans-db} +\end{figure} + +In the ``Entity Classes'' section, make sure generation type is set to either ``New'' or ``Recreate'' (this can be changed by choosing the \command{\ldots} below the class name). + +Make sure ``Generate Named Query Annotations for Persistent Fields'' and ``Generate JAXB Annotations'' are not selected as shown in Figure~\ref{fig:netbeans-ec} then click next + +\begin{figure}[htbp] + \centering + \scalebox{0.5}{\includegraphics*{figures/netbeans-entityclass.png}} + \caption{NetBeans Entity Classes} + \label{fig:netbeans-ec} +\end{figure} + +\newpage + +In the final window make sure the ``Collection Type'' is set to \command{java.util.list} and only ``Fully Qualified Database Table Names'' and ``Use Column Names in Relationships'' +are selected as shown +in Figure~\ref{fig:netbeans-mo}. + +This will generate all the JPO classes which map the database tables into java objects. + +\begin{figure}[htbp] + \centering + \scalebox{0.5}{\includegraphics*{figures/netbeans-mapping.png}} + \caption{NetBeans Mapping Options} + \label{fig:netbeans-mo} +\end{figure} + + diff --git a/docs/src/Introduction.tex b/docs/src/Introduction.tex new file mode 100644 index 0000000..f0a0be1 --- /dev/null +++ b/docs/src/Introduction.tex @@ -0,0 +1,27 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : Introduction.tex +% SUBJECT : Document describing the portal system in general terms. +% AUTHOR : (C) Copyright 2018 by Nortrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{Introduction} +\label{introduction} + +\portal is a website designed to be a central area where a user can schedule recurring automated tasks. The user can configure the service to connect to remote machines and execute +scripts on those machines or perform execution of local tasks. It can also send and receive files to/from remote machines. + +The \portal can checkout git repositories locally if need be. This allows for source controlled scripts to be utilized on the web site as well just in case certain external code needs +to be updated before local execution can be performed. + +\portal consists of several interacting programs. + +\begin{enumerate} +\item \bold{\Client} +The client (written in javascript and html) is used by the user to create scheduled tasks, git repositories and general configuration of the \portal. +\item \bold{\Server} +The server (written in Java v\JavaVersion) manages the activity of the entire \portal system. +\end{enumerate} + +This document describes the entire \portal system's requirements, design, and implementation. If you plan to work on \portal development you should read this entire document. +If you are a \portal administrator or user you may wish to read Chapter~\ref{deployment} describing how to deploy a \portal system. diff --git a/docs/src/Server.tex b/docs/src/Server.tex new file mode 100644 index 0000000..270fff2 --- /dev/null +++ b/docs/src/Server.tex @@ -0,0 +1,144 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : Server.tex +% SUBJECT : Document describing design issues in the Patch Repository Server. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{\Server} +\label{backend-server} + +\section{Requirements} + +\Server\ is the central connection point for any type of \portal client. This includes not only the web client, but the API and any scripts that can issue REST commands. +\Server\ implements Stomp Messages for message passing and handling. It can send these messages internally or, utilizing websockets, back up to the client. \Server\ also +has direct access to the database and all queries go through it to retrieve the desired information. This section describes the server's responsibility. + +\subsection{Functional Requirements} + +Once the \Server\ is started, it will create the message queuing processes and subscribe to them. It will also receive connections from any client trying to connect. During this attempt +to connect the client-server interaction will pass data via websockets to make sure websockets can be utilized. If not a warning will appear on-screen indicating to to the user the +process will function in a degradated state, otherwise no message will be passed but a green check mark will be displayed to the user. + +The \Server\ is responsible for checking users group permissions to make sure they are granted certain authority to perform actions. These user groups are completely customizable by +the end user but by default there are two groups pre initialized: user, \italic{user} and \italic{administrator} +\begin{enumerate} + \item Unauthenticated cannot access the site + \item Authenticated users in the \italic{User} group is the default group that is granted the following permissions by default: + \begin{enumerate} + \item Login + \item View Scripts Tab + \item View Git Tab + \end{enumerate} + \item Autenticated users in the \italic{Administrator} group is granted complete control by default such as: + \begin{enumerate} + \item Add/Edit/Delete Scripts + \item Add/Edit/Delete Git Repositories + \item Add/Edit/Delete Credentials + \item Add/Edit/Delete Email Templates + \item Add/Edit/Delete Users. + \item Add/Edit/Delete Permission Groups + \item Add/Edit/Delete Environment Variables + \item View/Purge system logs + \item Modify the server configuration value + \item Change seed value + \item Change SMTP settings for mail + \item Change the log level of the server logger + \end{enumerate} +\end{enumerate} + +Users, with the correct permission, have the ability to add new permission groups for use and to assign to other users. A user can only belong ton one permission group. + +It is expected for the server to respect the permissions granted to the user and not allow for actions to be performed in which the user is not allowed to perform. + +\subsubsection{Statistics Tracking} + +Currently there is no statistics tracking done by the \Server. It is a possiblity to be implemented in a later date in which this section of the documentation will be updated to reflect what is being tracked. + +\subsection{Non-Functional Requirements} + +\subsubsection{Platform} + +\Server\ is written in Java and does not depend on a certain operating system or operatying system functions. IT will run on any platform that meets the following requirements: +\begin{enumerate} + \item Supports Java \JavaVersion + \item Supports MySQL \MySQLVersion\ (only if mysql is to be ran on the same server as the application) + \item Supports Tomcat \TomcatVersion +\end{enumerate} + +\subsubsection{Performance} +Beceause slow performance on the host server does impact the performance, \Server does have strict performance requirements. Below is the minimum recommendations requirements to run \Server. +\begin{itemize} + \item 16 GB of RAM + \item Dual Core CPU running at 2 GHZ or greater + \item 8 GB of RAM allocated to the Tomcat Service + \item 25 GB of hard drive space. +\end{itemize} + + +\subsubsection{Security} + +The main server acts as the central connection point for every client, including the API and custom scripts, and most actions require authentication from each of these clients; therefore, security in \Server\ +is a high priority. + +Every client must authenticate with \Server. Once authenticated, actions from the client are generally considered to be non-sensitive, so SSL is not necessary once a session has been established. It is +recommended to either configure tomcat or the proxy service to use SSL/TLS to encrypt the web traffic between the client and server. + +Passwords will be stored as a salted hash in a MySQL database column. Once \Server\ receives the password from the client, it performs a SHA-1 hash with salt on it. Since the hash is considered irreversible, +it would be impossible to log in using that hash if one were somehow able to steal it. The database should be on an the same machine or on an isolated network with no access allowed except for the server, so +packet sniffing efforts are not likely to produce results. + +\subsubsection{User Characteristics} + +\Server\ will be run and managed by administrators, who are expected to be competent in the operating system used. The user must be familiar with passing command-line arguments into programs, +editing configuration files, and must have some experience securing their system (particularly the firewall configuration). + +\subsubsection{Scale} + +While the main server has high performance requirements it is unlikely the server will need to be expanded into grid computing or server clusters. +\footnote{This may change considerably if \portal gains thousands of active users.} + +\subsubsection{Data Formats} + +The server reads a custom configuration file upon startup called \filename{portal.properties}. Configuration properties are formatted like: +\command{key=value} + +Data is passed between the client and server as a json formatted string. + +\subsubsection{Internationalization} + +\Server\ configuration files, databases, log outputs and debug outputs are all in US English. There are no plans to change this currently. + +\section{Design \& Architecture} + +The \Server\ can handle serveral different types of clients and all clients wishing to interact with the files connect to this server. This is done for two main reasons: +\begin{enumerate} + \item It's easier to maintain N number of types of clients under one server + \item I'ts easier to add multiple types of clients as long as they use the API format +\end{enumerate} + +The server is the only connection point that will pass information back to the client. + +\section{Compiling} + +\Server\ is written in Java, which is a compiled language. Java files (\filename{*.java}) are compiled to \filename{*.class}. Any errors during compile time will cause the overall build to fail. +Even with a successful build, it could still potentially fail to run. The only way to test if the program will successfully run is to provide 100\% coverage via unit testing. +\footnote{Please see the section on testing.} + +Eclipse's Java EE plugin is used to develop the JAVA code. Projects can be imported to Eclipse and executed from there. The program is compiled into a \filename{.war} file and cannot be directly ran from Eclipse. +It is possible to configure Eclipse to attach to the tomcat instance and control the instance from there which will cause the program to start and stop. + +To compile and copy the \filename{war} file to the tomcat directory when the tomcat instance is not running perform the following commands (provided proper permissions to copy the file to the tomcat directory): +\newline +\command{mvn clean install antrun:run@warcopy} + + +To compile and redeploy the \filename{war} file to tomcat when tomcat is running perform the follow commands (provided the manager-script role as been assigned and specified)\newline +\command{mvn clean install tomcat7:redeploy} +\newline +All 3rd party JAVA libraries will be downloaded and incorporated into the build. There is no need to manually download the 3rd party libraries. + + + + + diff --git a/docs/src/ToolsLibraries.tex b/docs/src/ToolsLibraries.tex new file mode 100644 index 0000000..1bc4d2f --- /dev/null +++ b/docs/src/ToolsLibraries.tex @@ -0,0 +1,247 @@ +\chapter{Tools \& Libraries} +\label{toolslibraries} + +This chapter describes the tools and libraries used in the \portal development environment. Both where to get the necessary components and how to set them up are covered here. +This chapter is only of interest to \portal developers. If you are a \portal player or administrator you do not need to read this chapter. Instead the details for setting up the +components of a working \portal system are described in Chapter~\ref{deployment} on Deployment. + +The following list reflects the tools and libraries used by the \portal developers. + +\begin{itemize} + \item Eclipse \EclipseVersion + \item NetBeans \NetBeansVersion + \item Java \JavaVersion + \item Maven \MavenVersion + \item Ant \AntVersion + \item AngularJs \AngularJsVersion + \item Spring \SpringVersion + \item Hibernate \HibernateVersion + \item Quartz \QuartzVerson + \item JSch \JSchVersion + \item JGit \JGitVersion + \item Node.js \NodeVersion + \item Yarn \YarnVersion + \item Flyway \FlywayVersion + \item Tomcat \TomcatVersion +\end{itemize} + +\section{Eclipse} +Eclipse\cite{eclipse} provides IDEs and platforms for nearly every language and architecture such as Java, C/C++, JavaScript and PHP IDEs. It is built on extensible platforms for +creating desktop, Web and cloud IDEs. These platforms deliver the most extensive collection of add-on tools available for software developers. + +The \portal uses Eclipse freely to develop both the client and server side code. Eclipse is not needed to build the application. The offical version of Eclipse used by the \portal +project is \EclipseVersion. Other nearby versions are likely to work as long as it supports Java \JavaVersion + +\section{NetBeans} +NetBeans\cite{netbeans} is an integrated development environment (IDE) for Java. NetBeans allows applications to be developed from a set of modular software components called modules. +NetBeans runs on Microsoft Windows, macOS, Linux and Solaris. In addition to Java development, it has extensions for other languages like PHP, C, C++ and HTML5, Javadoc and Javascript. +Applications based on NetBeans, including the NetBeans IDE, can be extended by third party developers. + +The \portal uses NetBeans to generate the database entity classes for use as it is more robust than what Eclipse provides. See Chapter~\ref{development} section~\ref{netbeans} for +more information + +\section{Java} +Java is the language the server is written in \cite{java}. + +\portal uses extensive features that are provided in version \JavaVersion, therefore; previous versions of java will not be able to compile the server code. + +\section{Maven} +Maven is utilized for the build process \cite{maven}. It dynamically downloads the required libraries as specified in the \filename{pom.xml} +files as to reduce the size of the overall projects. The downloaded libraries are stored within the users home directory under \filename{.m2/repository}. +First maven will look inside the local repository for the required library and if its not found, it will download it from the internet automatically. With that being said, +an active internet connection is required to build the web application for the first time. It is possible to configure the \filename{pom.xml} files for offline builds but requires +the libraries to be pre staged in the local repository or a service such as Nexus. + +To build the web application, be in the root project folder and issue the commaond \command{mvn install}. This will start the download process and run through various tasks to build +and verify the application. If any part of the process fails to complete (such as a failed java compile or invalid javascript) the build will be terminated and will not continue until +the errors are corrected. + +The \portal utilizes Maven version \MavenVersion for its build process. It is possible to use older versions of Maven as long as it is version 3.0+ + +\section{Ant} +Ant\cite{ant} is only utilized to copy the war file to the tomcat directory if tomcat is not currently running. The maven command \command{antrun:run@warcopy} is the command that will +invoke the ant process. \portal utilizes ant version \AntVersion but older versions of Ant will work since the process of copying files has not changed + +\section{AngularJs} +AngularJs\cite{angularjs} is extensively used inside the client html code along with various other 3rd party javascript libraries. The list of 3rd party libraries used in the client +can be found in \filename{assets.js}. It is not necessary to download the AngularJs library as it will be downloaded automatically along with the other vendor libraries during the build +process. Yarn is utilized to do this task and place references to the files inside the \filename{index.html} during build. AngularJs is specifically called out because of how much it does +inside the client and developers should be familiar with it. \portal utlizes version \AngularJsVersion of AngularJs as specified in the \filename{package.json} file. + +\section{Spring} +Spring Framework is the workhorse library of the server\cite{spring}. It drives every aspect of the server, from client authentication, page permissions and database transactions. +It is responsible for converting json strings passed from the client to java objects in which can be utilized in the spring services. Spring operates on the Model View Controller aspect +(MVC) in which the client requests information and spring returns the model to be displayed on the client page. + +The typical workflow for spring can be shown in Figure~\ref{fig:spring-flow} + +\begin{figure}[htbp] + \centering + \scalebox{0.6}{\includegraphics*{figures/spring-flow.png}} + \caption{Spring Workflow} + \label{fig:spring-flow} +\end{figure} + +Spring security is utilized to make sure users do not access portions of the website they are not permitted to view. This is done through permission rules setup in the code and through +an authorization token which is assigned to the user during login. The authentication token tells the security provider which part of the website the user has access to. +The authentication token is populated with information that is retrieved from the permissions tables based on the users group permissions. + +Spring websocket is utilized for the server to pass information back to the client asynchronously. Every time a user logins in they are automatically subscribed to the system websocket +channel and their own private channel so the user can receive personal websocket messages and system wide websocket messages. This allows for the client to update the users screen when +other users change something on the website. Without websockets the user would not know what has changed by other users unless they refresh their browser. During login, the website +checks to see if websockets is allowed (some web site providers might block websockets). If the check passes normal processes can continue. If it fails, the site will fall back into a +degredated state in which the user will not be able to get all the changes as specified above. + +Spring CRUD Repository is utilized for querying the database. Developers do not need to worry about creating SQL statements as the repository will take care of that for them. +It allows for create, read, update, and delete operations on the table the repository references. This is done through JPQL (java persistence query language) and Spring annotations. + +Developers should consult the Spring JPA guide if more spring query methods are needed during development. The guide can be found at +\url{https://docs.spring.io/spring-data/jpa/docs/current/reference/html/} + +\portal utilizes Spring Framework version \SpringVersion. The developer does not need to worry about downloading this library is it will automatically be downloaded through maven during +build time. + +\section{Hibernate} +Hibernate ORM\cite{hibernate} enables developers to more easily write applications whose data outlives the application process. As an Object/Relational Mapping (ORM) framework, +Hibernate is concerned with data persistence as it applies to relational databases (via JDBC). Hibernate maps database tables to Java objects called Java Persistence Objects (JPO). +This allows developers to create normal objects and act on them like a regular object but the changes will then persist to the database once the object has been saved. \portal utilizes +Hibernate through the use of Spring which uses Hibernate in the background for persistence. The JPO objects are also referenced in the Spring CRUD Repositories. These objects are the +objects the developer will use when doing CRUD operations (Create/Read/Update/Delete). + +\portal utilizes Hibernate ORM version \HibernateVersion. The developer does not need to worry about downloading this library is it will automatically be downloaded through maven +during build time. + +\section{Quartz Scheduler} +Quartz Scheduler\cite{quartz} is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from the smallest stand-alone +application to the largest e-commerce system. Quartz can be used to create simple or complex schedules for executing tens, hundreds, or even tens-of-thousands of jobs; jobs whose tasks +are defined as standard Java components that may execute virtually anything you may program them to do. The Quartz Scheduler includes many enterprise-class features, such as support for +JTA transactions and clustering. + +Quartz is freely usable, licensed under the Apache 2.0 license. + +Sample uses of job scheduling with Quartz: +\begin{itemize} + \item \bold{Driving Process Workflow:} As a new order is initially placed, schedule a Job to fire in exactly 2 hours, that will check the status of that order, and trigger a + warning notification if an order confirmation message has not yet been received for the order, as well as changing the order's status to ``awaiting intervention''. + \item \bold{System Maintenance:} Schedule a job to dump the contents of a database into an XML file every business day (all weekdays except holidays) at 11:30 PM. + \item Providing reminder services within an application. +\end{itemize} + +\portal utilizes Quartz Scheduler version \QuartzVerson to schedule all the tasks to be completed. The developer does not need to worry about downloading this library is it will automatically be downloaded through maven +during build time. + +\section{JSch} +JSch\cite{jsch} is a pure Java implementation of SSH2. + +JSch allows you to connect to an sshd server and use port forwarding, X11 forwarding, file transfer, etc., and you can integrate its functionality into your own Java programs. +JSch is licensed under BSD style license. + +\portal utilizes JSch version \JSchVersion. The developer does not need to worry about downloading this library is it will automatically be downloaded through maven during build time. + +\section{JGit} +JGit\cite{jgit} i is a pure Java implementation of the Git version control system. Git is a distributed SCM, which means every developer has a full copy of all history of every revision +of the code, making queries against the history very fast and versatile. + +\portal utilizes JGit version \JGitVersion. The developer does not need to worry about downloading this library is it will automatically be downloaded through maven during build time. + +\section{Node.js} + +Node.js\cite{nodejs} is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. + +Node.js is used to assemble and verify the client javascript and html. Node.js stores the 3rd party vendor libraries in the \filename{portal\_client} project folder under +\filename{node\_modules} folders. Typically when using Node.js, there is a Node.js server running that serves up these 3rd party libraries to the client, but \portal does not use a +node.js server to do this. Instead during compile time, the process looks at the \filename{assets.js} file to see which 3rd party libraries are needed and appends these files to the +\filename{index.html} file for refence by the client. + +Once the \filename{index.html} file has been written it is copied to the rest of the client code, then utilizing \command{jshint}, the javascript is checked for any errors. It uses +'strict' checking to make sure there is proper syntax in the javascript such as semi-colons in the right place and proper closing tags. If the javascript check fails the whole build will +fail and a message will tell the developer where the javascript error is. + +Node.js is also used for live reloading of the client code. Utilizing \command{grunt}, the developer can make local changes to the client code and see the changes live on the website. +This is done through a proxy service that points to the local content instead of the deployed content (as long as the deployed content is on the same machine as the developer is on). +This allows the developer to make changes and not have to rebuild the whole application to see the client side changes (See Section~\ref{grunt} for more details). + +\portal utilizes version \NodeVersion\ of Node.js. It is possible to use older versions of Node.js but it not recommended for security concerns. + +\section{Yarn} +\label{yarn2} + +Yarn\cite{yarn} is a package manager program that allows for easy download of 3rd party javascript/typescript libraries. It caches every package so it never needs to download it again. +Yarn is used as a replacement for node.js package manager (npm) and utilizes the same \filename{package.json} file as npm along with installing the packages in the same directory that +npm (\filename{portal\_client/node\_modules}). + +This allows for developers to switch easily from npm to yarn. During build time, maven will automatically download yarn locally to the project so it can use the local process to download +the 3rd party libraries as specified in the \filename{package.json} file. + +Yarn needs to be installed on the developers home path to be utilizes by the developer to install new packages. To install new packages issue the command + \command{yarn add --save}. This will download the request package and save the package to the \filename{package.json} file for future reference. + +See~\ref{yarn} for more details. + +\portal utilizes version \YarnVersion of yarn. No older version can be used at this time. + +\section{Flyway} +Flyway\cite{flyway} is an open source database migration tool. It strongly favors simplicity and convention over configuration. It is based around 7 basic commands: Migrate, Clean, +Info, Validate, Undo, Baseline and Repair. Migrations can be written in SQL (database-specific syntax (such as MySQL, MSSQL, Oracle) is supported) or Java (for advanced data transformations +or dealing with LOBs). It has a Command-line client, a Java API (also works on Android) for migrating the database on application startup and a Maven plugin. + +\portal utilizes the sql syntax migration along with java implementaiton and maven plugin. It is not necessary to download flyway as maven will download the required lbraries automatically +during build time. + +Flyway allows for easy database migrations between versions from older versions of the software to new version (but not from newer to older). This allows for every deployment of the web +application to perform the migraiton if needed to keep the database up to date. Flyway looks into the specified migration folder to execute migration scripts. These scripts can range from +table creation, to adding data and modifying existing tables/data. \portal utilizes Flyway at two different times. The first time is during build flyway is executed to do the required +migrations. The second time is during applcation startup to perform any migrations that need to happen on a production enviornment. + +Flyway creates a table within the application table schema to keep track of which migrations have been applied. It also creates a checksum for each file. During migration, +flyway will check the checksum of the file against a database entry. If no entry exists the migration is performed and an entry is entered into the flyway table. If the checksum +matches, that migration is skipped as its already been applied. If the checksum does not match the entry, an error will be thrown and the build/startup will fail. This is to ensure that +no previous migration files have been modified. All new modifications to existing migrations should be performed in another migration script. + +The flyway maven plugin allows developers to perform clean, migrate, repair, info, and validate commands. Below are the maven commands that can be executed for various flyway processes + +\begin{itemize} + \item \command{mvn flyway:clean}\newline + Clean drops all objects in the configured schemas. Clean is a great help in development and test. It will effectively give you a fresh start, by wiping your configured schemas + completely clean. All objects (tables, views, procedures, \ldots) will be dropped. Needless to say: \bold{do not use against your production DB!} + \item \command{mvn flyway:migrate}\newline + Migrate migrates the schema to the latest version. Flyway will create the schema history table automatically if it doesn�t exist. Migrate is the centerpiece of the Flyway workflow. + It will scan the filesystem or your classpath for available migrations. It will compare them to the migrations that have been applied to the database. If any difference is found, it + will migrate the database to close the gap. Migrate should preferably be executed on application startup to avoid any incompatibilities between the database and the expectations + of the code. + \item \command{mvn flyway:repair}\newline + Repair repairs the schema history table. Repair is your tool to fix issues with the schema history table. It has two main uses: + \begin{itemize} + \item Remove failed migration entries (only for databases that do NOT support DDL transactions) + \item Realign the checksums, descriptions and types of the applied migrations with the ones of the available migrations + \end{itemize} + \item \command{mvn flyway:info}\newline + Info prints the details and status information about all the migrations. Info lets developers know where they stand. At a glance they will see which migrations have already been + applied, which other ones are still pending, when they were executed and whether they were successful or not. + \item \command{mvn flyway:validate}\newline + Validate validates the applied migrations against the available ones. Validate helps developers verify that the migrations applied to the database match the ones available locally. + This is very useful to detect accidental changes that may prevent you from reliably recreating the schema. +\end{itemize} + +\portal utilizes version \FlywayVersion of flyway. The developer does not need to worry about downloading this library is it will automatically be downloaded through maven during build time. + +\section{Tomcat} +\label{tomcat2} +Apache Tomcat\cite{tomcat}, often referred to as Tomcat Server, is an open-source Java Servlet Container developed by the Apache Software Foundation (ASF). Tomcat implements several +Java EE specifications including Java Servlet, JavaServer Pages (JSP), Java EL, and WebSocket, and provides a "pure Java" HTTP web server environment in which Java code can run. + +Tomcat is developed and maintained by an open community of developers under the auspices of the Apache Software Foundation, released under the Apache License 2.0 license, and is +open-source software. + +While \portal was developed to run on tomcat, it is possible to be ran on other servers that support \filename{.war} files such as JBOSS, TomEE, and Wildfly. + +Once tomcat is installed on the host server, the only configuration that needs to change is adding a user to the manager-script role. This user should be specified in +\filename{conf/tomcat-users.xml} file under the tomcat home directory. This user and the corresponding password should be the same one as reflected in the \filename{settings.xml} +file (see Reference~\ref{sample-settings.xml} and Section~\ref{settings.xml}). This will allow the application to redeploy to tomcat without having to shutdown deploy and startup tomcat +after a successful build. + +The developer just has to issues the command \command{mvn tomcat7:redeploy} to deploy the application to tomcat. + +\portal utilizes version \TomcatVersion of Tomcat. Older or newer versions of tomcat can be used but it is not recommend to go any lower than tomcat 7 + + diff --git a/docs/src/UserGuide.tex b/docs/src/UserGuide.tex new file mode 100644 index 0000000..aa34b24 --- /dev/null +++ b/docs/src/UserGuide.tex @@ -0,0 +1,26 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : User.tex +% SUBJECT : Document describing design issues in Patch Repoistory. +% AUTHOR : (C) Copyright 2018 Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{User Guide} +\label{userGuide} + +This documetions how the user uses various aspects of the \portal. + +\input{guides/Login} +\input{guides/ScriptTab} +\input{guides/GitTab} +\input{guides/CredTab} +\input{guides/EmailTab} +\input{guides/UserTab} +\input{guides/PermissionTab} +\input{guides/EnvVarTab} +\input{guides/SystemLogTab} +\input{guides/Configuration} +\input{guides/LogLevel} +\input{guides/AESSeed} +\input{guides/SMTP} + diff --git a/docs/src/api/AccountAPI.tex b/docs/src/api/AccountAPI.tex new file mode 100644 index 0000000..13d3324 --- /dev/null +++ b/docs/src/api/AccountAPI.tex @@ -0,0 +1,99 @@ +\section{Account API} +This section documents api calls that are associated with user accounts. + +\subsection{Is Logged In} +The following will check if the user is logged into the system. It would be for the local user, not a specified user. Each time a user logs in, the user is assiged a token. This method call checks to see if that token is present for that user. +\begin{apimethod} + {User Is Logged In} + {account/isLoggedIn.do} + {POST} + {No} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/isLoggedInResp}} + +\subsection{Login} +The following method allows a user to log into the application +\begin{apimethod} + {Login} + {account/login.do} + {POST} + {No} + \begin{itemize} + \item \Required{username}{Email of the user to login} + \item \Required{password}{password of the user} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/loginReq}}{\input{api/sample/response/loginResp}} + +\subsection{Logout} +The following method logs the current user out +\begin{apimethod} + {Logout} + {account/logout.do} + {POST} + {No} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/None}} + +\subsection{Add User} +The following method adds a user to the server +\begin{apimethod} + {Add User} + {account/addUser.do} + {POST} + {Yes} + \begin{itemize} + \item \Required{email}{Email associated with the user for login} + \item \Required{password}{Default password for the user to login with} + \item \Required{role}{Role the user belongs to} + \item \Optional{firstName}{First name of the user to add} + \item \Optional{lastName}{Last name of the user to add} + \item \Optional{effectiveDate}{Date in which the user goes into effect (can acess site)} + \item \Optional{expirationDate}{Date in which the user loses access to the site} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/addUserReq}}{\input{api/sample/response/addUserResp}} + +\subsection{Delete User} +The following method removes a user from the server +\begin{apimethod} + {Delete User} + {account/deleteUser.do} + {POST} + {Yes} + \begin{itemize} + \item \Required{email}{Email associated with the user to delete} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/deleteUserReq}}{\input{api/sample/SuccessResp}} + +\subsection{Get All Users} +The following method gets a list of all users (active and inactive) for the application +\begin{apimethod} + {Get All Users} + {account/getAll.do} + {GET} + {Yes} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getAllUsersResp}} + + + + + + diff --git a/docs/src/api/AppLogAPI.tex b/docs/src/api/AppLogAPI.tex new file mode 100644 index 0000000..27d19ef --- /dev/null +++ b/docs/src/api/AppLogAPI.tex @@ -0,0 +1,31 @@ +\section{Application Log API} +This section documents api calls that are associated with the application logs + +\subsection{Get All Logs} +The following method gets a list of all the application logs +\begin{apimethod} + {Get All Logs} + {logs/getAll.do} + {GET} + {Yes} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getAllLogsResp}} + +\subsection{Purge Logs} +The following method purges logs between the start date and end date +\begin{apimethod} + {Purge Logs} + {logs/purge.do} + {POST} + {Yes} + \begin{itemize} + \item \Required{purgeStartDate}{Starting date to purge logs from} + \item \Required{purgeEndDate}{Ending date to purge logs from} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/purgeLogReq}}{\input{api/sample/response/purgeLogResp}} \ No newline at end of file diff --git a/docs/src/api/ConfigurationAPI.tex b/docs/src/api/ConfigurationAPI.tex new file mode 100644 index 0000000..6e181b8 --- /dev/null +++ b/docs/src/api/ConfigurationAPI.tex @@ -0,0 +1,55 @@ +\section{Configuration API} + +This section documents api calls that are associated with configuration settings. + +\subsection{Get Configuration Value} +The following will get a specified configuration value if that value is present in the server configuration +\begin{apimethod} + {Get Configuration Value} + {config/getConfigurationValue.do} + {POST} + {Yes} + \begin{itemize} + \item \Required{key}{Name of the configuration Value to retrieve} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/getConfValReq}}{\input{api/sample/response/getConfValResp}} + +\subsection{Get Configuration} +The following will get the current configuration values associated with the server as specified in the file \filename{\$TOMCAT\_HOME/conf/portal.properties} +\begin{apimethod} + {Get Configuration} + {config/getConfiguration.do} + {GET} + {Yes} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getConfResp}} + +\subsection{Save Configuration} +The following will save the server configuration values to \filename{\$TOMCAT\_HOME/conf/portal.properties}. All the parameters are option as they all do not need to be populated, just the ones that need to be changed +\begin{apimethod} + {Save Configuration} + {config/saveConfiguration.do} + {POST} + {Yes} + \begin{itemize} + \item \Optional{filepath}{File path to where the files are stored on the server} + \item \Optional{dbUsername}{Username granted access to the portal tables} + \item \Optional{dbPassword}{Password for the portal database user} + \item \Optional{dbRootUser}{Root username for the database.} + \item \Optional{dbRootPassword}{Password for the root user for the database} + \item \Optional{dbHost}{URL or IP address for the database server} + \item \Optional{dbPort}{Database port to connect on} + \item \Optional{userExpirationDays}{Default number of days until a new user account expires} + \item \Optional{logLevel}{Default log level that new loggers initialize with} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/response/getConfResp}}{\input{api/sample/SuccessResp}} + + diff --git a/docs/src/api/CredentialsAPI.tex b/docs/src/api/CredentialsAPI.tex new file mode 100644 index 0000000..31b0f2d --- /dev/null +++ b/docs/src/api/CredentialsAPI.tex @@ -0,0 +1,89 @@ +\section{Credentials API} +This section documents api calls that are associated with script and git credentials. + +\subsection{Get All Credentials} +The following will get all the available credentials + +\begin{apimethod} + {Get All Credentials} + {creds/do.do} + {GET} + {YES} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getAllCredsResp}} + +\subsection{Get Credential Types} +The following will get the available credential types allowed. + +\begin{apimethod} + {Get Credential Types} + {creds/getCredentialTypes.do} + {GET} + {YES} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getCredTypesResp}} + +\subsection{Add/Edit Credentials (Username/Password)} +The following will add/edit username and password credentials to the system + +\begin{apimethod} + {Add/Edit Credentials (Username/Password)} + {creds/add.do} + {POST} + {YES} + \begin{itemize} + \item \Required{name}{Unique name for the credentials} + \item \Required{credentialType}{``PASSWD''} + \item \Required{username}{Username to add} + \item \Required{password}{Password of the user} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/addCredUnameReq}}{\input{api/sample/response/addCredUnameResp}} + +\subsection{Add/Edit Credentials (SSH Key)} +The following will add/edit a ssh key to the system + +\begin{apimethod} + {Add/Edit Credentials (SSH Key)} + {creds/add.do} + {POST} + {YES} + \begin{itemize} + \item \Required{name}{Unique name for the credentials} + \item \Required{credentialType}{``SSH''} + \item \Required{username}{Identity of the user who the key belongs to} + \item \Required{key}{SSH private key} + \item \Optional{passphrase}{Passphrase for the ssh key} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/addCredSshKeyReq}}{\input{api/sample/response/addCredSshKeyResp}} + +\subsection{Delete Credentials} +The following will delete credentials from the system + +\begin{apimethod} + {Delete Credentials} + {creds/delete.do} + {POST} + {YES} + \begin{itemize} + \item \Required{uid}{Unique identifier of the credentials} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/deleteCredsReq}}{\input{api/sample/response/deleteCredsResp}} + + + + + diff --git a/docs/src/api/FileAPI.tex b/docs/src/api/FileAPI.tex new file mode 100644 index 0000000..857bfa9 --- /dev/null +++ b/docs/src/api/FileAPI.tex @@ -0,0 +1,31 @@ +\section{File API} +This section documents api calls associated with the patch files + +\subsection{Download File} +The following will allow to download a file +\begin{apimethod} + {Download File} + {file/download.do} + {GET} + {No} + \begin{itemize} + \item \Required{id}{Id of the file to download} + \end{itemize} +\end{apimethod} + +Sample GET request\newline +\command{https://portal.viprcenter.com/portal/file/download.do?id=2} + +\subsection{Delete File} +The following will delete a file from the server +\begin{apimethod} + {Delete File} + {file/delete.do} + {POST} + {Yes} + \begin{itemize} + \item \Required{id}{The ID of the file to delete} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/deleteFileReq}}{\input{api/sample/SuccessResp}} diff --git a/docs/src/api/GitAPI.tex b/docs/src/api/GitAPI.tex new file mode 100644 index 0000000..2abe4f2 --- /dev/null +++ b/docs/src/api/GitAPI.tex @@ -0,0 +1,73 @@ +\section{Git API} +This section documents api calls that are associated with git repositories. + +\subsection{Get All Repositories} +The following will get all the available git repositories + +\begin{apimethod} + {Get All Repositories} + {git/getAll.do} + {GET} + {YES} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getAllReposResp}} + +\subsection{Add/Edit Git Repository} +The following will add/edit a git repository + +\begin{apimethod} + {Add/Edit Git Repository} + {git/add.do} + {POST} + {YES} + \begin{itemize} + \item \Required{name}{Unique name of the repository} + \item \Required{scm}{The git repository url to clone} + \item \Required{branch}{The branch to checkout} + \item \Required{directory}{Local directory to clone into (doesn't need to exist)} + \item \Required{credentials}{The credentials to use. Requires the following:}\newline + \begin{itemize} + \item \Required{uid}{Unique identifer of the credentials to use.} + \end{itemize} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/addGitRepoReq}}{\input{api/sample/response/addGitRepoResp}} + +\subsection{Pull Repository} +The following will issue a pull command to the desired repository + +\begin{apimethod} + {Pull Repository} + {git/pull.do} + {POST} + {YES} + \begin{itemize} + \item \Required{id}{Id of the repository to pull} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/deleteGitRepoReq}}{\input{api/sample/response/addGitRepoResp}} + +\subsection{Delete Git Repository} +The following will delete a git repository + +\begin{apimethod} + {Delete Git Repository} + {git/delete.do} + {POST} + {YES} + \begin{itemize} + \item \Required{id}{Id of the repository to delete} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/deleteGitRepoReq}}{\input{api/sample/response/addGitRepoResp}} + + + + diff --git a/docs/src/api/LoggerAPI.tex b/docs/src/api/LoggerAPI.tex new file mode 100644 index 0000000..69cdb78 --- /dev/null +++ b/docs/src/api/LoggerAPI.tex @@ -0,0 +1,35 @@ +\section{Logger API} +This section documents api calls associated with the application loggers + +\subsection{Get Loggers} +The following will get a list of the current loggers and their log levels + +\begin{apimethod} + {Get Loggers} + {logger/getLoggers.do} + {GET} + {Yes} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getLoggersResp}} + +\subsection{Set Loggers} +The following will set the current loggers to the selected log level. + +NOTE: the request body parameters are in a list + +\begin{apimethod} + {Set Loggers} + {logger/setLoggers.do} + {POST} + {Yes} + \begin{itemize} + \item \Required{logger}{The Logger to set the log level to} + \item \Required{level}{The level to set the logging to} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/response/getLoggersResp}}{\input{api/sample/SuccessResp}} \ No newline at end of file diff --git a/docs/src/api/PermissionsAPI.tex b/docs/src/api/PermissionsAPI.tex new file mode 100644 index 0000000..4bce0e2 --- /dev/null +++ b/docs/src/api/PermissionsAPI.tex @@ -0,0 +1,121 @@ +\section{Permission API} + +This section documents api calls that are associated with defining permissions + +\subsection{Add Permission Category} + +The following section will describe adding a permission category + +\begin{apimethod} + {Add Permission Category} + {permissions/updatePermissionCategory.do} + {POST} + {Yes} + \begin{itemize} + \item \Required{name}{Unique name of the permission group } + \item \Required{displayName}{Name to be displayed in the permissions list} + \item \Required{enabled}{Whether or not the permission group is enabled} + \item \Required{permissions}{permissions to add. The allowed permissions area aleady predefined by the server. Below are the required parameters that have to be set inside the permissions list.\newline \begin{itemize} + \item \Required{permissionId}{Id of the permission to add} + \item \Required{allowed}{whether or not the permission is enabled or not} + \end{itemize}} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/addPermGroupReq}}{\input{api/sample/response/addPermGroupResp}} + +\subsection{Update Permission Category} + +The following section will describe updating a permission category + +\begin{apimethod} + {Update Permission Category} + {permissions/updatePermissionCategory.do} + {POST} + {Yes} + \begin{itemize} + \item \Required{name}{Unique name of the permission group } + \item \Required{enabled}{Whether or not the permission group is enabled} + \item \Required{permissions}{permissions to add. The allowed permissions area aleady predefined by the server. + Below are the required parameters that have to be set inside the permissions list.\newline + \begin{itemize} + \item \Required{permissionId}{Id of the permission to add} + \item \Required{allowed}{whether or not the permission is enabled or not} + \end{itemize}} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/updatePermGroupReq}}{\input{api/sample/response/addPermGroupResp}} + +\subsection{Get Permission Groups} + +The following section will get permission groups with their associative permissions + +\begin{apimethod} + {Get Permission Groups} + {permissions/getPermissionGroups.do} + {GET} + {Yes} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getPermGroupResp}} + +\subsection{Get All Permission Groups} + +The following section will get permission groups only. Will not show the actual permissions + +\begin{apimethod} + {Get All Permission Groups} + {permissions/getPermissionGroups.do} + {GET} + {Yes} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getAllPermsResp}} + +\subsection{Get Permission Categories} + +The following section will get the different permission categories and the permissions associated with the category + +\begin{apimethod} + {Get All Permission Groups} + {permissions/getPermissionGroups.do} + {GET} + {Yes} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getPermCatResp}} + +\subsection{Delete Permission Group} + +The following section will delete a selected permission group. The return value is the deleted group + +Note: The permission group has to be empty. No user can be long to the group to be deleted otherwise an error will be returned + +\begin{apimethod} + {Get All Permission Groups} + {permissions/deletePermissionGroup.do} + {GET} + {Yes} + \begin{itemize} + \item \Required{name}{Permission group name to be deleted} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/deletePermGroupReq}}{\input{api/sample/response/deletePermGroupResp}} + + + + + + + diff --git a/docs/src/api/ScriptAPI.tex b/docs/src/api/ScriptAPI.tex new file mode 100644 index 0000000..18244f7 --- /dev/null +++ b/docs/src/api/ScriptAPI.tex @@ -0,0 +1,290 @@ +\section{Scripts API} +This section documents api calls that are associated with creating and maintaining scripts. + +\subsection{Get All Scripts} +The following will get all the available scripts + +\begin{apimethod} + {Get All Scripts} + {scripts/getAll.do} + {GET} + {YES} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getAllScriptsResp}} + +\subsection{Get Schedule Frequency} +The following will get a list of the available schedule frequencies + +\begin{apimethod} + {Get Get Schedule Frequency} + {scripts/getScheduleFrequencies.do} + {GET} + {YES} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getScheduleFreqResp}} + +\subsection{Get Script Type} +The following will get a list of the types of scripts that can be created + +\begin{apimethod} + {Get Script Type} + {scripts/getScriptType.do} + {GET} + {YES} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getScriptTypesResp}} + +\subsection{Add Script Local} +\label{add-script-Local} +The following will a a local script to the system + +\begin{apimethod} + {Add Script Local} + {scripts/addScript.do} + {POST} + {YES} + \begin{itemize} + \item \Required{name}{Unique name of the script} + \item \Required{scriptType}{``LOCAL''} + \item \Required{command}{The command to execute} + \item \Optional{baseDirectory}{Local directory to start the command in} + \item \Optional{outputDirectory}{Directory to place any output into} + \item \Optional{parameters}{Paremetrs to be used with the command}\newline + \begin{itemize} + \item \Required{parameter}{The parameter to be used} + \end{itemize} + \item \Optional{schedule}{Time and frequency the script should execute}\newline + \begin{itemize} + \item \Required{frequency}{How often the script should trigger} + \item \Required{startDatetime}{When the schedule should start to trigger} + \item \Optional{endDatetime}{When the schedule should end} + \item \Optional{maxExecution}{Number of executions before schedule stops} + \end{itemize} + \Optional{scriptVariables}{Environment variables specific to the script}\newline + \begin{itemize} + \item \Required{name}{Name of the variable} + \item \Required{value}{Value of the variable} + \end{itemize} + \Optional{scriptChain}{Follow-on scripts to execute after parent script finishes}\newline + \begin{itemize} + \item \Required{childScript}{Id of the child script to execute} + \item \Required{exitStatus}{Exit status of the parent to trigger child script} + \end{itemize} + \Optional{scriptEmails}{Email to send once script finishes}\newline + \begin{itemize} + \item \Required{emailName}{Name of the email template to send} + \item \Required{recipients}{List of recipients to send the email to. Requires email address} + \end{itemize} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/addScriptLocalReq}}{\input{api/sample/response/addScriptLocalResp}} + +\subsection{Update Script Local} + +The following will update a local script on the system + +\begin{apimethod} + {Update Script Local} + {scripts/updateScript.do} + {POST} + {YES} + See section~\ref{add-script-Local} for acceptable parameters and response +\end{apimethod} + +\subsection{Add Script Remote} +\label{add-script-remote} +The following will a remote script to the system + +\begin{apimethod} + {Add Script Remote} + {scripts/addScript.do} + {POST} + {YES} + \begin{itemize} + \item \Required{name}{Unique name of the script} + \item \Required{scriptType}{``REMOTE''} + \item \Required{remote}{The remote connection information}\newline + \begin{itemize} + \item \Required{scriptCredentials}{The uid of the credentials to use} + \item \Required{url}{the IP address or url to connect to} + \item \Required{port}{The port to connect to} + \end{itemize} + \item \Optional{baseDirectory}{Remote directory to start the command in} + \item \Optional{outputDirectory}{Directory to place any output into} + \item \Optional{parameters}{Paremetrs to be used with the command}\newline + \begin{itemize} + \item \Required{parameter}{The parameter to be used} + \end{itemize} + \item \Optional{schedule}{Time and frequency the script should execute}\newline + \begin{itemize} + \item \Required{frequency}{How often the script should trigger} + \item \Required{startDatetime}{When the schedule should start to trigger} + \item \Optional{endDatetime}{When the schedule should end} + \item \Optional{maxExecution}{Number of executions before schedule stops} + \end{itemize} + \Optional{scriptVariables}{Environment variables specific to the script}\newline + \begin{itemize} + \item \Required{name}{Name of the variable} + \item \Required{value}{Value of the variable} + \end{itemize} + \Optional{scriptChain}{Follow-on scripts to execute after parent script finishes}\newline + \begin{itemize} + \item \Required{childScript}{Id of the child script to execute} + \item \Required{exitStatus}{Exit status of the parent to trigger child script} + \end{itemize} + \Optional{scriptEmails}{Email to send once script finishes}\newline + \begin{itemize} + \item \Required{emailName}{Name of the email template to send} + \item \Required{recipients}{List of recipients to send the email to. Requires email address} + \end{itemize} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/addScriptRemoteReq}}{\input{api/sample/response/addScriptRemoteResp}} + +\subsection{Update Script Remote} + +The following will update a remote script on the system + +\begin{apimethod} + {Update Script Remote} + {scripts/updateScript.do} + {POST} + {YES} + See section~\ref{add-script-remote} for acceptable parameters and response +\end{apimethod} + +\subsection{Add Script Scp} +\label{add-script-scp} +The following will a scp script to the system + +\begin{apimethod} + {Add Script Scp} + {scripts/addScript.do} + {POST} + {YES} + \begin{itemize} + \item \Required{name}{Unique name of the script} + \item \Required{scriptType}{``SCP''} + \item \Required{scp}{The remote connection information}\newline + \begin{itemize} + \item \Required{fromCredentials}{The uid of the credentials for the machine retriving file from} + \item \Required{fromHost}{the IP address or url to connect to or ``LOCAL'' if the file is from the server} + \item \Required{fromPort}{The port to connect to or 0 if the file is ``LOCAL''} + \item \Required{toCredentials}{The uid of the credentials for the machine the files are being sent to} + \item \Required{toHost}{the IP address or url to connect to or ``LOCAL'' to store files on the server} + \item \Required{toPort}{The port to connect to or 0 if the file is being saved ``LOCAL''} + \end{itemize} + \item \Optional{baseDirectory}{Remote to look for the file} + \item \Optional{outputDirectory}{Directory to place any files into} + \item \Optional{schedule}{Time and frequency the script should execute}\newline + \begin{itemize} + \item \Required{frequency}{How often the script should trigger} + \item \Required{startDatetime}{When the schedule should start to trigger} + \item \Optional{endDatetime}{When the schedule should end} + \item \Optional{maxExecution}{Number of executions before schedule stops} + \end{itemize} + \Optional{scriptChain}{Follow-on scripts to execute after parent script finishes}\newline + \begin{itemize} + \item \Required{childScript}{Id of the child script to execute} + \item \Required{exitStatus}{Exit status of the parent to trigger child script} + \end{itemize} + \Optional{scriptEmails}{Email to send once script finishes}\newline + \begin{itemize} + \item \Required{emailName}{Name of the email template to send} + \item \Required{recipients}{List of recipients to send the email to. Requires email address} + \end{itemize} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/addScriptScpReq}}{\input{api/sample/response/addScriptScpResp}} + +\subsection{Update Script Remote} + +The following will update a scp script on the system + +\begin{apimethod} + {Update Script Scp} + {scripts/updateScript.do} + {POST} + {YES} + See section~\ref{add-script-scp} for acceptable parameters and response +\end{apimethod} + +\subsection{Manual Run Script} +The following will manually run a script + +\begin{apimethod} + {Manually Run Script} + {scripts/runScript.do} + {POST} + {YES} + \begin{itemize} + \item \Required{id}{Id of the script to run} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/runScriptReq}}{\input{api/sample/response/addScriptLocalResp}} + +\subsection{Delete Script} +The following will delete a script + +\begin{apimethod} + {Delete Script} + {scripts/deleteScript.do} + {POST} + {YES} + \begin{itemize} + \item \Required{id}{Id of the script to delete} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/runScriptReq}}{\input{api/sample/response/addScriptLocalResp}} + +\subsection{Delete Script Schedule} +The following will delete a script schedule from a script + +\begin{apimethod} + {Delete Script Schedule} + {scripts/deleteScriptSchedule.do} + {POST} + {YES} + \begin{itemize} + \item \Required{jobId}{Unique job identifier of the schedule to delete} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/deleteScriptScheduleReq}}{\input{api/sample/response/deleteScriptScheduleResp}} + +\subsection{Get Script Schedule Status Results} +The following will get the results of the the schedule + +\begin{apimethod} + {Get Script Schedule Status Results} + {scripts/getScriptScheduleStatus.do} + {GET} + {YES} + \begin{itemize} + \item uuid + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/getScriptStatusReq}}{\input{api/sample/response/getScriptScheduleStatusResp}} + + + + + diff --git a/docs/src/api/SystemAPI.tex b/docs/src/api/SystemAPI.tex new file mode 100644 index 0000000..42aa965 --- /dev/null +++ b/docs/src/api/SystemAPI.tex @@ -0,0 +1,200 @@ +\section{System API} +This section documents api calls that are associated with the system environment. + +\subsection{Get SMTP Configuration} +The following will get the current smtp configuration + +\begin{apimethod} + {Get SMTP Configuration} + {system/getSmtpConfig.do} + {GET} + {YES} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getSmtpConfigResp}} + +\subsection{Update SMTP Configuration} +The following will set the smtp configuration. The return value will be the same values that were set. + +Below is the list of the smtp names that can be used: +\begin{itemize} +\label{list:smtp-keys} + \item smtp.enabled + \item mail.transport.protocol + \item mail.smtp.port + \item mail.smtp.host + \item mail.smtp.auth + \item mail.smtp.starttls.enable + \item mail.smtp.starttls.required + \item mail.smtp.ssl.trust + \item smtp.from.user + \item smtp.username + \item smtp.password + \item mail.debug +\end{itemize} + +\begin{apimethod} + {Update SMTP Configuration} + {system/updateSmtpConfig.do} + {POST} + {YES} + \begin{itemize} + \item \Required{name}{Name of the smtp configuration key} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/response/getSmtpConfigResp}}{\input{api/sample/response/getSmtpConfigResp}} + +\subsection{Get All Environment Variables} + +The following will get all global environment variables on the system. + +\begin{apimethod} + {Get All Environment Variables} + {system/getAll.do} + {GET} + {YES} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/deleteEnvVarsResp}} + +\subsection{Add Environment Variables} + +The following will add global environment variables to the system. + +Both the request and response values are in json arrays. + +\begin{apimethod} + {Add Environment Variables} + {system/addEnvVars.do} + {POST} + {YES} + \begin{itemize} + \item \Required{name}{Unique name of the environment variable} + \item \Required{value}{Value of the environment variable} + \item \Required{isNew}{\command{true} if its a new environment variable or + \command{false} if updating an existing variable} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/addEnvVarsReq}}{\input{api/sample/response/addEnvVarsResp}} + +\subsection{Delete Environment Variables} + +The following will delete global environment variables from the system. + +Both the request and response values are in json arrays. + +\begin{apimethod} + {Delete Environment Variables} + {system/deleteEnvVars.do} + {POST} + {YES} + \begin{itemize} + \item \Required{id}{id of the environment variable to remove} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/deleteEnvVarsReq}}{\input{api/sample/response/deleteEnvVarsResp}} + +\subsection{Get Email Templates} + +The following will get all the email templates on the system. + +\begin{apimethod} + {Get Email Templates} + {system/getEmailTemplates.do} + {GET} + {YES} + \begin{itemize} + \item None + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/None}}{\input{api/sample/response/getEmailTemplateResp}} + +\subsection{Add Email Template} + +The following will add an email template to the system. + +\begin{apimethod} + {Add Email Template} + {system/addEmailTemplate.do} + {POST} + {YES} + \begin{itemize} + \item \Required{emailName}{Unique name of the email template} + \item \Required{emailSubject}{Subject of the email.} + \item \Required{emailMessage}{Message to be sent to the recipients} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/addEmailTemplateReq}}{\input{api/sample/response/addEmailTemplateResp}} + +\subsection{Delete Email Template} + +The following will delete an email template from the system. + +\begin{apimethod} + {Delete Email Template} + {system/deleteEmailTemplate.do} + {POST} + {YES} + \begin{itemize} + \item \Required{id}{Id of the template to delete} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/deleteEmailTemplateReq}}{\input{api/sample/response/addEmailTemplateResp}} + +\subsection{Update Seed} +The following will update the aes seed + +\begin{apimethod} + {Update Seed} + {system/updateSeed.do} + {POST} + {YES} + \begin{itemize} + \item \Required{seed}{New seed value} + \end{itemize} +\end{apimethod} + +\ApiData{\input{api/sample/request/updateSeedReq}}{\input{api/sample/SuccessResp}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/api/sample/None.tex b/docs/src/api/sample/None.tex new file mode 100644 index 0000000..4af1832 --- /dev/null +++ b/docs/src/api/sample/None.tex @@ -0,0 +1 @@ +None \ No newline at end of file diff --git a/docs/src/api/sample/SuccessResp.tex b/docs/src/api/sample/SuccessResp.tex new file mode 100644 index 0000000..4fc8500 --- /dev/null +++ b/docs/src/api/sample/SuccessResp.tex @@ -0,0 +1,8 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "success":true, + "body":true +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/addCredSshKeyReq.tex b/docs/src/api/sample/request/addCredSshKeyReq.tex new file mode 100644 index 0000000..dcd3a29 --- /dev/null +++ b/docs/src/api/sample/request/addCredSshKeyReq.tex @@ -0,0 +1,11 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "username": "iparenteau", + "name": "Isaac's Creds", + "credentialType": "SSH", + "passphrase": "foo", + "key": "-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----" +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/addCredUnameReq.tex b/docs/src/api/sample/request/addCredUnameReq.tex new file mode 100644 index 0000000..534a83b --- /dev/null +++ b/docs/src/api/sample/request/addCredUnameReq.tex @@ -0,0 +1,10 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "username": "foo", + "password": "bar", + "name": "FooBar", + "credentialType": "PASSWD", +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/addEmailTemplateReq.tex b/docs/src/api/sample/request/addEmailTemplateReq.tex new file mode 100644 index 0000000..be3b0a7 --- /dev/null +++ b/docs/src/api/sample/request/addEmailTemplateReq.tex @@ -0,0 +1,9 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "emailName": "Script Status 2", + "emailSubject": "Script Status", + "emailMessage": "

Script ${script_name} finished with an exit code of ${exit_status}

", +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/addEnvVarsReq.tex b/docs/src/api/sample/request/addEnvVarsReq.tex new file mode 100644 index 0000000..dab8990 --- /dev/null +++ b/docs/src/api/sample/request/addEnvVarsReq.tex @@ -0,0 +1,16 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "name": "JAVA_HOME", + "value": "C:\Program Files\Java\jdk1.8.0_152", + "isNew": true + }, + { + "name": "MAVEN_HOME", + "value": "C:\tools\apache-maven-3.5.2", + "isNew": true + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/addGitRepoReq.tex b/docs/src/api/sample/request/addGitRepoReq.tex new file mode 100644 index 0000000..b28ea74 --- /dev/null +++ b/docs/src/api/sample/request/addGitRepoReq.tex @@ -0,0 +1,13 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "name": "Stig Apply", + "scm": "https://J65594@bigmac.northgrum.com/bitbucket/scm/saipt/stig-apply.git", + "branch": "develop", + "directory": "C:\\stigapply", + "credentials": { + "uid": "a7d6b897-8ec6-483c-9e68-c5a2676dcbf1", + } +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/addPermGroupReq.tex b/docs/src/api/sample/request/addPermGroupReq.tex new file mode 100644 index 0000000..02e5d25 --- /dev/null +++ b/docs/src/api/sample/request/addPermGroupReq.tex @@ -0,0 +1,19 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "name": "test", + "displayName": "Test", + "enabled": true, + "permissions": [ + { + "permissionId": 1, + "allowed": true + }, + { + "permissionId": 2, + "allowed": false + } + ] +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/addScriptLocalReq.tex b/docs/src/api/sample/request/addScriptLocalReq.tex new file mode 100644 index 0000000..098f177 --- /dev/null +++ b/docs/src/api/sample/request/addScriptLocalReq.tex @@ -0,0 +1,38 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "name": "tomcat running", + "command": "netstat", + "baseDirectory": "C:\stigapply", + "outputDirectory": "", + "scriptType": "LOCAL", + "parameters": [{ + "parameter": "-ano", + },{ + "parameter": "|grep", + },{ + "parameter": "8080", + }], + "schedule": [{ + "startDatetime": 1524229200673, + "endDatetime": 1524834000000, + "frequency": "HOURLY", + "maxExecutions": null, + }], + "scriptVariables": [{ + "name": "JAVA_HOME", + "value": "C:\Program Files\Java\jdk1.8.0_152", + }], + "scriptChain": [{ + "childScript": 3, + "exitStatus": 0 + }], + "scriptEmails": [{ + "emailName": "Script Status", + "recipients": [{ + "email": "portalAdmin@ngc.com", + }] + }], +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/addScriptRemoteReq.tex b/docs/src/api/sample/request/addScriptRemoteReq.tex new file mode 100644 index 0000000..2e78e96 --- /dev/null +++ b/docs/src/api/sample/request/addScriptRemoteReq.tex @@ -0,0 +1,42 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "name": "tomcat running", + "baseDirectory": "", + "outputDirectory": "", + "scriptType": "REMOTE", + "remote": { + "scriptCredentials": { + "uid":"e6e99ca6-afee-4de6-ad30-9a8f12429160", + }, + "url": "patchrepo.viprcenter.com", + "port": 22 + }, + "parameters": [{ + "parameter": "netstat -ano", + },{ + "parameter": "|grep 8080", + }], + "schedule": [{ + "startDatetime": 1524229200673, + "endDatetime": 1524834000000, + "frequency": "HOURLY", + "maxExecutions": null, + }], + "scriptVariables": [{ + "name": "JAVA_HOME", + "value": "C:\Program Files\Java\jdk1.8.0_152", + }], + "scriptChain": [{ + "childScript": 3, + "exitStatus": 0 + }], + "scriptEmails": [{ + "emailName": "Script Status", + "recipients": [{ + "email": "portalAdmin@ngc.com", + }] + }], +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/addScriptScpReq.tex b/docs/src/api/sample/request/addScriptScpReq.tex new file mode 100644 index 0000000..a7fd1cb --- /dev/null +++ b/docs/src/api/sample/request/addScriptScpReq.tex @@ -0,0 +1,39 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "name": "scp script", + "baseDirectory": "/opt/tomcat", + "outputDirectory": "", + "scriptType": "SCP", + "scp": { + "fromHost": "patchrepo.viprcenter.com", + "fromPort": 22, + "fromCredentials": { + "uid": ...", + }, + "toHost": "LOCAL", + "toPort": 0, + "toCredentials": { + "uid": "...", + }, + "dataRegex": "tomcat_[0-9][0-9]_status.txt" + }, + "schedule": [{ + "startDatetime": 1524229200673, + "endDatetime": 1524834000000, + "frequency": "HOURLY", + "maxExecutions": null, + }], + "scriptChain": [{ + "childScript": 3, + "exitStatus": 0 + }], + "scriptEmails": [{ + "emailName": "Script Status", + "recipients": [{ + "email": "portalAdmin@ngc.com", + }] + }], +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/addUserReq.tex b/docs/src/api/sample/request/addUserReq.tex new file mode 100644 index 0000000..8767cb5 --- /dev/null +++ b/docs/src/api/sample/request/addUserReq.tex @@ -0,0 +1,13 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "password": "bar", + "lastName": "foo", + "firstName": "bar", + "email": "bar@ngc.com", + "role": "USER", + "expirationDate": "2028-03-31T19:54:40.550Z", + "effectiveDate": "2018-04-03T19:54:39.929Z", +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/deleteCredsReq.tex b/docs/src/api/sample/request/deleteCredsReq.tex new file mode 100644 index 0000000..2b993be --- /dev/null +++ b/docs/src/api/sample/request/deleteCredsReq.tex @@ -0,0 +1,7 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "uid": "161415ae-07fd-427e-9bf8-3d08a2af7722", +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/deleteEmailTemplateReq.tex b/docs/src/api/sample/request/deleteEmailTemplateReq.tex new file mode 100644 index 0000000..dadf625 --- /dev/null +++ b/docs/src/api/sample/request/deleteEmailTemplateReq.tex @@ -0,0 +1,7 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 4 +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/deleteEnvVarsReq.tex b/docs/src/api/sample/request/deleteEnvVarsReq.tex new file mode 100644 index 0000000..e20628f --- /dev/null +++ b/docs/src/api/sample/request/deleteEnvVarsReq.tex @@ -0,0 +1,12 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 6, + }, + { + "id": 7, + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/deleteFileReq.tex b/docs/src/api/sample/request/deleteFileReq.tex new file mode 100644 index 0000000..78f82af --- /dev/null +++ b/docs/src/api/sample/request/deleteFileReq.tex @@ -0,0 +1,7 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 6 +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/deleteGitRepoReq.tex b/docs/src/api/sample/request/deleteGitRepoReq.tex new file mode 100644 index 0000000..4a20338 --- /dev/null +++ b/docs/src/api/sample/request/deleteGitRepoReq.tex @@ -0,0 +1,7 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 5 +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/deletePermGroupReq.tex b/docs/src/api/sample/request/deletePermGroupReq.tex new file mode 100644 index 0000000..80c3208 --- /dev/null +++ b/docs/src/api/sample/request/deletePermGroupReq.tex @@ -0,0 +1,7 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "name": "TEST" +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/deleteScriptReq.tex b/docs/src/api/sample/request/deleteScriptReq.tex new file mode 100644 index 0000000..b3069fb --- /dev/null +++ b/docs/src/api/sample/request/deleteScriptReq.tex @@ -0,0 +1,7 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 4, +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/deleteScriptScheduleReq.tex b/docs/src/api/sample/request/deleteScriptScheduleReq.tex new file mode 100644 index 0000000..a56f899 --- /dev/null +++ b/docs/src/api/sample/request/deleteScriptScheduleReq.tex @@ -0,0 +1,7 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ +"jobId": "a3bf7cac-bba5-4cd4-bdfa-9fdbcbf08e25", +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/deleteUserReq.tex b/docs/src/api/sample/request/deleteUserReq.tex new file mode 100644 index 0000000..a40b23e --- /dev/null +++ b/docs/src/api/sample/request/deleteUserReq.tex @@ -0,0 +1,7 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "email": "bar@ngc.com", +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/getConfValReq.tex b/docs/src/api/sample/request/getConfValReq.tex new file mode 100644 index 0000000..722d3d4 --- /dev/null +++ b/docs/src/api/sample/request/getConfValReq.tex @@ -0,0 +1,6 @@ +\begin{minipage}[t]{0.45\textwidth} +\footnotetext{Send the key name to the server} +\begin{lstlisting}[language=JSON] +userExpirationDays +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/getScriptStatusReq.tex b/docs/src/api/sample/request/getScriptStatusReq.tex new file mode 100644 index 0000000..b3acb8c --- /dev/null +++ b/docs/src/api/sample/request/getScriptStatusReq.tex @@ -0,0 +1,6 @@ +\begin{minipage}[t]{0.45\textwidth} +\footnotetext{Send the job id to the server} +\begin{lstlisting}[language=JSON] +459db38f-317f-40ad-bcf1-692c0404e7ac +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/loginReq.tex b/docs/src/api/sample/request/loginReq.tex new file mode 100644 index 0000000..c4e9f29 --- /dev/null +++ b/docs/src/api/sample/request/loginReq.tex @@ -0,0 +1,8 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] + { + "username":"foo", + "password":"bar" + } +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/purgeLogReq.tex b/docs/src/api/sample/request/purgeLogReq.tex new file mode 100644 index 0000000..69b2b53 --- /dev/null +++ b/docs/src/api/sample/request/purgeLogReq.tex @@ -0,0 +1,8 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "purgeStartDate": "2018-02-07T05:00:00.000Z", + "purgeEndDate": "2018-03-26T04:00:00.000Z" +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/runScriptReq.tex b/docs/src/api/sample/request/runScriptReq.tex new file mode 100644 index 0000000..b3069fb --- /dev/null +++ b/docs/src/api/sample/request/runScriptReq.tex @@ -0,0 +1,7 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 4, +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/updatePermGroupReq.tex b/docs/src/api/sample/request/updatePermGroupReq.tex new file mode 100644 index 0000000..03530c5 --- /dev/null +++ b/docs/src/api/sample/request/updatePermGroupReq.tex @@ -0,0 +1,18 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "name": "test", + "enabled": true, + "permissions": [ + { + "permissionId": 1, + "allowed": true + }, + { + "permissionId": 2, + "allowed": false + } + ] +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/request/updateSeedReq.tex b/docs/src/api/sample/request/updateSeedReq.tex new file mode 100644 index 0000000..26985c2 --- /dev/null +++ b/docs/src/api/sample/request/updateSeedReq.tex @@ -0,0 +1,6 @@ +\begin{minipage}[t]{0.45\textwidth} +\footnotetext{Send the seed text to the server} +\begin{lstlisting}[language=JSON] +This is a seed text +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/addCredSshKeyResp.tex b/docs/src/api/sample/response/addCredSshKeyResp.tex new file mode 100644 index 0000000..fc48759 --- /dev/null +++ b/docs/src/api/sample/response/addCredSshKeyResp.tex @@ -0,0 +1,14 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 6, + "uid": "e6e99ca6-afee-4de6-ad30-9a8f12429160", + "name": "Isaac's Creds", + "key": null, + "passphrase": null, + "username": "iparenteau", + "credentialType": "SSH" +} + +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/addCredUnameResp.tex b/docs/src/api/sample/response/addCredUnameResp.tex new file mode 100644 index 0000000..fb86418 --- /dev/null +++ b/docs/src/api/sample/response/addCredUnameResp.tex @@ -0,0 +1,12 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 5, + "uid": "252be4f2-d88f-4819-9ddb-e2176364804a", + "name": "FooBar", + "username": "Foo", + "password": null, + "credentialType": "PASSWD" +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/addEmailTemplateResp.tex b/docs/src/api/sample/response/addEmailTemplateResp.tex new file mode 100644 index 0000000..6ded5e7 --- /dev/null +++ b/docs/src/api/sample/response/addEmailTemplateResp.tex @@ -0,0 +1,12 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 4, + "emailName": "Script Status 2", + "emailSubject": "Script Status", + "emailMessage": "

Script ${script_name} finished with an exit code of ${exit_status}

", + "createdBy": 1, + "dateCreated": 1524165482009 +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/addEnvVarsResp.tex b/docs/src/api/sample/response/addEnvVarsResp.tex new file mode 100644 index 0000000..becd864 --- /dev/null +++ b/docs/src/api/sample/response/addEnvVarsResp.tex @@ -0,0 +1,24 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 6, + "name": "JAVA_HOME", + "value": "C:\Program Files\Java\jdk1.8.0_152", + "createdBy": 1, + "lastUpdated": 1524158738000, + "dateCreated": 1524158738000, + "isNew": false + }, + { + "id": 7, + "name": "MAVEN_HOME", + "value": "C:\tools\apache-maven-3.5.2", + "createdBy": 1, + "lastUpdated": 1524158738000, + "dateCreated": 1524158738000, + "isNew": false + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/addGitRepoResp.tex b/docs/src/api/sample/response/addGitRepoResp.tex new file mode 100644 index 0000000..b081c0a --- /dev/null +++ b/docs/src/api/sample/response/addGitRepoResp.tex @@ -0,0 +1,21 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 5, + "name": "Stig Apply", + "scm": "https://J65594@bigmac.northgrum.com/bitbucket/scm/saipt/stig-apply.git", + "branch": "develop", + "directory": "C:\\stigapply", + "credentials": { + "id": 3, + "uid": "a7d6b897-8ec6-483c-9e68-c5a2676dcbf1", + "name": "Bit Bucket", + "username": "J65594", + "credentialType": "PASSWD" + }, + "lastUpdated": 1523988019143, + "createDate": 1523988019143, + "createdBy": "Portal Admin" +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/addPermGroupResp.tex b/docs/src/api/sample/response/addPermGroupResp.tex new file mode 100644 index 0000000..d4c3e0b --- /dev/null +++ b/docs/src/api/sample/response/addPermGroupResp.tex @@ -0,0 +1,30 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 3, + "name": "TEST", + "displayName": "Test", + "enabled": true, + "permissions": [ + { + "id": 245, + "name": "VIEW_SCRIPTS_TAB", + "displayName": "View Repository Bank Tab", + "permissionCategory": "TABS", + "permissionId": 1, + "permissionGroupId": 3, + "allowed": true + }, + { + "id": 246, + "name": "VIEW_USER_TAB", + "displayName": "View User Tab", + "permissionCategory": "TABS", + "permissionId": 2, + "permissionGroupId": 3, + "allowed": false + } + ] +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/addScriptLocalResp.tex b/docs/src/api/sample/response/addScriptLocalResp.tex new file mode 100644 index 0000000..ffef1c8 --- /dev/null +++ b/docs/src/api/sample/response/addScriptLocalResp.tex @@ -0,0 +1,71 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 4, + "name": "tomcat running", + "command": "netstat", + "scriptType": "LOCAL", + "baseDirectory": "C:\stigapply", + "outputDirectory": "", + "parameters": [{ + "id": 1, + "parameter": "-ano", + "script": 4 + },{ + "id": 2, + "parameter": "|grep", + "script": 4 + },{ + "id": 3, + "parameter": "8080", + "script": 4 + }], + "schedule": [{ + "id": 5, + "jobId": "...", + "startDatetime": 1524229201000, + "endDatetime": 1524834000000, + "frequency": "HOURLY", + "maxExecutions": null, + "currentExecutionCount": null, + "executionStatus": null, + "lastRun": 0, + "script": 4 + }], + "scriptVariables": [{ + "id": 1, + "name": "JAVA_HOME", + "value": "C:\Program Files\Java\jdk1.8.0_152", + "createdBy": 1, + "lastUpdated": 1524226391000, + "dateCreated": 1524226391000, + "isNew": null, + "script": 4 + }], + "scriptChain": [{ + "id": 1, + "parentScript": 4, + "childScript": 3, + "executeAfterMin": null, + "exitStatus": 0 + }], + "scriptEmails": [{ + "id": 1, + "emailName": "Script Status", + "emailSubject": "Script Status", + "emailMessage": "...", + "scriptId": 4, + "recipients": [{ + "id": 1, + "firstName": "Portal", + "lastName": "Admin", + "role": "ADMIN", + "email": "portalAdmin@ngc.com", + "expirationDate": 2.534022324e+14, + "effectiveDate": 1519275600000, + "enabled": true + }] + }], +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/addScriptRemoteResp.tex b/docs/src/api/sample/response/addScriptRemoteResp.tex new file mode 100644 index 0000000..2b382e2 --- /dev/null +++ b/docs/src/api/sample/response/addScriptRemoteResp.tex @@ -0,0 +1,74 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 4, + "name": "tomcat running", + "command": "REMOTE", + "scriptType": "REMOTE", + "baseDirectory": "", + "outputDirectory": "", + "parameters": [{ + "id": 4, + "parameter": "netstat -ano", + "script": 6 + },{ + "id": 5, + "parameter": "|grep 8080", + "script": 6 + }],"schedule": [{ + "id": 5, + "jobId": "...", + "startDatetime": 1524229201000, + "endDatetime": 1524834000000, + "frequency": "HOURLY", + "maxExecutions": null, + "currentExecutionCount": null, + "executionStatus": null, + "lastRun": 0, + "script": 4 + }],"scriptVariables": [{ + "id": 1, + "name": "JAVA_HOME", + "value": "C:\Program Files\Java\jdk1.8.0_152", + "createdBy": 1, + "lastUpdated": 1524226391000, + "dateCreated": 1524226391000, + "isNew": null, + "script": 4 + }],"scriptChain": [{ + "id": 1, + "parentScript": 4, + "childScript": 3, + "executeAfterMin": null, + "exitStatus": 0 + }],"scriptEmails": [{ + "id": 1, + "emailName": "Script Status", + "emailSubject": "Script Status", + "emailMessage": "...", + "scriptId": 4, + "recipients": [{ + "id": 1, + "firstName": "Portal", + "lastName": "Admin", + "role": "ADMIN", + "email": "portalAdmin@ngc.com", + "expirationDate": 2.534022324e+14, + "effectiveDate": 1519275600000, + "enabled": true + }] + }],"remote": { + "scriptId": 6, + "scriptCredentials": { + "id": 6, + "uid": "e6e99ca6-afee-4de6-ad30-9a8f12429160", + "name": "Isaac's Creds", + "username": "iparenteau", + "credentialType": "SSH" + }, + "url": "patchrepo.viprcenter.com", + "port": 22 + }, +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/addScriptScpResp.tex b/docs/src/api/sample/response/addScriptScpResp.tex new file mode 100644 index 0000000..0266dda --- /dev/null +++ b/docs/src/api/sample/response/addScriptScpResp.tex @@ -0,0 +1,51 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 7, + "name": "scp script", + "command": "SCP", + "scriptType": "SCP", + "baseDirectory": "/opt/tomcat", + "outputDirectory": "", + "scp": { + "id": 1, + "scriptId": 7, + "fromHost": "patchrepo.viprcenter.com", + "fromPort": 22, + "fromCredentials": { + "id": 3, + "uid": "a7d6b897-8ec6-483c-9e68-c5a2676dcbf1", + "name": "Bit Bucket", + "username": "J65594", + "credentialType": "PASSWD" + }, + "toHost": "LOCAL", + "toPort": 0, + "toCredentials": { + "id": 3, + "uid": "a7d6b897-8ec6-483c-9e68-c5a2676dcbf1", + "name": "Bit Bucket", + "username": "J65594", + "credentialType": "PASSWD" + }, + "dataRegex": "tomcat_[0-9][0-9]_status.txt" + }, + "schedule": [{ + "startDatetime": 1524229200673, + "endDatetime": 1524834000000, + "frequency": "HOURLY", + "maxExecutions": null, + }], + "scriptChain": [{ + "childScript": 3, + "exitStatus": 0 + }], + "scriptEmails": [{ + "emailName": "Script Status", + "recipients": [{ + "email": "portalAdmin@ngc.com", + }] + }], +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/addUserResp.tex b/docs/src/api/sample/response/addUserResp.tex new file mode 100644 index 0000000..3736123 --- /dev/null +++ b/docs/src/api/sample/response/addUserResp.tex @@ -0,0 +1,17 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 3, + "auId": 3, + "firstName": "bar", + "lastName": "foo", + "role": "USER", + "username": "bar@ngc.com", + "password": null, + "email": "bar@ngc.com", + "expirationDate": 1838145280550, + "effectiveDate": 1522785279929, + "enabled": true +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/deleteCredsResp.tex b/docs/src/api/sample/response/deleteCredsResp.tex new file mode 100644 index 0000000..a68fe38 --- /dev/null +++ b/docs/src/api/sample/response/deleteCredsResp.tex @@ -0,0 +1,11 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 8, + "uid": "161415ae-07fd-427e-9bf8-3d08a2af7722", + "name": "FooBar", + "username": "foo", + "credentialType": "PASSWD" +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/deleteEnvVarsResp.tex b/docs/src/api/sample/response/deleteEnvVarsResp.tex new file mode 100644 index 0000000..becd864 --- /dev/null +++ b/docs/src/api/sample/response/deleteEnvVarsResp.tex @@ -0,0 +1,24 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 6, + "name": "JAVA_HOME", + "value": "C:\Program Files\Java\jdk1.8.0_152", + "createdBy": 1, + "lastUpdated": 1524158738000, + "dateCreated": 1524158738000, + "isNew": false + }, + { + "id": 7, + "name": "MAVEN_HOME", + "value": "C:\tools\apache-maven-3.5.2", + "createdBy": 1, + "lastUpdated": 1524158738000, + "dateCreated": 1524158738000, + "isNew": false + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/deletePermGroupResp.tex b/docs/src/api/sample/response/deletePermGroupResp.tex new file mode 100644 index 0000000..806aba1 --- /dev/null +++ b/docs/src/api/sample/response/deletePermGroupResp.tex @@ -0,0 +1,30 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 3, + "name": "TEST", + "displayName": "Test", + "enabled": false, + "permissions": [ + { + "id": 245, + "name": "VIEW_SCRIPTS_TAB", + "displayName": "View Repository Bank Tab", + "permissionCategory": "TABS", + "permissionId": 1, + "permissionGroupId": 3, + "allowed": true + }, + { + "id": 246, + "name": "VIEW_USER_TAB", + "displayName": "View User Tab", + "permissionCategory": "TABS", + "permissionId": 2, + "permissionGroupId": 3, + "allowed": false + } + ] +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/deleteScriptScheduleResp.tex b/docs/src/api/sample/response/deleteScriptScheduleResp.tex new file mode 100644 index 0000000..2de5b04 --- /dev/null +++ b/docs/src/api/sample/response/deleteScriptScheduleResp.tex @@ -0,0 +1,16 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 6, + "jobId": "a3bf7cac-bba5-4cd4-bdfa-9fdbcbf08e25", + "startDatetime": 1524229332000, + "endDatetime": null, + "frequency": "NOW", + "maxExecutions": 1, + "currentExecutionCount": 1, + "executionStatus": 1, + "lastRun": 1524229357000, + "script": 4 +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getAllCredsResp.tex b/docs/src/api/sample/response/getAllCredsResp.tex new file mode 100644 index 0000000..86bb713 --- /dev/null +++ b/docs/src/api/sample/response/getAllCredsResp.tex @@ -0,0 +1,20 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 3, + "uid": "a7d6b897-8ec6-483c-9e68-c5a2676dcbf1", + "name": "Bit Bucket", + "username": "J65594", + "credentialType": "PASSWD" + }, + { + "id": 6, + "uid": "e6e99ca6-afee-4de6-ad30-9a8f12429160", + "name": "Isaac's Creds", + "username": "iparenteau", + "credentialType": "SSH" + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getAllLogsResp.tex b/docs/src/api/sample/response/getAllLogsResp.tex new file mode 100644 index 0000000..d757007 --- /dev/null +++ b/docs/src/api/sample/response/getAllLogsResp.tex @@ -0,0 +1,33 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 45, + "loggedBy": "Repo Admin", + "eventName": "USER DELETED", + "event": "Deleted bar foo", + "severity": "INFO", + "ipAddress": "127.0.0.1", + "timestamp": 1522787487000 + }, + { + "id": 44, + "loggedBy": "Repo Admin", + "eventName": "LOGIN", + "event": "Logged in", + "severity": "INFO", + "ipAddress": "127.0.0.1", + "timestamp": 1522787466000 + }, + { + "id": 43, + "loggedBy": "Repo Admin", + "eventName": "EXCEPTION", + "event": "Something went wrong. Please see logs for details", + "severity": "ERROR", + "ipAddress": "127.0.0.1", + "timestamp": 1522787194000 + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getAllPermsResp.tex b/docs/src/api/sample/response/getAllPermsResp.tex new file mode 100644 index 0000000..534d7b7 --- /dev/null +++ b/docs/src/api/sample/response/getAllPermsResp.tex @@ -0,0 +1,20 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 1, + "name": "ADMIN", + "displayName": "Administrator", + "enabled": true, + "permissions": [] + }, + { + "id": 2, + "name": "USER", + "displayName": "User", + "enabled": true, + "permissions": [] + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getAllReposResp.tex b/docs/src/api/sample/response/getAllReposResp.tex new file mode 100644 index 0000000..6522cb4 --- /dev/null +++ b/docs/src/api/sample/response/getAllReposResp.tex @@ -0,0 +1,23 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 5, + "name": "Stig Apply", + "scm": "https://J65594@bigmac.northgrum.com/bitbucket/scm/saipt/stig-apply.git", + "branch": "develop", + "directory": "C:\\stigapply", + "credentials": { + "id": 3, + "uid": "a7d6b897-8ec6-483c-9e68-c5a2676dcbf1", + "name": "Bit Bucket", + "username": "J65594", + "credentialType": "PASSWD" + }, + "lastUpdated": 1523988019143, + "createDate": 1523988019143, + "createdBy": "Portal Admin" + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getAllScriptsResp.tex b/docs/src/api/sample/response/getAllScriptsResp.tex new file mode 100644 index 0000000..bcdfeee --- /dev/null +++ b/docs/src/api/sample/response/getAllScriptsResp.tex @@ -0,0 +1,71 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[{ + "id": 4, + "name": "tomcat running", + "command": "netstat", + "scriptType": "LOCAL", + "baseDirectory": "C:\stigapply", + "outputDirectory": "", + "parameters": [{ + "id": 1, + "parameter": "-ano", + "script": 4 + },{ + "id": 2, + "parameter": "|grep", + "script": 4 + },{ + "id": 3, + "parameter": "8080", + "script": 4 + }], + "schedule": [{ + "id": 5, + "jobId": "...", + "startDatetime": 1524229201000, + "endDatetime": 1524834000000, + "frequency": "HOURLY", + "maxExecutions": null, + "currentExecutionCount": null, + "executionStatus": null, + "lastRun": 0, + "script": 4 + }], + "scriptVariables": [{ + "id": 1, + "name": "JAVA_HOME", + "value": "C:\Program Files\Java\jdk1.8.0_152", + "createdBy": 1, + "lastUpdated": 1524226391000, + "dateCreated": 1524226391000, + "isNew": null, + "script": 4 + }], + "scriptChain": [{ + "id": 1, + "parentScript": 4, + "childScript": 3, + "executeAfterMin": null, + "exitStatus": 0 + }], + "scriptEmails": [{ + "id": 1, + "emailName": "Script Status", + "emailSubject": "Script Status", + "emailMessage": "...", + "scriptId": 4, + "recipients": [{ + "id": 1, + "firstName": "Portal", + "lastName": "Admin", + "role": "ADMIN", + "email": "portalAdmin@ngc.com", + "expirationDate": 2.534022324e+14, + "effectiveDate": 1519275600000, + "enabled": true + }] + }], +}] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getAllUsersResp.tex b/docs/src/api/sample/response/getAllUsersResp.tex new file mode 100644 index 0000000..9910c43 --- /dev/null +++ b/docs/src/api/sample/response/getAllUsersResp.tex @@ -0,0 +1,32 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 1, + "auId": 1, + "firstName": "Repo", + "lastName": "Admin", + "role": "ADMIN", + "username": "portalAdmin@ngc.com", + "password": null, + "email": "portalAdmin@ngc.com", + "expirationDate": 2.534022324e+14, + "effectiveDate": 1517979600000, + "enabled": true + }, + { + "id": 2, + "auId": 2, + "firstName": "foo", + "lastName": "bar", + "role": "USER", + "username": "foo@ngc.com", + "password": null, + "email": "foo@ngc.com", + "expirationDate": 1838088000000, + "effectiveDate": 1522728000000, + "enabled": true + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getConfResp.tex b/docs/src/api/sample/response/getConfResp.tex new file mode 100644 index 0000000..d3b1d34 --- /dev/null +++ b/docs/src/api/sample/response/getConfResp.tex @@ -0,0 +1,16 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "logLevel": "INFO", + "userExpirationDays": "3650", + "dbRootPassword": "Em46JDIzVOWBgGFqxDrqnw==", + "dbHost": "localhost", + "dbPort": "3306", + "dbRootUser": "root", + "dbUsername": "portalAdmin", + "filepath": "C:\\tempStorage", + "dbPassword": "CWg48+h4q7iK4ICGaDhRd10EMQfFgthm3FjhkLd6gis=" + "aesSeedFile": "C:/development/apache-tomcat-8.5.24/conf/portal.tomcat" +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getConfValResp.tex b/docs/src/api/sample/response/getConfValResp.tex new file mode 100644 index 0000000..3151a76 --- /dev/null +++ b/docs/src/api/sample/response/getConfValResp.tex @@ -0,0 +1,7 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "userExpirationDays": 3650 +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getCredTypesResp.tex b/docs/src/api/sample/response/getCredTypesResp.tex new file mode 100644 index 0000000..9bb42dc --- /dev/null +++ b/docs/src/api/sample/response/getCredTypesResp.tex @@ -0,0 +1,16 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 1, + "name": "SSH", + "value": "SSH Key" + }, + { + "id": 2, + "name": "PASSWD", + "value": "Username/Password" + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getEmailTemplateResp.tex b/docs/src/api/sample/response/getEmailTemplateResp.tex new file mode 100644 index 0000000..4b8b2e2 --- /dev/null +++ b/docs/src/api/sample/response/getEmailTemplateResp.tex @@ -0,0 +1,16 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 1, + "emailName": "Script Status", + "emailSubject": "Script Status", + "emailMessage": "

Script ${script_name} finished with exit code of ${exit_status}

\n +

Results:

\n

${script_results}

\n

Thank you,

\n +

postmaster@viprcenter.com

", + "createdBy": null, + "dateCreated": null + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getLoggersResp.tex b/docs/src/api/sample/response/getLoggersResp.tex new file mode 100644 index 0000000..d96df1f --- /dev/null +++ b/docs/src/api/sample/response/getLoggersResp.tex @@ -0,0 +1,22 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "logger": "AbstractObjectMessageHandler", + "level": "INFO" + }, + { + "logger": "AccountController", + "level": "ERROR" + }, + { + "logger": "ApplicationLogController", + "level": "INFO" + }, + { + "logger": "ApplicationLogService", + "level": "INFO" + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getPermCatResp.tex b/docs/src/api/sample/response/getPermCatResp.tex new file mode 100644 index 0000000..eec29ea --- /dev/null +++ b/docs/src/api/sample/response/getPermCatResp.tex @@ -0,0 +1,56 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 1, + "name": "TABS", + "displayName": "Tabs", + "permissions": [ + { + "id": 1, + "name": "VIEW_SCRIPTS_TAB", + "displayName": "View Repository Bank Tab", + "permissionCategory": "TABS" + }, + { + "id": 2, + "name": "VIEW_USER_TAB", + "displayName": "View User Tab", + "permissionCategory": "TABS" + }, + { + "id": 3, + "name": "VIEW_LOG_TAB", + "displayName": "View System Log Tab", + "permissionCategory": "TABS" + }, + { + "id": 4, + "name": "VIEW_PERMISSION_TAB", + "displayName": "View Permission Tab", + "permissionCategory": "TABS" + } + ] + }, + { + "id": 3, + "name": "PERMISSION_TAB", + "displayName": "Permissions Tab", + "permissions": [ + { + "id": 22, + "name": "ADD_PERMS", + "displayName": "Add/Edit Permissions", + "permissionCategory": "PERMISSION_TAB" + }, + { + "id": 23, + "name": "DELETE_PERMS", + "displayName": "Delete Permissions", + "permissionCategory": "PERMISSION_TAB" + } + ] + }, +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getPermGroupResp.tex b/docs/src/api/sample/response/getPermGroupResp.tex new file mode 100644 index 0000000..14ef0dd --- /dev/null +++ b/docs/src/api/sample/response/getPermGroupResp.tex @@ -0,0 +1,60 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 1, + "name": "ADMIN", + "displayName": "Administrator", + "enabled": true, + "permissions": [ + { + "type": null, + "id": 181, + "name": "VIEW_SCRIPTS_TAB", + "displayName": "View Scripts Tab", + "permissionCategory": "TABS", + "permissionId": 1, + "permissionGroupId": 1, + "allowed": true + }, + { + "type": null, + "id": 182, + "name": "VIEW_USER_TAB", + "displayName": "View User Tab", + "permissionCategory": "TABS", + "permissionId": 2, + "permissionGroupId": 1, + "allowed": true + } + ] + }, + { + "id": 2, + "name": "USER", + "displayName": "User", + "enabled": true, + "permissions": [ + { + "id": 233, + "name": "VIEW_SCRIPTS_TAB", + "displayName": "View Repository Bank Tab", + "permissionCategory": "TABS", + "permissionId": 1, + "permissionGroupId": 2, + "allowed": true + }, + { + "id": 234, + "name": "VIEW_USER_TAB", + "displayName": "View User Tab", + "permissionCategory": "TABS", + "permissionId": 2, + "permissionGroupId": 2, + "allowed": false + } + ] + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getScheduleFreqResp.tex b/docs/src/api/sample/response/getScheduleFreqResp.tex new file mode 100644 index 0000000..03840df --- /dev/null +++ b/docs/src/api/sample/response/getScheduleFreqResp.tex @@ -0,0 +1,46 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 1, + "name": "HOURLY", + "value": "Hourly" + }, + { + "id": 2, + "name": "DAILY", + "value": "Daily" + }, + { + "id": 3, + "name": "WEEKLY", + "value": "Weekly" + }, + { + "id": 4, + "name": "MONTHLY", + "value": "Monthly" + }, + { + "id": 5, + "name": "YEARLY", + "value": "Yearly" + }, + { + "id": 6, + "name": "NOW", + "value": "Now" + }, + { + "id": 7, + "name": "SPECIFIC", + "value": "Specific" + }, + { + "id": 8, + "name": "TRIGGERED", + "value": "Triggered" + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getScriptScheduleStatusResp.tex b/docs/src/api/sample/response/getScriptScheduleStatusResp.tex new file mode 100644 index 0000000..2a2925a --- /dev/null +++ b/docs/src/api/sample/response/getScriptScheduleStatusResp.tex @@ -0,0 +1,26 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 31, + "exitStatus": 0, + "results": "...", + "startTime": 1524243606000, + "endTime": 1524243606000, + "resourceFiles": [ + + ] + }, + { + "id": 30, + "exitStatus": 0, + "results": "..", + "startTime": 1524240727000, + "endTime": 1524240727000, + "resourceFiles": [ + + ] + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getScriptTypesResp.tex b/docs/src/api/sample/response/getScriptTypesResp.tex new file mode 100644 index 0000000..533048a --- /dev/null +++ b/docs/src/api/sample/response/getScriptTypesResp.tex @@ -0,0 +1,21 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 1, + "name": "LOCAL", + "value": "Local Execution" + }, + { + "id": 2, + "name": "REMOTE", + "value": "Remote Execution" + }, + { + "id": 3, + "name": "SCP", + "value": "Secure Copy" + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/getSmtpConfigResp.tex b/docs/src/api/sample/response/getSmtpConfigResp.tex new file mode 100644 index 0000000..c3954d9 --- /dev/null +++ b/docs/src/api/sample/response/getSmtpConfigResp.tex @@ -0,0 +1,31 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +[ + { + "id": 1, + "name": "smtp.enabled", + "value": "false" + }, + { + "id": 2, + "name": "mail.transport.protocol", + "value": "smtp" + }, + { + "id": 3, + "name": "mail.smtp.host", + "value": "" + }, + { + "id": 4, + "name": "mail.smtp.port", + "value": "0" + }, + { + "id": 5, + "name": "mail.debug", + "value": "false" + } +] +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/isLoggedInResp.tex b/docs/src/api/sample/response/isLoggedInResp.tex new file mode 100644 index 0000000..ba88166 --- /dev/null +++ b/docs/src/api/sample/response/isLoggedInResp.tex @@ -0,0 +1,8 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "success":false, + "body":null +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/loginResp.tex b/docs/src/api/sample/response/loginResp.tex new file mode 100644 index 0000000..b52f035 --- /dev/null +++ b/docs/src/api/sample/response/loginResp.tex @@ -0,0 +1,32 @@ +\begin{minipage}[t]{0.45\textwidth} +\begin{lstlisting}[language=JSON] +{ + "id": 1, + "auId": 1, + "firstName": "foo", + "lastName": "bar", + "role": "USER", + "username": "foo@ngc.com", + "email": "foo@ngc.com", + "expirationDate": 2.534022324e+14, + "effectiveDate": 1517979600000, + "enabled": true, + "isNew": false, + "authorities": [ + "VIEW_REPO_TAB", + "VIEW_USER_TAB", + "VIEW_LOG_TAB", + "VIEW_PERMISSION_TAB", + "VIEW_CONFIG", + "VIEW_HASH_TYPES", + "VIEW_FILE_INFO", + "VIEW_LOG_LEVELS", + ], + "stompTopics": [ + "/topic/account|system", + "/topic/account|1" + ], + "page": "dashboard" +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/api/sample/response/purgeLogResp.tex b/docs/src/api/sample/response/purgeLogResp.tex new file mode 100644 index 0000000..583f814 --- /dev/null +++ b/docs/src/api/sample/response/purgeLogResp.tex @@ -0,0 +1,9 @@ +\begin{minipage}[t]{0.45\textwidth} +\footnotetext{The return value in body is the number of logs that were purged} +\begin{lstlisting}[language=JSON] +{ + "success":true, + "body":13 +} +\end{lstlisting} +\end{minipage} \ No newline at end of file diff --git a/docs/src/appendix/ApacheAppendex.tex b/docs/src/appendix/ApacheAppendex.tex new file mode 100644 index 0000000..080ef21 --- /dev/null +++ b/docs/src/appendix/ApacheAppendex.tex @@ -0,0 +1,10 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : ApacheAppendix.tex +% SUBJECT : ApacheApendex. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{Apache Configuration} +\label{sample-httpd.conf} +\lstinputlisting[caption=httpd.conf]{figures/httpd.conf} \ No newline at end of file diff --git a/docs/src/appendix/NginxAppendex.tex b/docs/src/appendix/NginxAppendex.tex new file mode 100644 index 0000000..6fff2a7 --- /dev/null +++ b/docs/src/appendix/NginxAppendex.tex @@ -0,0 +1,10 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : Architecture.tex +% SUBJECT : Document describing architecture issues in the entire Patch Repository system. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{NGINX Configuration} +\label{sample-nginx.conf} +\lstinputlisting[caption=portal.conf]{figures/portal.conf} \ No newline at end of file diff --git a/docs/src/appendix/PropertiesAppendex.tex b/docs/src/appendix/PropertiesAppendex.tex new file mode 100644 index 0000000..af078a7 --- /dev/null +++ b/docs/src/appendix/PropertiesAppendex.tex @@ -0,0 +1,10 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : Architecture.tex +% SUBJECT : Document describing architecture issues in the entire Patch Repository system. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{Portal Properties File} +\label{sample-portal.properties} +\lstinputlisting[caption=portal.properties]{figures/portal.properties} \ No newline at end of file diff --git a/docs/src/appendix/Revisions.tex b/docs/src/appendix/Revisions.tex new file mode 100644 index 0000000..936a85f --- /dev/null +++ b/docs/src/appendix/Revisions.tex @@ -0,0 +1,16 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : Revision.tex +% SUBJECT : Revision history. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{Revision History} +\label{revisions} +\begin{center} + \begin{tabular}{|C{0.1\textwidth}|C{0.5\textwidth}|C{0.15\textwidth}|C{0.15\textwidth}|} + \hline + \bold{Revision Number} & \bold{Comment} & \bold{Date} & \bold{Name} \\ \hline + 1.0 & Initial documentation draft & 10 April, 2018 & Isaac Parenteau \\ \hline + \end{tabular} +\end{center} \ No newline at end of file diff --git a/docs/src/appendix/SeedFileAppendex.tex b/docs/src/appendix/SeedFileAppendex.tex new file mode 100644 index 0000000..4582c8c --- /dev/null +++ b/docs/src/appendix/SeedFileAppendex.tex @@ -0,0 +1,23 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : ApacheAppendix.tex +% SUBJECT : ApacheApendex. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{Seed File Configuration} +\label{sample-seed-file} + +The seed file is a phrase or random letters using the \filename{UTF-8} character set. This file is read into the encryption service, converted to binary and passed to the AES encryption +program as the seed for java's \filename{SecureRandom} class. Since the same seed is used every time, the generated AES key is the same and all values afterwards. Any changes to this seed +file will invalidate any data encrypted. There is a mechanism in place in the application to change the seed file value and all subsequent data provided the user has the proper permissions. + +Sample seed value: + +\begin{minipage}[t]{0.45\textwidth} +\begin{verbatim} +thisIsASecretKey +\end{verbatim} +\end{minipage} + + diff --git a/docs/src/appendix/SettingsAppendex.tex b/docs/src/appendix/SettingsAppendex.tex new file mode 100644 index 0000000..562a2fe --- /dev/null +++ b/docs/src/appendix/SettingsAppendex.tex @@ -0,0 +1,10 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : Architecture.tex +% SUBJECT : Document describing architecture issues in the entire Patch Repository system. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\chapter{Sample Settings.xml} +\label{sample-settings.xml} +\lstinputlisting[language=XML, caption=settings.xml]{figures/settings.xml} \ No newline at end of file diff --git a/docs/src/figures/NGLogo.png b/docs/src/figures/NGLogo.png new file mode 100644 index 0000000..17fafa6 Binary files /dev/null and b/docs/src/figures/NGLogo.png differ diff --git a/docs/src/figures/Overall-Architecture.png b/docs/src/figures/Overall-Architecture.png new file mode 100644 index 0000000..885106e Binary files /dev/null and b/docs/src/figures/Overall-Architecture.png differ diff --git a/docs/src/figures/add-cred-priv.png b/docs/src/figures/add-cred-priv.png new file mode 100644 index 0000000..b704154 Binary files /dev/null and b/docs/src/figures/add-cred-priv.png differ diff --git a/docs/src/figures/add-cred-un.png b/docs/src/figures/add-cred-un.png new file mode 100644 index 0000000..dfe7dea Binary files /dev/null and b/docs/src/figures/add-cred-un.png differ diff --git a/docs/src/figures/add-cred.png b/docs/src/figures/add-cred.png new file mode 100644 index 0000000..8da8aa3 Binary files /dev/null and b/docs/src/figures/add-cred.png differ diff --git a/docs/src/figures/add-email.png b/docs/src/figures/add-email.png new file mode 100644 index 0000000..75dddb4 Binary files /dev/null and b/docs/src/figures/add-email.png differ diff --git a/docs/src/figures/add-envvar.png b/docs/src/figures/add-envvar.png new file mode 100644 index 0000000..5599c29 Binary files /dev/null and b/docs/src/figures/add-envvar.png differ diff --git a/docs/src/figures/add-git.png b/docs/src/figures/add-git.png new file mode 100644 index 0000000..8dc0cfd Binary files /dev/null and b/docs/src/figures/add-git.png differ diff --git a/docs/src/figures/add-perm.png b/docs/src/figures/add-perm.png new file mode 100644 index 0000000..943eb24 Binary files /dev/null and b/docs/src/figures/add-perm.png differ diff --git a/docs/src/figures/add-script.png b/docs/src/figures/add-script.png new file mode 100644 index 0000000..5a00baa Binary files /dev/null and b/docs/src/figures/add-script.png differ diff --git a/docs/src/figures/add-user.png b/docs/src/figures/add-user.png new file mode 100644 index 0000000..4f7e6fb Binary files /dev/null and b/docs/src/figures/add-user.png differ diff --git a/docs/src/figures/aes-seed.png b/docs/src/figures/aes-seed.png new file mode 100644 index 0000000..ec9017a Binary files /dev/null and b/docs/src/figures/aes-seed.png differ diff --git a/docs/src/figures/config-db.png b/docs/src/figures/config-db.png new file mode 100644 index 0000000..dc86e9d Binary files /dev/null and b/docs/src/figures/config-db.png differ diff --git a/docs/src/figures/config-gen.png b/docs/src/figures/config-gen.png new file mode 100644 index 0000000..248e63d Binary files /dev/null and b/docs/src/figures/config-gen.png differ diff --git a/docs/src/figures/config-root.png b/docs/src/figures/config-root.png new file mode 100644 index 0000000..9ec14ba Binary files /dev/null and b/docs/src/figures/config-root.png differ diff --git a/docs/src/figures/cred-tab.png b/docs/src/figures/cred-tab.png new file mode 100644 index 0000000..c3887c8 Binary files /dev/null and b/docs/src/figures/cred-tab.png differ diff --git a/docs/src/figures/edit-perm.png b/docs/src/figures/edit-perm.png new file mode 100644 index 0000000..36725f1 Binary files /dev/null and b/docs/src/figures/edit-perm.png differ diff --git a/docs/src/figures/email-tab.png b/docs/src/figures/email-tab.png new file mode 100644 index 0000000..afa4369 Binary files /dev/null and b/docs/src/figures/email-tab.png differ diff --git a/docs/src/figures/envvar-tab.png b/docs/src/figures/envvar-tab.png new file mode 100644 index 0000000..c9c708f Binary files /dev/null and b/docs/src/figures/envvar-tab.png differ diff --git a/docs/src/figures/git-tab.png b/docs/src/figures/git-tab.png new file mode 100644 index 0000000..77770ef Binary files /dev/null and b/docs/src/figures/git-tab.png differ diff --git a/docs/src/figures/httpd.conf b/docs/src/figures/httpd.conf new file mode 100644 index 0000000..84d2ddf --- /dev/null +++ b/docs/src/figures/httpd.conf @@ -0,0 +1,18 @@ +Listen 80 +Listen 443 + + + RewriteEngine On + RewriteCond %{HTTP_HOST} ^(.*)$ + RewriteRule ^(.*)$ https://%1$1 [R=Permanent,L,QSA] + + + + SSLEngine On + SSLCertificateFile /etc/apache/ssl/portal.viprcenter.com.crt + SSLCertificateKeyFile /etc/apache/ssl/portal.viprcenter.com.key + + ProxyPreserveHost On + ProxyPass / http://127.0.0.1:8080/ + ProxyPassReverse / http://127.0.0.1:8080/ + \ No newline at end of file diff --git a/docs/src/figures/local-execution.png b/docs/src/figures/local-execution.png new file mode 100644 index 0000000..c14dc8f Binary files /dev/null and b/docs/src/figures/local-execution.png differ diff --git a/docs/src/figures/log-levels.png b/docs/src/figures/log-levels.png new file mode 100644 index 0000000..7611564 Binary files /dev/null and b/docs/src/figures/log-levels.png differ diff --git a/docs/src/figures/log-tab.png b/docs/src/figures/log-tab.png new file mode 100644 index 0000000..c4e8a88 Binary files /dev/null and b/docs/src/figures/log-tab.png differ diff --git a/docs/src/figures/login.png b/docs/src/figures/login.png new file mode 100644 index 0000000..8d9c77c Binary files /dev/null and b/docs/src/figures/login.png differ diff --git a/docs/src/figures/menu.png b/docs/src/figures/menu.png new file mode 100644 index 0000000..1d2ee01 Binary files /dev/null and b/docs/src/figures/menu.png differ diff --git a/docs/src/figures/netbeans-database.png b/docs/src/figures/netbeans-database.png new file mode 100644 index 0000000..c33a142 Binary files /dev/null and b/docs/src/figures/netbeans-database.png differ diff --git a/docs/src/figures/netbeans-entityclass.png b/docs/src/figures/netbeans-entityclass.png new file mode 100644 index 0000000..c59ebbe Binary files /dev/null and b/docs/src/figures/netbeans-entityclass.png differ diff --git a/docs/src/figures/netbeans-mapping.png b/docs/src/figures/netbeans-mapping.png new file mode 100644 index 0000000..6895779 Binary files /dev/null and b/docs/src/figures/netbeans-mapping.png differ diff --git a/docs/src/figures/permission-tab.png b/docs/src/figures/permission-tab.png new file mode 100644 index 0000000..7a9f476 Binary files /dev/null and b/docs/src/figures/permission-tab.png differ diff --git a/docs/src/figures/portal.conf b/docs/src/figures/portal.conf new file mode 100644 index 0000000..fdfe7c1 --- /dev/null +++ b/docs/src/figures/portal.conf @@ -0,0 +1,48 @@ +proxy_cache_path /tmp/NGINX_cache/ keys_zone=backcache:10m; + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +upstream tomcat { + # Use IP Hash for session persistence + ip_hash; + + # List of Tomcat application servers + server 127.0.0.1:8080; +} + +server { + listen 80; + server_name patchrepo.viprcenter.com; + client_max_body_size 0; + + # Redirect all HTTP requests to HTTPS + location / { + return 301 https://$server_name$request_uri; + } +} + +server { + listen 443 ssl http2; + server_name patchrepo.viprcenter.com; + client_max_body_size 0; + + ssl_certificate /etc/nginx/ssl/portal.viprcenter.com.crt; + ssl_certificate_key /etc/nginx/ssl/portal.viprcenter.com.key; + + ssl_session_cache shared:SSL:1m; + ssl_prefer_server_ciphers on; + + # WebSocket configuration + location / { + proxy_pass http://tomcat; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Host $remote_addr; + } +} \ No newline at end of file diff --git a/docs/src/figures/portal.png b/docs/src/figures/portal.png new file mode 100644 index 0000000..657ae49 Binary files /dev/null and b/docs/src/figures/portal.png differ diff --git a/docs/src/figures/portal.properties b/docs/src/figures/portal.properties new file mode 100644 index 0000000..a00c650 --- /dev/null +++ b/docs/src/figures/portal.properties @@ -0,0 +1,11 @@ +userExpirationDays=3650 +logLevel=INFO +dbHost=localhost +dbPort=3306 +dbUsername=portalAdmin +dbPassword=CWg48+h4q7iK4ICGaDhRd10EMQfFgthm3FjhkLd6gis\= +dbRootUser=root +dbRootPassword=Em46JDIzVOWBgGFqxDrqnw\=\= +caCertFile=${user.home}/cacerts.jks +caCertPassword=N/OQnJ9uQh1oYxadwGC3vA== +aesSeedFile=${catalina.base}/conf/portal.tomcat \ No newline at end of file diff --git a/docs/src/figures/purge-log.png b/docs/src/figures/purge-log.png new file mode 100644 index 0000000..b2faaae Binary files /dev/null and b/docs/src/figures/purge-log.png differ diff --git a/docs/src/figures/script-chain.png b/docs/src/figures/script-chain.png new file mode 100644 index 0000000..747760d Binary files /dev/null and b/docs/src/figures/script-chain.png differ diff --git a/docs/src/figures/script-email.png b/docs/src/figures/script-email.png new file mode 100644 index 0000000..d4baba1 Binary files /dev/null and b/docs/src/figures/script-email.png differ diff --git a/docs/src/figures/script-env.png b/docs/src/figures/script-env.png new file mode 100644 index 0000000..ee98db9 Binary files /dev/null and b/docs/src/figures/script-env.png differ diff --git a/docs/src/figures/script-local.png b/docs/src/figures/script-local.png new file mode 100644 index 0000000..9187e36 Binary files /dev/null and b/docs/src/figures/script-local.png differ diff --git a/docs/src/figures/script-params.png b/docs/src/figures/script-params.png new file mode 100644 index 0000000..ae728d2 Binary files /dev/null and b/docs/src/figures/script-params.png differ diff --git a/docs/src/figures/script-remote.png b/docs/src/figures/script-remote.png new file mode 100644 index 0000000..593e35e Binary files /dev/null and b/docs/src/figures/script-remote.png differ diff --git a/docs/src/figures/script-schedule.png b/docs/src/figures/script-schedule.png new file mode 100644 index 0000000..39d3605 Binary files /dev/null and b/docs/src/figures/script-schedule.png differ diff --git a/docs/src/figures/script-scp.png b/docs/src/figures/script-scp.png new file mode 100644 index 0000000..35d0bb8 Binary files /dev/null and b/docs/src/figures/script-scp.png differ diff --git a/docs/src/figures/script-status-results.png b/docs/src/figures/script-status-results.png new file mode 100644 index 0000000..7db59b0 Binary files /dev/null and b/docs/src/figures/script-status-results.png differ diff --git a/docs/src/figures/script-status.png b/docs/src/figures/script-status.png new file mode 100644 index 0000000..783dad9 Binary files /dev/null and b/docs/src/figures/script-status.png differ diff --git a/docs/src/figures/scripts-tab.png b/docs/src/figures/scripts-tab.png new file mode 100644 index 0000000..6e8f654 Binary files /dev/null and b/docs/src/figures/scripts-tab.png differ diff --git a/docs/src/figures/settings.xml b/docs/src/figures/settings.xml new file mode 100644 index 0000000..38e92ed --- /dev/null +++ b/docs/src/figures/settings.xml @@ -0,0 +1,46 @@ + + + + ngas-proxy + true + http + eastproxy.northgrum.com + 80 + (your MyID) + (your login password) + + + ngas-proxy2 + true + https + eastproxy.northgrum.com + 80 + (your MyID) + (your login password) + + + + + flyway-localhost + root + (mysql root password) + + + + tomcat-localhost + (manager-script username) + (manager-script password) + + + + + local-deploy + + C:\development\apache-tomcat-8.5.24 + 8.5.24 + http://localhost:8080 + + + + \ No newline at end of file diff --git a/docs/src/figures/smtp-auth.png b/docs/src/figures/smtp-auth.png new file mode 100644 index 0000000..1c9f0ad Binary files /dev/null and b/docs/src/figures/smtp-auth.png differ diff --git a/docs/src/figures/smtp-gen.png b/docs/src/figures/smtp-gen.png new file mode 100644 index 0000000..6b80678 Binary files /dev/null and b/docs/src/figures/smtp-gen.png differ diff --git a/docs/src/figures/smtp-tls.png b/docs/src/figures/smtp-tls.png new file mode 100644 index 0000000..a950cdb Binary files /dev/null and b/docs/src/figures/smtp-tls.png differ diff --git a/docs/src/figures/spring-flow.png b/docs/src/figures/spring-flow.png new file mode 100644 index 0000000..397ac17 Binary files /dev/null and b/docs/src/figures/spring-flow.png differ diff --git a/docs/src/figures/user-tab.png b/docs/src/figures/user-tab.png new file mode 100644 index 0000000..8614df7 Binary files /dev/null and b/docs/src/figures/user-tab.png differ diff --git a/docs/src/guides/AESSeed.tex b/docs/src/guides/AESSeed.tex new file mode 100644 index 0000000..a72ebfc --- /dev/null +++ b/docs/src/guides/AESSeed.tex @@ -0,0 +1,43 @@ +\section{AES Seed Settings} +\label{aesSeed} + +All the username, passwords, private keys, passphrases are encrypted using AES encryption. To increase security, the AES key relies on a +seed\footnote{A seed is a number or vector used to initialize a pseudorandom number generator. It will generate the same output every time if the same seed is used} +file (see Appendex~\ref{sample-seed-file}). This file is loaded into java's \filename{SecureRandom} class to generate the AES key. + +The \filename{portal.properties} specifies where this seed file is located (see Appendex~\ref{sample-portal.properties}). This file should be in a protected area with strict access. + +Some legacy operating systems do not support setting seeds within a random number generator such as \filename{SecureRandom}; therefore, legacy systems running \portal will fall back +to using the key defined within the code itself. This can pose a security so it is advise to upgrade the server in which the application runs on to a newer operating system. + +During the course of operations it might be necessary for the seed file to be changed. This section describes how to do this. + +\subsection{Changing Seed Value} +The following case allows a user to change the seed value. Upon saving, the server will go through all encrypted values and updated them using the new seed. + +\begin{figure}[!htb] + \centering + \scalebox{0.75}{\includegraphics*{figures/menu.png}} + \caption{Menu} + \label{fig:menu} +\end{figure} + + \begin{figure}[!htp] + \centering + \scalebox{0.75}{\includegraphics*{figures/aes-seed.png}} + \caption{AES Seed Configuration} + \label{fig:aes-seed} +\end{figure} + +\begin{usecase} + {CHANGE AES Seed Value} + {Authenticated User with change aes seed permission} + {The user is viewing the aes seed dialog after selecting it from the menu. Reference figure~\ref{fig:menu}} + \begin{enumerate} + \item User inputs new seed value. This can be any input. See Figure~\ref{fig:aes-seed} + \item User will be presented with a confirmation box. + \item If `No' is selected the dialog box will disappear + \item If `Yes' is selected the encrypted data will update and the dialog box will disappear. + \item Success or error message will be presented to the user in the top left corner of the script known as a toast message. + \end{enumerate} +\end{usecase} diff --git a/docs/src/guides/Configuration.tex b/docs/src/guides/Configuration.tex new file mode 100644 index 0000000..d62adce --- /dev/null +++ b/docs/src/guides/Configuration.tex @@ -0,0 +1,87 @@ +\section{Configuration Settings} +\label{configuration} + +The configuration settings (located in the menu drop down to right hand corner. See figure~\ref{fig:menu}) controls various aspects of the server during start up. Any changes made here will not take effect until server restart. + +\begin{figure}[!htb] + \centering + \scalebox{0.75}{\includegraphics*{figures/menu.png}} + \caption{Menu} + \label{fig:menu} +\end{figure} + +\subsection{General Configuration} + +General configuration tells the server where the upload files will be stored locally, the default logging level and the default number of days a new user account will expire in. + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/config-gen.png} + \caption{General Configuration} + \label{fig:gen-config} +\end{figure} + +\begin{usecase} + {EDIT GENERAL CONFIGURATION} + {Authenticated User with change configuration permission} + {The user is viewing the General Configuration Tab within the configuration settings. Reference figure~\ref{fig:gen-config}} + \begin{enumerate} + \item User enters the location where the upload files will be stored on the server + \item User selects the default log level. This determines the default verbosity of the logs during start up + \item User enters the number of days a new user account has until that account expires. + \item User chooses the save button to persist the values to the server or chooses another tab to make changes + \item A confirmation box will be displayed to the user letting them know the changes wont take effect until the server restarts + \item Upon clicking yes, the dialog boxes will disappear and a success message will be displayed + \end{enumerate} +\end{usecase} + +\subsection{Database General Configuration} + +Database general configuration tells the server which information to use to connect to the database with. + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/config-db.png} + \caption{Database General Configuration} + \label{fig:db-config} +\end{figure} + +\begin{usecase} + {EDIT DATABASE GENERAL CONFIGURATION} + {Authenticated User with change configuration permission} + {The user is viewing the Database General Configuration Tab within the configuration settings. Reference figure~\ref{fig:db-config}} + \begin{enumerate} + \item User enters the database host information. This is either the url or IP Address of the database server + \item User enters the database port information. This is the port the database is listening on for connections + \item User enters the database username. This is the user that only has permission to access the portal database \newline + Note: Under normal circumstances, this user shouldn't change as it is set during database migration. Only time this should be changed is if the migration changes it. + \item User enters the database password. \newline + Note: Under normal circumstances, this password shouldn't change as it is set during database migration. Only time this should be changed is if the migration changes it. + \item User chooses the save button to persist the values to the server or chooses another tab to make changes + \item A confirmation box will be displayed to the user letting them know the changes wont take effect until the server restarts + \item Upon clicking yes, the dialog boxes will disappear and a success message will be displayed + \end{enumerate} +\end{usecase} + +\subsection{Database Root Configuration} + +Database root configuration shows the root database user and password (the password is encrypted). This configuration is used for the flyway migrations only. + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/config-root.png} + \caption{Database Root Configuration} + \label{fig:db-root} +\end{figure} + +\begin{usecase} + {EDIT DATABASE ROOT CONFIGURATION} + {Authenticated User with change configuration permission} + {The user is viewing the Database Root Configuration Tab within the configuration settings. Reference figure~\ref{fig:db-root}} + \begin{enumerate} + \item User enters the database root username. \newline + Note: Under normal circumstances, this user shouldn't change. Only time this should be changed is if the root db user changes. + \item User enters the database root password. This password is stored encrypted into the configuration file\newline + Note: Under normal circumstances, this password shouldn't change. Only time this should be changed is if the root db password changes. + \item User chooses the save button to persist the values to the server or chooses another tab to make changes + \item A confirmation box will be displayed to the user letting them know the changes wont take effect until the server restarts + \item Upon clicking yes, the dialog boxes will disappear and a success message will be displayed + \end{enumerate} +\end{usecase} \ No newline at end of file diff --git a/docs/src/guides/CredTab.tex b/docs/src/guides/CredTab.tex new file mode 100644 index 0000000..b6d7aab --- /dev/null +++ b/docs/src/guides/CredTab.tex @@ -0,0 +1,136 @@ +\section{Credentials Tab} +\label{cred-tab} +The credentials tab lists all the available credentials that can be used when executing a script or git repository checkout. + +The rest of this section describes how to perform various actions on the Credentials Tab. See Figure~\ref{fig:cred-tab} + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/cred-tab.png} + \caption{Credentials Tab} + \label{fig:cred-tab} +\end{figure} + +\subsection{Add Credentials} +The following case allows the user to add new credentials. + +\begin{figure}[htbp] + \centering + \scalebox{0.5}{\includegraphics*{figures/add-cred.png}} + \caption{Add Credentials} + \label{fig:add-cred} +\end{figure} + +\begin{usecase} + {Add Credentials} + {Authenticated User with add/edit credentials permissions} + {The user is viewing the Credentials Tab} + \begin{enumerate} + \item User clicks on the `Add Credentials' button located top right of the credentials grid (Figure~\ref{fig:cred-tab}) + \item User will be presented with an add credentials dialog box. See figure~\ref{fig:add-cred} + \item User will specify a unique name for the credentials + \item User will specify the credential type from the following: + \begin{itemize} + \item \bold{SSH Key}: Use a private ssh key for authentication. See Section~\ref{sssec:ssh-key} + \item \bold{Username \& Password}: Use a username and password combination for authentication. See Section~\ref{sssec:uname-pass} + \end{itemize} + \item Once all the information is filled in as desired, the user will click the ok button to submit the credentials. + \item Any errors will be presented in an on-screen error describing what the error was. + \item If successfull the dialog box will disappear and the new credentials will be shown in the grid. + \end{enumerate} +\end{usecase} + +Note: The private key, passphrase and password are all encrypted when stored into the database. + +\subsubsection{SSH Key} +\label{sssec:ssh-key} + +The following case allows the user to enter the ssh credentials. + +\begin{figure}[htbp] + \centering + \scalebox{0.5}{\includegraphics*{figures/add-cred-priv.png}} + \caption{Add SSH Credentials} + \label{fig:add-cred-priv} +\end{figure} + +\begin{usecase} + {Add SSH Credentials} + {Authenticated User with add/edit credentials permissions} + {The user is adding new credentials and selected `SSH Key' as the credential type. See Figure~\ref{fig:add-cred-priv}} + \begin{enumerate} + \item User will specify the user identity. This is usually the username the private key belongs to. + \item User will specify the ssh private key. + \item User will specify the passphrase to use for the private key. + \end{enumerate} +\end{usecase} + +\subsubsection{Username \& Password} +\label{sssec:uname-pass} + +The following case allows the user to enter username \& password credentials. + +\begin{figure}[htbp] + \centering + \scalebox{0.5}{\includegraphics*{figures/add-cred-un.png}} + \caption{Add Username \& Password Credentials} + \label{fig:add-cred-un} +\end{figure} + +\begin{usecase} + {Add Username \& Password Credentials} + {Authenticated User with add/edit credentials permissions} + {The user is adding new credentials and selected `Username/Password' as the credential type. See Figure~\ref{fig:add-cred-un}} + \begin{enumerate} + \item User will specify the username. + \item User will specify the password for the user. + \end{enumerate} +\end{usecase} + +\subsection{Edit Credentials} +The following case allows the user to edit exiting credentials. + +\begin{usecase} + {Edit Credentials} + {Authenticated User with add/edit credentials permissions} + {The user is viewing the Credentials Tab} + \begin{enumerate} + \item User clicks on the `Edit' pencil button located in the actions column of the credentials to edit (See Figure~\ref{fig:cred-tab}). + \item User will be presented with an edit credentials dialog box with the information filled out. + \item User can change the credentials name + \item User can change the credentials type. + \begin{itemize} + \item See Section~\ref{sssec:ssh-key} to modify SSH Key + \item See Section~\ref{sssec:uname-pass} to modify Username \& Password + \end{itemize} + \item Once all the information is filled in as desired, the user will click the ok button to submit the changes + \item Any errors will be presented in an on-screen error describing what the error was. + \item If successfull the dialog box will disappear and the edited credentials will be displayed in the grid. + \end{enumerate} +\end{usecase} + +\subsection{Delete Credentials} +The following case allows an user to delete credentials + +\begin{usecase} + {Delete Credentials} + {Authenticated User with delete credentials permissions} + {The user is viewing the Credentials Tab} + \begin{enumerate} + \item User clicks on the `Delete Credentials' trash can button located in the actions column of the credentials to delete (See Figure~\ref{fig:cred-tab}). + \item User will be presented with a confirmation box. + \item If `No' is selected the user will be returned to the credentials grid view + \item If `Yes' is selected the selected credentials will be deleted and the user will be returned to the repository grid view + \item Success or error message will be presented to the user in the top left corner of the script known as a toast message. + \item The user will see the repository removed from the grid view if the repository was successful in being deleted + \end{enumerate} +\end{usecase} + + + + + + + + + + diff --git a/docs/src/guides/EmailTab.tex b/docs/src/guides/EmailTab.tex new file mode 100644 index 0000000..28470b5 --- /dev/null +++ b/docs/src/guides/EmailTab.tex @@ -0,0 +1,85 @@ +\section{Email Template Tab} +\label{email-tab} +The email repository tab shows all the defined email templates that can be used for the scripts. + +The email can have key word fields to dynamically add content to the email. Below is the current list of keywords that can be used: + +\begin{itemize} + \item \command{\$\{script\_name\}} : Will insert the name of the script into the email. + \item \command{\$\{exit\_status\}} : The exit status of the script when it finished execution. + \item \command{\$\{script\_results\}} : Any output from the script when it finished execution. +\end{itemize} + +The email templates can be set in the `Script Email' section (see Section~\ref{sssec:script-email}) and once the script has finished execution, the emails in the list will +be sent out as long as the SMTP configuration is set correctly. + +The rest of this section describes how to perform various actions on the Email Template Tab. See Figure~\ref{fig:email-tab} + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/email-tab.png} + \caption{Email Repository Tab} + \label{fig:email-tab} +\end{figure} + +\subsection{Add Email Template} +The following case allows the user to add a new Email Template. + +\begin{figure}[htbp] + \centering + \scalebox{0.5}{\includegraphics*{figures/add-email.png}} + \caption{Add Email Template} + \label{fig:add-email} +\end{figure} + +\begin{usecase} + {Add Email Template} + {Authenticated User with add/edit Email Template permissions} + {The user is viewing the Email Template Tab} + \begin{enumerate} + \item User clicks on the `Add New Email Template' button located top right of the Email Template grid (Figure~\ref{fig:email-tab}) + \item User will be presented with an add Email Template dialog box. See figure~\ref{fig:add-email} + \item User will give the template a unique name. + \item User will specify the email subject. + \item User will define the email body. This body can have html elements in it such as \bold{bold}, \italic{italic}, \underline{underline} etc. to give it a more rich look. + \item Once all the information is filled in as desired, the user will click the ok button to submit the Email Template + \item Any errors will be presented in an on-screen error describing what the error was. + \item If successfull the dialog box will disappear and the new email template will be shown in the grid. + \end{enumerate} +\end{usecase} + +\subsection{Edit Email Template} +The following case allows the user to edit an existing Email Template. + +\begin{usecase} + {Edit Email Template} + {Authenticated User with add/edit Email Template permissions} + {The user is viewing the Email Template Tab} + \begin{enumerate} + \item User clicks on the `Edit' pencil button located in the actions column of the template to edit (See Figure~\ref{fig:email-tab}). + \item User will be presented with an edit Email Template dialog box with the information filled out. + \item User can change the template name. + \item User can change the subject name. + \item User can change the email body content. + \item Once all the information is filled in as desired, the user will click the ok button to submit the Email Template + \item Any errors will be presented in an on-screen error describing what the error was. + \item If successfull the dialog box will disappear and the edited template will be shown in the grid. + \end{enumerate} +\end{usecase} + +\subsection{Delete Email Template} +The following case allows an user to delete an email template. + +\begin{usecase} + {Delete Email Template} + {Authenticated User with delete Email Template permissions} + {The user is viewing the Email Template Tab} + \begin{enumerate} + \item User clicks on the `Delete Email Template' trash can button located in the actions column of the template to delete (See Figure~\ref{fig:email-tab}). + \item User will be presented with a confirmation box. + \item If `No' is selected the user will be returned to the template grid view + \item If `Yes' is selected the selected template will be deleted and the user will be returned to the template grid view + \item Success or error message will be presented to the user in the top left corner of the script known as a toast message. + \item The user will see the template removed from the grid view if the repository was successful in being deleted + \end{enumerate} +\end{usecase} + diff --git a/docs/src/guides/EnvVarTab.tex b/docs/src/guides/EnvVarTab.tex new file mode 100644 index 0000000..292868b --- /dev/null +++ b/docs/src/guides/EnvVarTab.tex @@ -0,0 +1,74 @@ +\section{Environment Variables Tab} +\label{envvarTab} +The environment variables tab defines global environment variables that will be set on all script executions. These variables will be added to the local \command{PATH}. +This allows for all scripts to utilize the same enviornment variables without having to set these variables locally on the server for the tomcat user. + +The rest of this section describes how to perform various actions on the the Enviornment Variables Tab. See Figure~\ref{fig:envvar-tab} + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/envvar-tab.png} + \caption{Environment Variables Tab} + \label{fig:envvar-tab} +\end{figure} + +\subsection{Add Environment Variables} +The following case allows a user to add environment variables. + +\begin{figure}[!htp] + \centering + \scalebox{0.5}{\includegraphics*{figures/add-envvar.png}} + \caption{Add Environment Variable} + \label{fig:add-envvar} +\end{figure} + +\begin{usecase} + {ADD ENVIRONMENT VARIABLE} + {Authenticated User with add environment variable permissions} + {The user is viewing the Environment Variables Tab} + \begin{enumerate} + \item The user clicks the 'Add New Environment Variable' button to the right of the Available Environment Variables window + \item A dialog box will appear. See figure~\ref{fig:add-envvar} + \item The user will click on the `plus' (New Environment Variable) sign next to Add New Environment Variables header to add variable. \label{itm:env-param1} + \item The user will enter the variable name (such as \command{JAVA\_HOME}, \command{MAVEN\_HOME} etc.). + \item The User will enter the variable value. \label{itm:env-param2} + \item the User will repeat steps~\ref{itm:env-param1} through~\ref{itm:env-param2} as needed for more variables + \item User will choose the 'ok' button once all the information is filled + \item Upon success, the dialog box will disapear and the new environment variables will be shown in the grid + \end{enumerate} +\end{usecase} + +\subsection{Edit Environment Variables} +The following case allows a user to edit environment variables + +\begin{usecase} + {EDIT ENVIRONMENT VARIABLE} + {Authenticated User with edit environment variable permissions} + {The user is viewing the Environment Variables Tab} + \begin{enumerate} + \item The user will click on the checkbox to the right of the environment variable to edit. To select multiple variables, the user can hold down + the \command{CTRL} keyboard button while selecting. See figure~\ref{fig:envvar-tab}. + \item Once all the variables are selected, the user will click on the `pencil' button to the right of the Available Environment Variables window + \item A dialog box will appear with the selected environment variables. + \item User will edit the variable information accordingly. + \item Upon success, the dialog box will disapear and the edited environment variables will be shown in the grid + \end{enumerate} +\end{usecase} + +\subsection{Delete Environment Variables} +The following case allows a user to delete environment variables + +\begin{usecase} + {DELETE ENVIRONMENT VARIABLE} + {Authenticated User with delete environment variable permissions} + {The user is viewing the Environment Variables Tab} + \begin{enumerate} + \item The user will click on the checkbox to the right of the environment variable to edit. To select multiple variables, the user can hold down + the \command{CTRL} keyboard button while selecting. See figure~\ref{fig:envvar-tab}. + \item Once all the variables are selected, the user will click on the `trash can' button to the right of the Available Environment Variables window + \item A confirmation button will appear asking the user to confirm delete of the selected variables. + \item Upon success, the confirmation box will disappear and the variables will no longer show up in the list of available users. + \end{enumerate} +\end{usecase} + + + diff --git a/docs/src/guides/GitTab.tex b/docs/src/guides/GitTab.tex new file mode 100644 index 0000000..6222877 --- /dev/null +++ b/docs/src/guides/GitTab.tex @@ -0,0 +1,111 @@ +\section{Git Tab} +\label{git-tab} +The git tab shows all the defined git repositories. These repositories are stored locally in the specified location. During startup the server will do a pull request from each defined repository +and then every night ad midnight local time, the server will do another git pull request for the defiend respositories. + +The rest of this section describes how to perform various actions on the Git Tab. See Figure~\ref{fig:git-tab} + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/git-tab.png} + \caption{Git Tab} + \label{fig:git-tab} +\end{figure} + +\subsection{Add Git Repository} +The following case allows the user to add a new git repository. + +\begin{figure}[htbp] + \centering + \scalebox{0.5}{\includegraphics*{figures/add-git.png}} + \caption{Add Git Repository} + \label{fig:add-git} +\end{figure} + +\begin{usecase} + {Add Git Repsitory} + {Authenticated User with add/edit git repository permissions} + {The user is viewing the Git Repository Tab} + \begin{enumerate} + \item User clicks on the `Add Git Repository' button located top right of the git repository grid (Figure~\ref{fig:git-tab}) + \item User will be presented with an add git repository dialog box. See figure~\ref{fig:add-git} + \item User will give the repository a unique name + \item User will specify the GIT SCM\footnote{SCM stands for software configuration management} url or IP address. + \item User will specify the branch to checkout. + \item User will specify the local directory to checkout into. + \item User will select the credentials\footnote{The credentials drop down is prepopulated with the credentials that have been defined in the ``Credentials Tab''. See Section~\ref{cred-tab}} + to use for the repository authentication. + \item Once all the information is filled in as desired, the user will click the ok button to submit the git repository + \item Any errors will be presented in an on-screen error describing what the error was. + \item If successfull the dialog box will disappear and the new repository will be shown in the grid. + \end{enumerate} +\end{usecase} + +Note: Once a git repository has been submitted, the server will try to check it out into the directory specified. Any errors in checkout due to permission issues will be presented to the user. + +\subsection{Edit Git Repository} +The following case allows the user to edit an existing git repository. + +\begin{usecase} + {Edit Git Repsitory} + {Authenticated User with add/edit git repository permissions} + {The user is viewing the Git Repository Tab} + \begin{enumerate} + \item User clicks on the `Edit' pencil button located in the actions column of the respitory to edit (See Figure~\ref{fig:git-tab}). + \item User will be presented with an edit git repository dialog box with the information filled out. + \item User can change the repository name + \item User can specify the GIT SCM\footnote{SCM stands for software configuration management} url or IP address. + \item User can specify the branch to checkout. + \item User can specify the local directory to checkout into. + \item User can select the credentials\footnote{The credentials drop down is prepopulated with the credentials that have been defined in the ``Credentials Tab''. See Section~\ref{cred-tab}} + to use for the repository authentication. + \item Once all the information is filled in as desired, the user will click the ok button to submit the git repository + \item Any errors will be presented in an on-screen error describing what the error was. + \item If successfull the dialog box will disappear and the edited repository will be shown in the grid. + \end{enumerate} +\end{usecase} + +Note: Once the git repository edits have been submitted, the server will try to either change the branch or checkout in a new directory if the checkout directory was changed. +Any errors in checkout due to permission issues will be presented to the user. + +\subsection{Manual Pull Request} +The following case allows the user to issue a pull request. + +\begin{usecase} + {Manual Pull Request} + {Authenticated User with pull git repository permissions} + {The user is viewing the Git Repository Tab} + \begin{enumerate} + \item User clicks on the `Issue Pull Request' play button located in the actions column of the respitory to edit (See Figure~\ref{fig:git-tab}). + \item User will be presented with a confirmation box. + \item If `No' is selected the user will be returned to the repository grid view + \item If `Yes' is selected the selected a pull request will be submitted and the user will be returned to the repository grid view + \item Success or error message will be presented to the user in the top left corner of the script known as a toast message. + \end{enumerate} +\end{usecase} + +\subsection{Delete Repository} +The following case allows an user to delete a repository + +\begin{usecase} + {Delete Repository} + {Authenticated User with delete git repository permissions} + {The user is viewing the Git Repository Tab} + \begin{enumerate} + \item User clicks on the `Delete Repository' trash can button located in the actions column of the respitory to delete (See Figure~\ref{fig:git-tab}). + \item User will be presented with a confirmation box. + \item If `No' is selected the user will be returned to the repository grid view + \item If `Yes' is selected the selected repository will be deleted and the user will be returned to the repository grid view + \item Success or error message will be presented to the user in the top left corner of the script known as a toast message. + \item The user will see the repository removed from the grid view if the repository was successful in being deleted + \end{enumerate} +\end{usecase} + + + + + + + + + + diff --git a/docs/src/guides/LogLevel.tex b/docs/src/guides/LogLevel.tex new file mode 100644 index 0000000..60965ad --- /dev/null +++ b/docs/src/guides/LogLevel.tex @@ -0,0 +1,59 @@ +\section{Log Level Settings} +\label{logLevel} + +The server utilizes loggers that logs various server events. These log are all stored in a log file on the server. If using \command{tomcat} server these logs are located at \filename{\$TOMCAT\_HOME/logs/portal.out}. + +Each application component has a logger associated with it with the default log level of \command{INFO} which will show general information to highlight progress. Each log level has an assigned level of importants with \command{OFF} being the most important and \command{ALL} being the least important. When determining what the log, the event to be logged compares itself to the logger and what its level is set at. If the event being logged is less important than the current log level for the component, that event will be ignored. If it is greater than or equal to the current level, the event will be logged. + +The level of importances from greatest to least is + +\command{OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL}. See Table~\ref{tab:logLevels} of explanation of each level +\begin{table}[!htb] + \centering + \caption{Log Level Description \label{tab:logLevels}} + \begin{tabular}{| r | p{7cm} |} \hline + Level & Description \\ \hline + ALL & All levels including custom levels. \\ \hline + TRACE & Designates finer-grained informational events than the DEBUG. \\ \hline + DEBUG & Designates fine-grained informational events that are most useful to debug an application. \\ \hline + INFO & Designates informational messages that highlight the progress of the application at coarse-grained level. \\ \hline + WARN & Designates potentially harmful situations. \\ \hline + ERROR & Designates error events that might still allow the application to continue running. \\ \hline + FATAL & Designates very severe error events that will presumably lead the application to abort. \\ \hline + OFF & The highest possible rank and is intended to turn off logging. \\ \hline + \end{tabular} +\end{table} + +\subsection{Changing log levels} +The following case allows a user to change the log level for server components. Upon saving the log levels, the component will now respects its new level and log according. + +NOTE: These values are presisted. Upon server restart, the server will see if there is an entry for the logger and set the log level according, otherwise; it will set it to the default value. The location for the persisted patch repo logging file is \filename{\$TOMCAT\_HOME/conf/portal-loggers.properties}. + +When displaying the log levels, the levels are displayed in alphabetical order based off the component name + +\begin{figure}[!htb] + \centering + \scalebox{0.75}{\includegraphics*{figures/menu.png}} + \caption{Menu} + \label{fig:menu} +\end{figure} + + \begin{figure}[!htp] + \centering + \scalebox{0.75}{\includegraphics*{figures/log-levels.png}} + \caption{Log Levels} + \label{fig:log-levels} +\end{figure} + +\begin{usecase} + {CHANGE LOG LEVELS} + {Authenticated User with change log level permission} + {The user is viewing the log levels after selecting it from the menu. Reference figure~\ref{fig:hash-alg}} + \begin{enumerate} + \item User pages through the tabs to find the component to change the log level + \item User chooses which level to set the log to (see table~\ref{tab:logLevels}) + \item User selects save to submit the change + \item A confirmation box will be presented to the user to confirm submission + \item Upon success the dialog boxes will disappear and a success message will be displayed to the user + \end{enumerate} +\end{usecase} diff --git a/docs/src/guides/Login.tex b/docs/src/guides/Login.tex new file mode 100644 index 0000000..4941d0c --- /dev/null +++ b/docs/src/guides/Login.tex @@ -0,0 +1,27 @@ +\section{Login} +The following case allows the user to authenticate with the server. See Figure~\ref{fig:login} + +\begin{figure}[!htp] + \centering + \scalebox{0.5}{\includegraphics*{figures/login.png}} + \caption{Login} + \label{fig:login} +\end{figure} + +\begin{usecase} + {LOGIN} + {Any User} + {The user navigated to the host url and the user has not logged in yet.} + \begin{enumerate} + \item User enters account information into the `Username' field. + \item User enters password into the `Password' field. + \item A spinner will appear indicating the client is trying to autenticate to the user. + \item If successful, the state of the client is changed to the tabular pages the user has permissions to view + \end{enumerate} +\end{usecase} + +The default username is: \bold{\command{portalAdmin@ngc.com}} + +The default password is: \bold{\command{portalAdmin2017!}} + +Note: This is the initial administrator login. It is highly suggested and encouraged to add a new user (see section~\ref{userTab}) into the admin role and disable or change the password of the default user \ No newline at end of file diff --git a/docs/src/guides/PermissionTab.tex b/docs/src/guides/PermissionTab.tex new file mode 100644 index 0000000..0513358 --- /dev/null +++ b/docs/src/guides/PermissionTab.tex @@ -0,0 +1,98 @@ +\section{Permissions Tab} +\label{permissionTab} + +The permissions tab defines permission groups that can be assigned to users as roles. These groups define which actions/views the users with the group can perform/see. + +This section describes how to perform various actions on the Permissions Tab. See Figure~\ref{fig:perm-tab} + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/permission-tab.png} + \caption{Permission Tab} + \label{fig:perm-tab} +\end{figure} + +\subsection{Add Permission Group} + +The following case allows a user to add a new permission group + +\begin{figure}[!htp] + \centering + \scalebox{0.4}{\includegraphics*{figures/add-perm.png}} + \caption{Add Permissions} + \label{fig:add-perm} +\end{figure} + +\begin{usecase} + {ADD PERMISSION GROUP} + {Authenticated User with add/edit permissions permission} + {The user is viewing the Permissions Tab} + \begin{enumerate} + \item The user clicks the 'Add Permissions' button to the right of the permissions window. See figure~\ref{fig:perm-tab} + \item A dialog box will appear. See figure~\ref{fig:add-perm} + \item User will enter a unique name for the permission group + \item User will enter a display name for the permission group. This will be what is displayed in the list + \item User can opt to enable or disable the permission group + \item User can choose which tabs this group has access to view by clicking on the switch. \newline + If the switch color is \Blue{Blue} the selected permission is enabled \newline + if the switch color is \Gray{Gray} the selected permission is disabled + \item User can cycle through the permission tabs to enable or disable various options + \begin{itemize} + \item \bold{Tabs}: Set which tabs are visible for the user + \item \bold{Scripts Tab}: Enable/disable user actions on the \command{Scripts Tab} + \item \bold{Git Tab}: Enable/disable user actions on the \command{Git Tab} + \item \bold{Credentials Tab}: Enable/disable user actions on the \command{Credentials Tab} + \item \bold{Email Tab}: Enable/disable user actions on the \command{Email Tab} + \item \bold{User Tab}: Enable/disable user actions on the \command{User Tab} + \item \bold{Permissions Tab}: Enable/disable user actions on the \command{Permissions Tab} + \item \bold{Enviornment Variables Tab}: Enable/disable user actions on the \command{Enviornment Variables Tab} + \item \bold{Log Tab}: Enable/disable user actions on the \command{System Logs Tab} + \item \bold{Configuration}: Enable/disable user actions for viewing/setting system configuration values + \end{itemize} + \item User will select ok once the desired options are selected + \item Upon success the dialog box will disappear and the new permission group will be displayed in the permissions list + \end{enumerate} +\end{usecase} + +\subsection{Edit Permission Group} + +The following case allows a user to edit a permission group + +\begin{figure}[!htp] + \centering + \scalebox{0.4}{\includegraphics*{figures/edit-perm.png}} + \caption{Edit Permissions} + \label{fig:edit-perm} +\end{figure} + +\begin{usecase} + {EDIT PERMISSION GROUP} + {Authenticated User with add/edit permissions permission} + {The user is viewing the Permissions Tab} + \begin{enumerate} + \item The user clicks on the 'pencil' button in the \command{Actions} column next to the permission group to edit. See figure~\ref{fig:perm-tab} + \item A dialog box will appear. See figure~\ref{fig:edit-perm} + \item User can opt to change the permission group name + \item User can opt to enable to disable the permission group. + \item User can choose which tabs this group has access to view by clicking on the switch. \newline + If the switch color is \Blue{Blue} the selected permission is enabled \newline + if the switch color is \Gray{Gray} the selected permission is disabled + \item User can cycle through the permission tabs to enable or disable various options + \item User will select ok once the desired options are selected + \item Upon success the dialog box will disappear and the permission group changes will be reflected immediately. + \end{enumerate} +\end{usecase} + +\subsection{Delete Permission Group} +The following case allows a user to delete an existing permission group + +\begin{usecase} + {DELETE PERMISSION GROUP} + {Authenticated User with delete permissions permissions} + {The user is viewing the Permissions Tab} + \begin{enumerate} + \item The user clicks on the 'trash can' button in the \command{Actions} column next to the permission group to delete. See figure~\ref{fig:perm-tab} + \item A confirmation button will appear asking the user to confirm delete. + \item Upon success, the confirmation box will disappear and the permission group will no longer show up in the list of available users.\newline + Note: If any user belongs to this group, the delete will fail saying as such. All users need to be relocated to a new group before permission groups can be removed. + \end{enumerate} +\end{usecase} \ No newline at end of file diff --git a/docs/src/guides/SMTP.tex b/docs/src/guides/SMTP.tex new file mode 100644 index 0000000..b19d2dd --- /dev/null +++ b/docs/src/guides/SMTP.tex @@ -0,0 +1,101 @@ +\section{SMTP Settings} +\label{smtp} + +The SMTP configuration (located in the menu drop down to right hand corner. See figure~\ref{fig:menu}) controls sending emails for scripts that have email templates associated with them. + +\begin{figure}[!htb] + \centering + \scalebox{0.75}{\includegraphics*{figures/menu.png}} + \caption{Menu} + \label{fig:menu} +\end{figure} + +\subsection{General Configuration} + +General configuration tells the application server how to connect to the mail server. + +\begin{figure}[!htp] + \scalebox{0.75}{\includegraphics*{figures/smtp-gen.png}} + \caption{General Configuration} + \label{fig:smtp-gen} +\end{figure} + +\begin{usecase} + {EDIT GENERAL CONFIGURATION} + {Authenticated User with change smtp permission} + {The user is viewing the General Configuration Tab within the smtp settings. Reference figure~\ref{fig:smtp-gen}} + \begin{enumerate} + \item User will indicate if SMTP is enabled by clicking on the `SMTP Enabled' Switch.\newline + \Blue{Blue} indicates that SMTP is enabled while \Gray{Gray} indicates it is disabled. + \item User will enter the mail protocol to use (defaulted to SMTP). + \item User will enter the mail servers IP address or URL + \item User will enter the port to connect on + \item User will indicate if log debugging should be enabled. This can help debug issues that prevent emails from sending. + \item User chooses the save button to persist the values to the server or chooses another tab to make changes + \item A confirmation box will be displayed to the user letting them know the changes wont take effect until the server restarts + \item Upon clicking yes, the dialog boxes will disappear and a success message will be displayed + \end{enumerate} +\end{usecase} + +\subsection{Start TLS Configuration} + +Start TLS Configuration indicates if the application should use TLS when sending an email through the mail server. + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/smtp-tls.png} + \caption{Start TLS Configuration} + \label{fig:start-tls} +\end{figure} + +\begin{usecase} + {EDIT START TLS CONFIGURATION} + {Authenticated User with change smtp permission} + {The user is viewing the Start TLS Configuration Tab within the SMTP settings. Reference figure~\ref{fig:start-tls}} + \begin{enumerate} + \item User will indicate if Enable Start TLS is enabled by clicking on the `Enable Start TLS' Switch.\newline + \Blue{Blue} indicates that SMTP is enabled while \Gray{Gray} indicates it is disabled. + \item User will indicate if TLS is required clicking on the `Require Start TLS' Switch.\newline + \Blue{Blue} indicates that SMTP is enabled while \Gray{Gray} indicates it is disabled. + \item User will set which SSL Trust Certificates to use. This is the full path to the trust certificate. By default `*' is used to trust all certificiates. + \item User chooses the save button to persist the values to the server or chooses another tab to make changes + \item A confirmation box will be displayed to the user letting them know the changes wont take effect until the server restarts + \item Upon clicking yes, the dialog boxes will disappear and a success message will be displayed + \end{enumerate} +\end{usecase} + +\subsection{Auth Configuration} + +Auth configuration tab stores information whether authentication is needed to send emails. + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/smtp-auth.png} + \caption{Auth Configuration} + \label{fig:smtp-auth} +\end{figure} + +\begin{usecase} + {AUTH CONFIGURATION} + {Authenticated User with change smtp permission} + {The user is viewing the Auth Configuration Tab within the smtp settings. Reference figure~\ref{fig:smtp-auth}} + \begin{enumerate} + \item User will indicate if authentication is required by clicking on the `Require Authentication' Switch.\newline + \Blue{Blue} indicates that SMTP is enabled while \Gray{Gray} indicates it is disabled. + \item User will indicate the default `From User' that will be in the email \command{from} line. + \item User will input the SMTP username to authenticate with + \item User will input the password for authentication + \item A confirmation box will be displayed to the user letting them know the changes wont take effect until the server restarts + \item Upon clicking yes, the dialog boxes will disappear and a success message will be displayed + \end{enumerate} +\end{usecase} + + + + + + + + + + + + diff --git a/docs/src/guides/ScriptTab.tex b/docs/src/guides/ScriptTab.tex new file mode 100644 index 0000000..6d7fb7c --- /dev/null +++ b/docs/src/guides/ScriptTab.tex @@ -0,0 +1,413 @@ +\section{Scripts Tab} +\label{script-tab} + +The Scripts Tab shows all the defined scripts (with or without schedules). Each script can be expanded to show any jobs that have been ran. + +The subgrid is broken down into 8 sections: + +\begin{itemize} + \item \bold{JobId}: Unique identifier for the scheduled job + \item \bold{Start Date/Time}: When the scheduled job task started + \item \bold{End Date/Time}: When the scheduled job task will finish + \item \bold{Frequency}: How often the scheduled job task will trigger + \item \bold{Max Executions}: Number of executions the schedule job task can perform before it will stop + \item \bold{Current Count}: Number of times the scheduled job task has triggered. + \item \bold{Last Ran}: Last time the scheduled job task ran. + \item \bold{Status}: Exit status of the last time the schedule job task ran (typically either success or failed) +\end{itemize} + +The last column are two actions that the user can perform: view and delete. These will be discussed in later sections. + +The rest of this section describes how to perform various actions on the Scripts Tab. See Figure~\ref{fig:scripts-tab} + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/scripts-tab.png} + \caption{Scripts Tab} + \label{fig:scripts-tab} +\end{figure} + +\subsection{Add Script} +The following case allows the user to add a new script. + +\begin{figure}[!htp] + \centering + \includegraphics*[width=\linewidth]{figures/add-script.png} + \caption{Create Script} + \label{fig:create-script} +\end{figure} + +\begin{usecase} + {Add Script} + {Authenticated User with create script permissions} + {The user is viewing the Script Tab} + \begin{enumerate} + \item User clicks on the `Add New Script' button located top right of the script grid (Figure~\ref{fig:scripts-tab}) + \item User will be presented with an add new script dialog box. See figure~\ref{fig:create-script} + \item User will give the script a unique name. + \item User will choose tye type of script to create (see subsections for more parameters for each type) + \begin{enumerate} + \item \bold{Local Execution}: Script will run locally on the host machine. See section~\ref{sssec:local-execution} + \item \bold{Remote Execution}: The script will run the commands on a remote machine. See section~\ref{sssec:remote-execution} + \item \bold{Secure Copy}: The script will copy files to/from a server to anoother. See section~\ref{sssec:secure-copy} + \end{enumerate} + \end{enumerate} +\end{usecase} + +\begin{usecase} + {Add Script continued} + {Authenticated User with create script permissions} + {The user is viewing `Add New Script' dialog box} + \begin{enumerate} + \setcounter{enumi}{4} + \item User will fill out any commands/parameters associated with the script (not present for scp). See section~\ref{sssec:params} + \item User will indicate if there are any follow on scripts to run. See section~\ref{sssec:script-chain} + \item User will add any environment variables that are needed for the script (not present for scp). See section~\ref{sssec:script-env} + \item User will add any emails to be sent once the script finishes. See section~\ref{sssec:script-email}\newline + This option will only be available if SMTP is enabled. See Section~\ref{smtp} + \item User will add any schedulers associated with the script. See section~\ref{sssec:script-schedule} + \item Once all the information is filled in as desired, the user will click the ok button to submit the new script + \item Any errors will be presented in an on-screen error describing what the error was. + \item If successfull the dialog box will disappear and the new script will be shown in the grid. + \end{enumerate} +\end{usecase} + +\subsubsection{Local Execution} +\label{sssec:local-execution} +The following case is for ``Local Execution'' script type. + +\begin{figure}[!htp] + \centering + \includegraphics*[width=\textwidth]{figures/script-local.png} + \caption{Local Execution Script} + \label{fig:local-execution} +\end{figure} + +\begin{usecase} + {Local Execution} + {Authenticated User with create script permissions} + {The user is adding a new script and selected `Local Execution` as the script type. See Figure~\ref{fig:local-execution}} + \begin{enumerate} + \item User will input the command to execute locally + \item User specifies which local directory the script should start it before execution + \item User can specify a local output directory to output any logs/files to during execution + \end{enumerate} +\end{usecase} + +\subsubsection{Remote Execution} +\label{sssec:remote-execution} +The following case is for ``Remote Execution'' script type. + +\begin{figure}[!htp] + \centering + \includegraphics*[width=\textwidth]{figures/script-remote.png} + \caption{Remote Execution Script} + \label{fig:remote-execution} +\end{figure} + +\begin{usecase} + {Remote Execution} + {Authenticated User with create script permissions} + {The user is adding a new script and selected `Remote Execution` as the script type. See Figure~\ref{fig:remote-execution}} + \begin{enumerate} + \item User will select the credentials\footnotemark\ to use for the remote execution. + \item User specifies the remote url or IP address. + \item User specifies the port to connect on + \item User specifies which remote directory the script should start it before execution + \item User can specify a remote output directory to output any logs/files to during execution + \end{enumerate} +\end{usecase} + +\subsubsection{Secure Copy} +\label{sssec:secure-copy} +The following case is for ``Secure Copy'' script type. + +\begin{figure}[!htp] + \centering + \includegraphics*[width=\textwidth]{figures/script-scp.png} + \caption{Secure Copy Script} + \label{fig:secure-copy} +\end{figure} + +\begin{usecase} + {Secure Copy} + {Authenticated User with create script permissions} + {The user is adding a new script and selected `Secure Copy' as the script type. See Figure~\ref{fig:secure-copy}} + \begin{enumerate} + \item User clicks `From Local` if the file to send is stored locally. + \item User will proceed to step~\ref{itm:end-not-from-local} if `From Local` was chosen + \item User will specify the remote url or IP address of the machine to retrieve the file from + \item User will specify the remote port of the machine to retrieve the file from + \item User will select the credentials\textsuperscript{\ref{note1}} to use to connect to the `from' machine. + \item \label{itm:end-not-from-local} User clicks `To Local' if the file being retrieved is going to be stored locally. + \item User will proceed to step~\ref{itm:end-not-to-local} if `To Local` was chosen + \item User will specify the remote url or IP address of the machine to send the file to + \item User will specify the remote port of the machine to send the file to + \item User will select the credentials\textsuperscript{\ref{note1}} to use to connect to the `to' machine. + \item \label{itm:end-not-to-local} User will specify the file name or a regular expression for the file to retrieve + \item User can specify the base directory on the `From' Machine to find the file(s) in. + \item User can specify the output directory to store the retrieved files in either locally or on the remote `To` machine + \end{enumerate} +\end{usecase} +\footnotetext{\label{note1} The credentials drop down is prepopulated with the credentials that have been defined in the ``Credentials Tab''. See Section~\ref{cred-tab}} + +\subsubsection{Parameters/Commands} +\label{sssec:params} +The following case is for setting parameters (if script type is set to local execution) or commands (if the script type is set to remote execution). + +When the script type is set to `local execution' values entered into the parameters section will be parameters that are added to the \command{Command} that was specified. +For example if the user specifies \command{mvn} as the \command{Command}, he/she can then add \command{clean} and \command{install} into the parameter section. Then when +the script runs the program will couple them all together and execute the command \command{mvn clean install}. + +When the script type is set to `remote execution` values entered into the \command{Commands} section will be the commands that are executed on the remote system. +For example, if the user specifies a command of \command{ps -ef |grep tomcat > tomcat-status.txt} then specifies a command of \command{cat tomcat-status.txt}, upon execution +the program will couple the commands all together (in the order specified) as \command{ps -ef |grep tomcat > tomcat-status.txt; cat tomcat-status.txt}. + +Note: The `Parameters' section will change to `Commands' when switching from local execution to remote execution. + +\begin{figure}[!htp] + \centering + \includegraphics*[width=\textwidth]{figures/script-params.png} + \caption{Script Parameters} + \label{fig:script-param} +\end{figure} + +\begin{usecase} + {Parameters/Commands} + {Authenticated User with create script permissions} + {The user is viewing `Add New Script' dialog box} + \begin{enumerate} + \item The user selects the `New Parameter/Command` sign in the `Parameters/Commands` section. See Figure~\ref{fig:script-param} \label{itm:pc-param1} + \item User will add the parameter/command in the text field. \label{itm:pc-param2} + \item User will repeat steps~\ref{itm:pc-param1} and~\ref{itm:pc-param2} as needed. + \item User can use the up and down arrows to rearrange the order of inclusion for parameters or execution for commands. + \item User can select the `trash can' to remove parameters/commands from the list + \end{enumerate} +\end{usecase} + +\subsubsection{Script Chain} +\label{sssec:script-chain} +The following case is for setting script chains. + +Script chains allows for follow on scripts to execute after a script completes with a specified status code. The user can specify how many minutes after completion will the next script execute. + +Only already created scripts can be chosen from the drop down. If another script needs to be created for a follow on action, the user will have to create it first. + +Note: It is possible to create circular chaining where one script calls another which in turn calls the original script. This can cause an indefinite loop so great care should be taken to insure +that no circular chaining happens. + +\begin{figure}[!htp] + \centering + \includegraphics*[width=\textwidth]{figures/script-chain.png} + \caption{Script Chain} + \label{fig:script-chain} +\end{figure} + +\begin{usecase} + {Script Chain} + {Authenticated User with create script permissions} + {The user is viewing `Add New Script' dialog box} + \begin{enumerate} + \item The user selects the `New Script Chain' sign in the `Script Chain' section. See Figure~\ref{fig:script-chain} \label{itm:sc-param1} + \item User will select the follow on script from the drop down. + \item User will input the exit status that will trigger the script. This exit status is the exit status of the parent script. + \item User will set the number of minutes after the parent script finishes before the child script executes. Setting nothing will cause the script to execute immediately + provided the exit status is correct. \label{itm:sc-param2} + \item User will repeat steps~\ref{itm:sc-param1} through~\ref{itm:sc-param2} as needed. + \item User can use the up and down arrows to rearrange the order script chains execute. + \item User can select the `trash can' to remove follow on scripts from the list. + \end{enumerate} +\end{usecase} + +\subsubsection{Script Environment Variables} +\label{sssec:script-env} +The following case is for setting environment variables specifically for the script. + +The enviornment variables are typically variables that have to be set before the script can be executed. The program will automatically load any system environment variables and any variables specified in the +global variables set in the ``Environment Variables'' tab. The ones defined here are typically ones that are unique for the script itself. + +\begin{figure}[!htp] + \centering + \includegraphics*[width=\textwidth]{figures/script-env.png} + \caption{Script Enviornment Variables} + \label{fig:script-env} +\end{figure} + +\begin{usecase} + {Script Environment Variables} + {Authenticated User with create script permissions} + {The user is viewing `Add New Script' dialog box} + \begin{enumerate} + \item The user selects the `New Script Environment Variable' sign in the `Script Environment Variables' section. See Figure~\ref{fig:script-env} \label{itm:sev-param1} + \item User will define the variable name. + \item User will define the variable value. \label{itm:sev-param2} + \item User will repeat steps~\ref{itm:sev-param1} through~\ref{itm:sev-param2} as needed. + \item User can use the up and down arrows to rearrange the order of the variables. + \item User can select the `trash can' to remove the variables from the list. + \end{enumerate} +\end{usecase} + +\subsubsection{Script Email} +\label{sssec:script-email} +The following case is for setting script emails. + +Script emails can be selected to be sent out after the script has finished execution. + +\begin{figure}[!htp] + \centering + \includegraphics*[width=\textwidth]{figures/script-email.png} + \caption{Script Email} + \label{fig:script-email} +\end{figure} + +\begin{usecase} + {Script Email} + {Authenticated User with create script permissions} + {The user is viewing `Add New Script' dialog box} + \begin{enumerate} + \item The user selects the `New Script Email' sign in the `Script Email' section. See Figure~\ref{fig:script-email} \label{itm:se-param1} + \item User will select the email template for use. The drop down is populated with the email templates that are defuned in the ``Email Template'' tab. + \item User will click the `plus' sign next to the template name to select the recipients. The recipients drop down are populated with the users as defined in the ``Users'' tab. + \item Once the user has included all the recipients they wish to send the email to, the user will click on the `check' button to confirm selection \label{itm:se-param2} + \item User will repeat steps~\ref{itm:se-param1} through~\ref{itm:se-param2} as needed. + \item User can select the `trash can' to remove emails from the list. + \end{enumerate} +\end{usecase} + +\subsubsection{Script Schedule} +\label{sssec:script-schedule} +The following case is for setting the script schedule. + +The script schedule dictates when the script will automatically run. The user has the ability to select from the following frequencies: +\begin{itemize} + \item Hourly + \item Daily + \item Weekly + \item Monthly + \item Yearly + \item Specific +\end{itemize} + +Note: If the user chooses the ``Specific'' frequency, the script is executed at a specific date/time as specified and only executes once. + +\begin{figure}[!htp] + \centering + \includegraphics*[width=\textwidth]{figures/script-schedule.png} + \caption{Script Schedule} + \label{fig:script-schedule} +\end{figure} + +\begin{usecase} + {Script Schedule} + {Authenticated User with create script permissions} + {The user is viewing `Add New Script' dialog box} + \begin{enumerate} + \item The user selects the `New Schedule' sign in the `Schedule' section. See Figure~\ref{fig:script-schedule} \label{itm:ss-param1} + \item User will select how often the schedule will trigger from the `Frequency' drop down. + \item User will set the start date and time the script will begin. + \item User can set the end date and time to specify when the script will stop executing. + \item User can set the number of executions the script will perform before it stops executing. \label{itm:ss-param2} + \item User will repeat steps~\ref{itm:ss-param1} through~\ref{itm:ss-param2} as needed. + \item User can use the up and down arrows to rearrange the schedules. + \item User can select the `trash can' to remove emails from the list. + \end{enumerate} +\end{usecase} + +Note: If no end date/time or execution count is specified the script will run indefinitely until changed. + +\subsection{Edit Script} + +The following case allows an user to edit a script. + +\begin{usecase} + {Edit Script} + {Authenticated User with create script permissions} + {The user is viewing the Script Tab} + \begin{enumerate} + \item User clicks on the `Edit Script' pencil button to the right of the script the user wishes to edit. (See Figure~\ref{fig:scripts-tab}) + \item User will be presented with an edit script dialog box prepopulated with the scripts information. + \item User will modify the information as needed: + \begin{itemize} + \item See subsection~\ref{sssec:local-execution} to modify location execution information + \item See subsection~\ref{sssec:remote-execution} to modify remote execution information + \item See subsection~\ref{sssec:secure-copy} to modify secure copy information + \item See subsection~\ref{sssec:params} to modify parameters/commands information + \item See subsection~\ref{sssec:script-chain} to modify script chain information + \item See subsection~\ref{sssec:script-env} to modify script environment variable information + \item See subsection~\ref{sssec:script-email} to modify script email information + \item See subsection~\ref{sssec:script-schedule} to modify script schedule information + \end{itemize} + \item Once all the information is modified as desired, the user will click the ok button to submit the edited script. + \item Any errors will be presented in an on-screen error describing what the error was. + \item If successfull the dialog box will disappear and the changest will be shown in the grid. + \end{enumerate} +\end{usecase} + +\subsection{Delete Script} +The following case allows an user to delete a script + +\begin{usecase} + {Delete Script} + {Authenticated User with delete script permissions} + {The user is viewing the Script Tab} + \begin{enumerate} + \item User clicks on the `Delete Script' trash can button to the right of the script the user wishes to delete. (See Figure~\ref{fig:scripts-tab}) + \item User will be presented with a confirmation box. + \item If `No' is selected the user will be returned to the script grid view + \item If `Yes' is selected the selected script will be deleted and the user will be returned to the script grid view + \item Success or error message will be presented to the user in the top left corner of the script known as a toast message. + \item The user will see the script removed from the grid view if the script was successful in being deleted + \end{enumerate} +\end{usecase} + +\subsection{View Script Status} +The following case allows an user to view the status of a script that has ran + +\begin{usecase} + {View Script Status} + {Authenticated User with view script status permissions} + {The user is viewing the Script Tab} + \begin{enumerate} + \item User clicks on the `expand button (plus sign)' to the left of the script to view(See Figure~\ref{fig:scripts-tab}). + This will expand the detail and show script jobs that have been ran. + \item User will click on the `View Status Results' (eye) icon to the right in the actions column next to the job to view the action column. + \item A dialog box will appear displaying the results. See Figure~\ref{fig:script-status} + \item User can expand each result to view the details. See Figure~\ref{fig:script-status-results} + \item The user will click on the cancel button to close the dialog box + \end{enumerate} +\end{usecase} + +\begin{figure}[!htp] + \centering + \includegraphics*[width=\textwidth]{figures/script-status.png} + \caption{Script Status} + \label{fig:script-status} +\end{figure} + +\begin{figure}[!htp] + \centering + \includegraphics*[width=\textwidth]{figures/script-status-results.png} + \caption{Script Status Results} + \label{fig:script-status-results} +\end{figure} + +\subsection{Delete Script Schedule} +The following case allows an user to delete script schedules + +\begin{usecase} + {Delete Script Schedule} + {Authenticated User with delete script schedule permissions} + {The user is viewing the Script Tab} + \begin{enumerate} + \item User clicks on the `expand button (plus sign)' to the left of the script to view(See Figure~\ref{fig:scripts-tab}). + \item User will click on the trashcan icon to the right in the actions column next to the job to view the action column. + \item User will be presented with a confirmation box. + \item If `No' is selected the user will be returned to the script grid view + \item If `Yes' is selected the selected script schedule will be deleted and the user will be returned to the script grid view + \item Success or error message will be presented to the user in the top left corner of the script known as a toast message. + \item The user will see the script schedule removed from the grid view if the script schedule was successful in being deleted + \end{enumerate} +\end{usecase} + + + + diff --git a/docs/src/guides/SystemLogTab.tex b/docs/src/guides/SystemLogTab.tex new file mode 100644 index 0000000..1ee3595 --- /dev/null +++ b/docs/src/guides/SystemLogTab.tex @@ -0,0 +1,43 @@ +\section{System Log Tab} +\label{systemLog} + +This section describes how to perform various actions on the System Logs Tab. See Figure~\ref{fig:log-tab} + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/log-tab.png} + \caption{System Logs Tab} + \label{fig:log-tab} +\end{figure} + +\subsection{System Tab Overview} +The systems log tab displays events that have happened. There are two types of events \command{INFO} and \command{ERROR}. \command{INFO} events are events that are informational to let the user know what has happened. \command{ERROR} events are errors that happened on the system. + +Only users that have the ability to view the system tab will see the events. The system logs shows who caused the event, the type of event, the event itself, severity level (\command{INFO} or \command{ERROR}), the IP address of the user who logged the event and the time the event happened. + +\subsection{Purge System Logs} +The following case allows a user to purge logs between two dates + +\begin{figure}[!htp] + \centering + \scalebox{0.75}{\includegraphics*{figures/purge-log.png}} + \caption{Purge Log} + \label{fig:purge-logs} +\end{figure} + +\begin{usecase} + {PURGE SYSTEM LOGS} + {Authenticated User with purge logs permissions} + {The user is viewing the System Logs Tab} + \begin{enumerate} + \item The user clicks on the 'trash can' button on the right side of the systems log window. See figure~\ref{fig:log-tab} + \item A dialog box will appear. See figure~\ref{fig:purge-logs} + \item User will enter the purge log start date + \item User will enter the purge log end date + \item User will select ok once the two dates are entered. + \item A confirmation box will appear. The user will select ok to continue. + \item Upon success, the dialog boxes will disappear and the system logs between the two dates will be removed.\newline + Note: Even though the system logs will be removed, the server logs will still retain all information + \end{enumerate} +\end{usecase} + + \ No newline at end of file diff --git a/docs/src/guides/UserTab.tex b/docs/src/guides/UserTab.tex new file mode 100644 index 0000000..91500ac --- /dev/null +++ b/docs/src/guides/UserTab.tex @@ -0,0 +1,76 @@ +\section{User Tab} +\label{userTab} +The user tab defines users who are allowed access to the website. Each user is assigned a role. These roles set certain permissions (see Section~\ref{permissionTab}) and defines which +permissions the user is allowed. + +A user can have a defined start and end date. The start date defines when the user can start logging in and the end date defines when the user will no longer be able to log in. + +The rest of this section describes how to perform various actions on the the User Tab. See Figure~\ref{fig:user-tab} + +\begin{figure}[!htp] + \includegraphics*[width=\linewidth]{figures/user-tab.png} + \caption{User Tab} + \label{fig:user-tab} +\end{figure} + +\subsection{Add User} +The following case allows a user to add another user to the site. + +\begin{figure}[!htp] + \centering + \scalebox{0.5}{\includegraphics*{figures/add-user.png}} + \caption{Add User} + \label{fig:add-user} +\end{figure} + +\begin{usecase} + {ADD USER} + {Authenticated User with add user permissions} + {The user is viewing the User Tab} + \begin{enumerate} + \item The user clicks the 'Add User' button to the right of the user window + \item A dialog box will appear. See figure~\ref{fig:add-user} + \item The user will populate the dialog box accordingly + \begin{itemize} + \item \bold{Email Address}: User's email address, also functions as the username + \item \bold{Password}: Users password + \item \bold{Confirm Password}: Confirm the users password + \item \bold{First Name}: First name of the user + \item \bold{Last Name}: Last name of the user + \item \bold{Permission Group}: Permission group the user belongs to + \item \bold{Effective Date}: Date in which the user can start logging into the application + \item \bold{Expiration Date}: Date in which the user can no longer log into the application + \end{itemize} + \item User will choose the 'ok' button once all the information is filled + \item Upon success, the dialog box will disapear and the new user will be shown in the listing of users + \end{enumerate} +\end{usecase} + +\subsection{Edit User} +The following case allows a user to edit an existing user + +\begin{usecase} + {EDIT USER} + {Authenticated User with edit user permissions} + {The user is viewing the User Tab} + \begin{enumerate} + \item The user clicks on the pencil button in the \command{Actions} column. See figure~\ref{fig:user-tab} + \item A dialog box will appear with the users information populated. + \item User will edit the users information accordingly. + \item Upon success, the dialog disappear and at success message will be presented to the user + \end{enumerate} +\end{usecase} + +\subsection{Delete User} +The following case allows a user to delete an existing user + +\begin{usecase} + {DELETE USER} + {Authenticated User with delete user permissions} + {The user is viewing the User Tab} + \begin{enumerate} + \item The user clicks on the 'trash can' button in the \command{Actions} column. See figure~\ref{fig:user-tab} + \item A confirmation button will appear asking the user to confirm delete. + \item Upon success, the confirmation box will disappear and the user will no longer show up in the list of available users. + \end{enumerate} +\end{usecase} \ No newline at end of file diff --git a/docs/src/macros.tex b/docs/src/macros.tex new file mode 100644 index 0000000..7f5a91a --- /dev/null +++ b/docs/src/macros.tex @@ -0,0 +1,144 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : macros.tex +% SUBJECT : File to hold the macros defined in the portal project. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%------------ +% New Commands +% ------------ + +\newcolumntype{L}[1]{>{\raggedright\let\newline\\\arraybackslash\hspace{0pt}}p{#1}} +\newcolumntype{C}[1]{>{\centering\let\newline\\\arraybackslash\hspace{0pt}}p{#1}} +\newcolumntype{R}[1]{>{\raggedleft\let\newline\\\arraybackslash\hspace{0pt}}p{#1}} + +% Add commands in alphabetical order. +\newcommand{\command}[1]{\texttt{#1}} % For formatting commands. +\newcommand{\filename}[1]{\texttt{#1}} % For formatting file names. +\newcommand{\newterm}[1]{\textit{#1}} % For formatting terms when they are first introduced. +\newcommand{\bold}[1]{\textbf{#1}} % For bold font +\newcommand{\italic}[1]{\textit{#1}} % For italic font +\newcommand{\nn}[0]{\newline\newline} % for double space +\newcommand{\portal}{Portal\xspace} % For formatting ``Portal.'' +\newcommand{\Client}{\portal Client\xspace} +\newcommand{\Server}{\portal Server\xspace} + +\newcommand{\Blue}[1]{\textcolor{blue}{#1}} +\newcommand{\Gray}[1]{\textcolor{gray}{#1}} +\newcommand{\Red}[1]{\textcolor{red}{#1}} + +\newcommand{\Required}[2]{ + \makebox[3.3cm][l]{\texttt{\bold{\Red{#1}}}}: \begin{minipage}[t]{7cm}#2\end{minipage} +} + +\newcommand{\Optional}[2]{ + \makebox[3.35cm][l]{\texttt{\bold{\Gray{#1}}}}: \begin{minipage}[t]{7cm}#2\end{minipage} +} + +\newcommand{\ApiData}[2] { + \begin{tabular}{| C{0.5\textwidth} | C{0.5\textwidth} |} \hline + \bold{Sample Request} & \bold{Sample Response} \\ \hline + #1 & #2 \\ \hline + \end{tabular} +} + +\newcommand{\ngasCommonsVersion}{1.0.0-RELEASE\xspace} +\newcommand{\EclipseVersion}{Oxygen.2 Release (4.7.2)\xspace} +\newcommand{\JavaVersion}{1.8.0\xspace} +\newcommand{\MavenVersion}{3.5.3\xspace} +\newcommand{\AntVersion}{1.9.10\xspace} +\newcommand{\AngularJsVersion}{1.6.9\xspace} +\newcommand{\SpringVersion}{5.0.5-RELEASE\xspace} +\newcommand{\NodeVersion}{v.8.9.4\xspace} +\newcommand{\YarnVersion}{1.3.2\xspace} +\newcommand{\HibernateVersion}{5.2.16.Final\xspace} +\newcommand{\FlywayVersion}{5.0.7\xspace} +\newcommand{\MySQLVersion}{5.7\xspace} +\newcommand{\TomcatVersion}{8.5.24\xspace} +\newcommand{\QuartzVerson}{2.3.0\xspace} +\newcommand{\JSchVersion}{0.1.54\xspace} +\newcommand{\JGitVersion}{4.11.0.201803080745-r\xspace} +\newcommand{\NetBeansVersion}{8.2\xspace} + +\let\Oldsection\section +\renewcommand{\section}{\FloatBarrier\Oldsection} + +\let\Oldsubsection\subsection +\renewcommand{\subsection}{\FloatBarrier\Oldsubsection} + +\let\Oldsubsubsection\subsubsection +\renewcommand{\subsubsection}{\FloatBarrier\Oldsubsubsection} + + +% An environment for displaying api methods. +% This environment takes four parameters: +% \param #1: The name of the api method. +% \param #2: The endpoint url. +% \param #3: if its a post or get operations. +% \param #4: if the method requires authentication. +% +% The body of the environment is the required parameters. +\newsavebox{\ApiName} % Create some boxes to hold the necessary text. +\newsavebox{\ApiUrl} % We need to do this because we can't use the +\newsavebox{\ApiMethod} % environment parameters in the 'end' definition. +\newsavebox{\ApiAuthReq} +\newsavebox{\ApiRequired} +\newenvironment{apimethod}[4] + { + \sbox{\ApiName}{\bfseries #1} % The name is easy. + \sbox{\ApiUrl}{\vspace{2mm}#2\vspace{2mm}} + \sbox{\ApiMethod}{\vspace{2mm}#3\vspace{2mm}} % The actor is easy. + \sbox{\ApiAuthReq}{\vspace{2mm}#4\vspace{2mm}} + \begin{lrbox}{\ApiRequired} % The environment body becomes the action. + \begin{minipage}{1\textwidth}\vspace{2mm} + }{ + \vspace{2mm} + \end{minipage} + \end{lrbox} + % Now spew forth the table using the information collected above. + + \begin{tabular}{|p{0.25\textwidth}||p{0.745\textwidth}|} \hline + \multicolumn{2}{|c|}{\usebox{\ApiName}} \\ \hline + Endpoint URL & \usebox{\ApiUrl} \\ \hline + Method & \usebox{\ApiMethod} \\ \hline + Requires Authentication & \usebox{\ApiAuthReq} \\ \hline + Parameters & \usebox{\ApiRequired} \\ \hline + \end{tabular} +} + +% An environment for displaying use cases. +% This environment takes three parameters: +% \param #1: The name of the use case. +% \param #2: The actor who participates in the use case. +% \param #3: The context in which the use case executes. +% +% The body of the environment is the action associated with the use case. +\newsavebox{\UseCaseName} % Create some boxes to hold the necessary text. +\newsavebox{\UseCaseActor} % We need to do this because we can't use the +\newsavebox{\UseCaseContext} % environment parameters in the 'end' definition. +\newsavebox{\UseCaseAction} +\newenvironment{usecase}[3] + { + \sbox{\UseCaseName}{\bfseries #1} % The name is easy. + \sbox{\UseCaseActor}{\vspace{2mm}#2\vspace{2mm}} + \begin{lrbox}{\UseCaseContext} % Format the context in a minipage. + \begin{minipage}{0.825\textwidth} + #3 + \end{minipage} + \end{lrbox} + \begin{lrbox}{\UseCaseAction} % The environment body becomes the action. + \begin{minipage}{0.825\textwidth}\vspace{2mm} + }{ + \vspace{2mm} + \end{minipage} + \end{lrbox} + % Now spew forth the table using the information collected above. + \begin{tabular}{|R{0.1\textwidth}||L{0.85\textwidth}|} \hline + \multicolumn{2}{|c|}{\usebox{\UseCaseName}} \\ \hline + Actor & \usebox{\UseCaseActor} \\ \hline + Context & \usebox{\UseCaseContext} \\ \hline + Action & \usebox{\UseCaseAction} \\ \hline + \end{tabular} +} + diff --git a/docs/src/portal.tex b/docs/src/portal.tex new file mode 100644 index 0000000..9c2f68e --- /dev/null +++ b/docs/src/portal.tex @@ -0,0 +1,138 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% FILE : portal.tex +% SUBJECT : Master document for the portal documentation set. +% AUTHOR : (C) Copyright 2018 by Northrop Grumman +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%++++++++++++++++++++ +% Preamble and global declarations +%++++++++++++++++++++ +%&pdflatex +\documentclass{report} +\setcounter{secnumdepth}{3} +%++++++++ +% Packages +%++++++++ +\usepackage[pdftex]{graphicx} +\usepackage[title]{appendix} +\usepackage[margin=1.0in]{geometry} +\usepackage[section]{placeins} +\usepackage{listings} +\usepackage{hyperref} +\usepackage{rotating} +\usepackage{fancyhdr,lipsum} +\usepackage{titlesec,titletoc} +\usepackage{enumitem} +\usepackage{color} +\usepackage{array} +\usepackage{xspace} +% The following are settings for the listings package +\input{xml_syntax} + +\newlist{legal}{enumerate}{10} +\setlist[legal]{label*=\arabic*.} + +\lstdefinelanguage{JSON} +{ + string=[s]{"}{"}, + stringstyle=\color{blue}, + comment=[l]{:}, + commentstyle=\color{black}, +} + +%---------------------------- +% Layout adjustment +%--------------------------- +\pagestyle{headings} +\setlength{\parindent}{0em} +\setlength{\parskip}{1.75ex plus0.5ex minus0.5ex} + + +\makeatletter +\def\@makechapterhead#1{% + \vspace*{50\p@}% + {\parindent \z@ \raggedright \normalfont + \ifnum \c@secnumdepth >\m@ne + \if@mainmatter + %\huge\bfseries \@chapapp\space \thechapter + \Huge\bfseries \thechapter.\space% + %\par\nobreak + %\vskip 20\p@ + \fi + \fi + \interlinepenalty\@M + \Huge \bfseries #1\par\nobreak + \vskip 40\p@ + }} +\makeatother + +%------ +%Macros +%------ +\input{macros} + + +%-------------------- +% The document itself +%-------------------- +\begin{document} + +%------------------- +%Title page +%------------------- +%\title{\portal Documentation} +%\author{Northrop Grumman} +%\date{21 March, 2018} +%\maketitle +\begin{titlepage} + \centering + \includegraphics[width=\textwidth]{figures/NGLogo.png} + \includegraphics[width=\textwidth]{figures/portal.png} + \vfill + { + \bfseries\Large + \portal Documentation \\ + \today \\ + } + \vfill + \vfill +\end{titlepage} + + +%-------------------- +% Table of contents +%------------------- +\pagenumbering{roman} +\tableofcontents +\newpage +\pagenumbering{arabic} + + +%------------------ +% Document Content +%------------------ +\input{Introduction} +\input{Architecture} +\input{ToolsLibraries} +\input{Development} +\input{Deployment} +\input{Server} +\input{Client} +\input{UserGuide} +\input{API} + +%------------------------------- +% Appendix and related materials +%------------------------------- +\appendix +\input{appendix/SettingsAppendex} +\input{appendix/NginxAppendex} +\input{appendix/ApacheAppendex} +\input{appendix/PropertiesAppendex} +\input{appendix/SeedFileAppendex} +\input{appendix/Revisions} + +\bibliographystyle{plain} +\bibliography{references} + +\end{document} \ No newline at end of file diff --git a/docs/src/xml_syntax.tex b/docs/src/xml_syntax.tex new file mode 100644 index 0000000..342519f --- /dev/null +++ b/docs/src/xml_syntax.tex @@ -0,0 +1,46 @@ +\usepackage{listings} +\usepackage{color} + +\definecolor{dkgreen}{rgb}{0,0.6,0} +\definecolor{gray}{rgb}{0.5,0.5,0.5} +\definecolor{mauve}{rgb}{0.58,0,0.82} +\definecolor{gray}{rgb}{0.4,0.4,0.4} +\definecolor{darkblue}{rgb}{0.0,0.0,0.6} +\definecolor{lightblue}{rgb}{0.0,0.0,0.9} +\definecolor{cyan}{rgb}{0.0,0.6,0.6} +\definecolor{darkred}{rgb}{0.6,0.0,0.0} + +\lstset{ + basicstyle=\ttfamily\footnotesize, + columns=fullflexible, + showstringspaces=false, + numbers=left, % where to put the line-numbers + numberstyle=\tiny\color{gray}, % the style that is used for the line-numbers + stepnumber=1, + numbersep=5pt, % how far the line-numbers are from the code + backgroundcolor=\color{white}, % choose the background color. You must add \usepackage{color} + showspaces=false, % show spaces adding particular underscores + showstringspaces=false, % underline spaces within strings + showtabs=false, % show tabs within strings adding particular underscores + frame=none, % adds a frame around the code + rulecolor=\color{black}, % if not set, the frame-color may be changed on line-breaks within not-black text (e.g. commens (green here)) + tabsize=2, % sets default tabsize to 2 spaces + captionpos=b, % sets the caption-position to bottom + breaklines=true, % sets automatic line breaking + breakatwhitespace=false, % sets if automatic breaks should only happen at whitespace + title=\lstname, % show the filename of files included with \lstinputlisting; + % also try caption instead of title + commentstyle=\color{gray}\upshape +} + +\lstdefinelanguage{XML} +{ + morestring=[s][\color{mauve}]{"}{"}, + morestring=[s][\color{black}]{>}{<}, + morecomment=[s]{}, + morecomment=[s][\color{dkgreen}]{}, + stringstyle=\color{black}, + identifierstyle=\color{lightblue}, + keywordstyle=\color{red}, + morekeywords={xmlns,xsi,noNamespaceSchemaLocation,type,id,x,y,source,target,version,tool,transRef,roleRef,objective,eventually}% list your attributes here +} \ No newline at end of file diff --git a/pipeline-settings.xml b/pipeline-settings.xml new file mode 100644 index 0000000..6eaea28 --- /dev/null +++ b/pipeline-settings.xml @@ -0,0 +1,24 @@ + + + + flyway-localhost + root + let_me_in + + + + + local-deploy + + true + + + D:\Development\apache-tomcat-9.0.10 + 9.0.10 + http://localhost:8080 + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..62efc45 --- /dev/null +++ b/pom.xml @@ -0,0 +1,337 @@ + + 4.0.0 + + net.locusworks + portal + 1.0.0-RELEASE + pom + Portal Web site + Website used for automated script runner + 2017 + + + Locusworks + https://www.locusworks.net + + + + scm://git@bitbucket.org:locus2k/portal-webapp.git + + + + 1.8 + 5.0.5.RELEASE + 5.0.4.RELEASE + ${project.basedir} + 5.2.16.Final + 5.0.7 + 2.6.0 + 1.0.0-RELEASE + 1.7.25 + 3.0.5 + 4.12 + portal + ${tomcat-deploy-url}/manager/text + tomcat-localhost + jdbc:mysql://localhost:3306 + flyway-localhost + + + + + + org.codehaus.mojo + findbugs-maven-plugin + ${findbugs.version} + + true + target/site + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0 + + -Xdoclint:none + false + + + javax.interceptor + javax.interceptor-api + 1.2.1 + + + javax.annotation + javax.annotation-api + 1.3.1 + + + + + NGAS Portal Common Library + net.locusworks.portal.common* + + + NGAS Portal Database Library + net.locusworks.portal.database* + + + NGAS Portal Migration Library + net.locusworks.portal.migration* + + + NGAS Portal WebApp Library + + net.locusworks.portal.config + :net.locusworks.portal.controllers + :net.locusworks.portal.handlers + :net.locusworks.portal.services + :net.locusworks.portal.utils + :net.locusworks.portal.websocket + + + + + + + + non-aggregate + + javadoc + + + + aggregate + + aggregate + + + + + + + + + + + + org.flywaydb + flyway-maven-plugin + ${flyway.version} + + true + ${flyway_deploy.server} + ${flyway_deploy.url}?useSSL=false + + + + org.codehaus.mojo + versions-maven-plugin + 2.2 + + false + + + + + + + org.owasp + dependency-check-maven + 3.1.2 + + ngas-proxy2 + true + true + false + true + + + + + check + + + + + + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + true + + + + maven-deploy-plugin + 2.5 + + + org.apache.tomcat.maven + tomcat7-maven-plugin + 2.2 + + + org.slf4j + slf4j-nop + ${slf4j.version} + + + + ${tomcat.deploy.url} + ${tomcat.deploy.server} + target/portal.war + /portal + + + + maven-scm-plugin + 1.9.4 + + portal-${project.version} + + + + + + + + + commons-beanutils + commons-beanutils + 1.9.3 + + + commons-codec + commons-codec + 1.11 + + + commons-io + commons-io + 2.6 + + + + + + + + junit + junit + ${junit.version} + test + + + org.apache.poi + poi + 3.17 + + + org.apache.poi + poi-ooxml + 3.17 + + + + org.apache.commons + commons-lang3 + 3.7 + + + commons-fileupload + commons-fileupload + 1.3.3 + + + org.apache.commons + commons-math3 + 3.6.1 + + + org.apache.tomcat + tomcat-jdbc + ${tomcat-version} + provided + + + commons-validator + commons-validator + 1.6 + + + + + org.springframework + spring-core + ${spring.version} + + + org.springframework + spring-aop + ${spring.version} + + + org.springframework + spring-context + ${spring.version} + + + org.springframework + spring-jdbc + ${spring.version} + + + org.springframework + spring-tx + ${spring.version} + + + org.springframework.data + spring-data-jpa + 2.0.6.RELEASE + + + + org.flywaydb + flyway-core + ${flyway.version} + jar + + + + + se.jiderhamn.classloader-leak-prevention + classloader-leak-prevention-core + ${leak-prevention.version} + + + se.jiderhamn.classloader-leak-prevention + classloader-leak-prevention-servlet + ${leak-prevention.version} + + + + net.locusworks + commons + 1.0.0-SNAPSHOT + + + + + + + portal_client + portal_database + portal_migration + portal_common + portal_webapp + portal_json + + \ No newline at end of file diff --git a/portal_client/.csslintrc b/portal_client/.csslintrc new file mode 100644 index 0000000..75a02da --- /dev/null +++ b/portal_client/.csslintrc @@ -0,0 +1,37 @@ +{ + "important": false, + "adjoining-classes": false, + "known-properties": true, + "box-sizing": false, + "box-model": true, + "overqualified-elements": false, + "display-property-grouping": true, + "bulletproof-font-face": true, + "compatible-vendor-prefixes": true, + "regex-selectors": true, + "errors": 2, + "duplicate-background-images": true, + "duplicate-properties": true, + "empty-rules": true, + "selector-max-approaching": true, + "gradients": true, + "fallback-colors": true, + "font-sizes": true, + "font-faces": true, + "floats": true, + "star-property-hack": false, + "outline-none": true, + "import": true, + "ids": false, + "underscore-property-hack": true, + "rules-count": true, + "qualified-headings": true, + "selector-max": true, + "shorthand": true, + "text-indent": true, + "unique-headings": true, + "universal-selector": true, + "unqualified-attributes": true, + "vendor-prefix": true, + "zero-units": true +} diff --git a/portal_client/.jshintrc b/portal_client/.jshintrc new file mode 100644 index 0000000..5be6b69 --- /dev/null +++ b/portal_client/.jshintrc @@ -0,0 +1,62 @@ +{ + //See: http://www.jshint.com/docs/options/ + + ////////////////////////////////////////////////////////////////////////////// + //Enforcements + ////////////////////////////////////////////////////////////////////////////// + + "bitwise": true, + "browser": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "eqnull": true, + "freeze": true, + "immed": true, + "indent": 2, + "jasmine": true, + "latedef": "nofunc", + "laxbreak": true, + "newcap": true, + "noarg": true, + "noempty": true, + "nonbsp": true, + "nonew": true, + "quotmark": "single", + //"strict": true, //TODO: tackle this monster. Could provide speedups + //"unused": "vars", //TODO: tackle this monster + "undef": true, + + + + ////////////////////////////////////////////////////////////////////////////// + //Relaxations + ////////////////////////////////////////////////////////////////////////////// + "sub": true, //TODO: consider removing this relaxation + + + //Globals + "globals": { + "jQuery": true, + "angular": true, + "console": true, + "$": true, + "_": true, + "moment": true, + "describe": true, + "beforeEach": true, + "afterEach": true, + "module": true, + "inject": true, + "it": true, + "expect": true, + "Timeline": true, + "d3": true, + "ngGridLayoutPlugin": true, + "tinymce": true, + "SockJS": true, + "Stomp": true, + "paper": true, + "validthis": true + } +} diff --git a/portal_client/Gruntfile.js b/portal_client/Gruntfile.js new file mode 100644 index 0000000..9d5d8bc --- /dev/null +++ b/portal_client/Gruntfile.js @@ -0,0 +1,453 @@ +/*jslint node: true */ +'use strict'; + +var pkg = require('./package.json'); +var serveStatic = require('serve-static'); +var serveIndex = require('serve-index'); + +//Using exclusion patterns slows down Grunt significantly +//instead of creating a set of patterns like '**/*.js' and '!**/node_modules/**' +//This method assumes the website root is in the app folder. +// +//omitDirPrefix: If true, refrains from adding 'app/' to each of the patterns. +var createFolderGlobs = function (fileTypePatterns, omitDirPrefix) { + var ignore = ['yarn_components', 'tzdata']; + + //FOR DEBUGGING + //console.log('\ncreateFolderGlobs()...'); + + //Convert fileTypePatterns to array + fileTypePatterns = Array.isArray(fileTypePatterns) ? fileTypePatterns : [fileTypePatterns]; + var fs = require('fs'); + + //FOR DEBUGGING + //console.log('File Type Patterns (Computed): ' + fileTypePatterns); + + //Change from the grunt dir to the app dir + var gruntDir = process.cwd(); + process.chdir('app'); + + //FOR DEBUGGING + //console.log('CWD: ' + process.cwd()); + + //Modify + var returnPatterns = fs.readdirSync(process.cwd()) + .map(function (file) { + //console.log('Examining: ' + file); + //If this is a file, or a folder in the ignore list + if (ignore.indexOf(file) !== -1 || + file.indexOf('.') === 0 || + !fs.lstatSync(file).isDirectory()) { + + //FOR DEBUGGING + //console.log('Ignoring: ' + file); + return null; + } else { + //FOR DEBUGGING + //console.log('--> Accepting: ' + file); + return fileTypePatterns.map(function (pattern) { + return file + '/**/' + pattern; + }); + } + }) + .filter(function (patterns) { + return patterns; + }) + .concat(fileTypePatterns); + + //Change back from app dir to grunt dir + process.chdir(gruntDir); + + if (!omitDirPrefix) { + //Prepend 'app/' to each filter item + var patternsString = returnPatterns.toString(); + var csvArray = patternsString.split(','); + returnPatterns = csvArray.map(function (pattern) { + return 'app/' + pattern; + }); + } + + //FOR DEBUGGING + //console.log('RESULT: ' + returnPatterns); + + return returnPatterns; +}; //end function createFolderGlobs() + +module.exports = function (grunt) { + + require('time-grunt')(grunt); + + // load all grunt tasks with jit loading + require('jit-grunt')(grunt, { + protractor: 'grunt-protractor-runner', + configureProxies: 'grunt-connect-proxy2', + dom_munger: 'grunt-dom-munger2', + ngtemplates: 'grunt-angular-templates' + }); + + // Project configuration. + grunt.initConfig({ + connect: { + //Default options for all connect targets + options: { + port: 9000, + hostname: '127.0.0.1', + base: 'app/', + livereload: 35729, + open: 'http://127.0.0.1:9000', + middleware: function (connect, options) { + if (!Array.isArray(options.base)) { + options.base = [options.base]; + } + + // Setup the proxy + var middlewares = [require('grunt-connect-proxy2/lib/utils').proxyRequest]; + + // Serve static files. + options.base.forEach(function (base) { + middlewares.push(serveStatic(base)); + }); + + // Make directory browse-able. + var directory = options.directory || options.base[options.base.length - 1]; + middlewares.push(serveIndex(directory)); + + return middlewares; + } + }, + main: { + //See https://github.com/drewzboto/grunt-connect-proxy + proxies: [ + { + context: ['/rest', '/portal'], + host: 'localhost', + port: 8080, + ws: true, + } + ] + }, + dist: { + options: { + base: 'dist/' + }, + proxies: [ + { + context: ['/rest', '/portal'], + host: 'localhost', + port: 8080 + } + ] + } + }, + watch: { + main: { + options: { + livereload: true, + spawn: false, + debounceDelay: 250 + }, + files: [createFolderGlobs(['*.js', '*.less', '*.html', '*.css']), '!_SpecRunner.html', '!.grunt', '!*-spec.js'], + tasks: [] + }, + dist: { + options: { + livereload: true, + spawn: false + }, + files: ['app.full.min.css', 'app.full.min.js', 'index.html'], + tasks: [] //all the tasks are run dynamically during the watch event handler + } + }, + jshint: { + main: { + options: { + jshintrc: '.jshintrc' + }, + src: [createFolderGlobs('*.js'), '*.js', '!app/common/generated_code/**', '!Gruntfile.js', '!assets.js', '!webpack.config.js', '!yarn_file_copy.js'] + } + }, + csslint: { + main: { + options: { + csslintrc: '.csslintrc' + }, + src: ['<%= dom_munger.data.applintcss %>', '!app/compiled-less/**', '!app/yarn_components/**'] + } + }, + lesslint: { + main: { + options: { + csslint: { + csslintrc: '.csslintrc' + } + }, + src: ['app/*.less', '!app/compiled-less/**', '!app/yarn_components/**'] + } + }, + clean: { + before: { + src: ['dist', 'temp', 'test_results'] + }, + after: { + src: ['temp'] + }, + cleanDistTest: { + src: ['distTestFolder'] + } + }, + less: { + production: { + files: [{ + cwd: 'app', + src: ['**/*.less', '!yarn_components/**'], + dest: 'app/compiled-less/', + filter: 'isFile', + expand: true, + ext: '.less.css' + }] + } + }, + ngtemplates: { + main: { + options: { + module: pkg.name, + htmlmin: '<%= htmlmin.main.options %>' + }, + src: [createFolderGlobs('*.html', true), '!index.html', '!_SpecRunner.html'], + dest: 'temp/templates.js', + cwd: 'app/' + } + }, + copy: { + main: { + files: [ + {cwd: 'app', src: ['<%= dom_munger.data.appjsCopyList %>','<%= dom_munger.data.appcssCopyList %>'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['yarn_components/**/*.woff'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['yarn_components/**/*.woff2'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['yarn_components/**/*.eot'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['yarn_components/**/*.svg'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['yarn_components/**/*.ttf'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['yarn_components/**/*.png'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['yarn_components/**/*.jpg'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['yarn_components/**/*.gif'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['images/**/*.svg'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['images/**/*.png'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['images/**/*.jpg'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['images/**/*.gif'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['fonts/google-md-icons/**'], dest:'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['favicon.ico'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['robots.txt'], dest: 'dist/', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['404.html'], dest: 'dist/', filter: 'isFile', expand: true} + ] + }, + //Prepares a copy of the dist folder and inserts unit tests for running + //unit tests on the distribution build. + prepareDistTestFolder: { + files: [ + //Copy the dist folder's contents to the war. + //Path is relative to src/main/webapp/grunt + {cwd: 'dist', src: ['**'], dest: 'distTestFolder', filter: 'isFile', expand: true}, + {cwd: 'app', src: ['**/*-spec.js'], dest: 'distTestFolder', filter: 'isFile', expand: true} + ] + } + }, + dom_munger: { //jshint ignore: line + read: { + options: { + read: [ + {selector: 'script[data-build!="exclude"]', attribute: 'src', writeto: 'appjs', isPath: true}, + {selector: 'link[type="text/css"][data-build!="exclude"]', attribute: 'href', writeto: 'appcss', isPath: true}, + {selector: 'script[data-remove="exclude"]', attribute: 'src', writeto: 'appjsCopyList'}, + {selector: 'link[type="text/css"][data-remove="exclude"]', attribute: 'href', writeto: 'appcssCopyList'}, + {selector: 'script[data-karma-tests!="exclude"]', attribute: 'src', writeto: 'applintjs', isPath: true}, + {selector: 'link[type="text/css"]', attribute: 'href', writeto: 'applintcss', isPath: true} + ] + }, + src: 'app/index.html' + }, + update: { + options: { + remove: ['script[data-remove!="exclude"]', 'link[data-remove!="exclude"]'], + append: [ + {selector: 'body', html: ''}, + {selector: 'head', html: ''} + ] + }, + src: 'app/index.html', + dest: 'dist/index.html' + } + }, + cssmin: { + options: { + target: 'dist/'//Eg. Changes '../relative/url/path/in/bower/components' to 'app/yarn_components/...' + //root: 'app' //Eg. changes '../relative/url/path/in/bower/components' to '/yarn_components...', but the starting slash causes problems + }, + main: { + files: { + 'dist/app.full.min.css': [ + 'temp/**/*.less.css', + '<%= dom_munger.data.appcss %>' + ] + } + } + }, + concat: { + main: { + src: ['<%= dom_munger.data.appjs %>', '<%= ngtemplates.main.dest %>'], + dest: 'temp/app.full.js' + } + }, + ngAnnotate: { + main: { + src: 'temp/app.full.js', + dest: 'temp/app.full.js' + } + }, + uglify: { + main: { + files: { + 'dist/app.full.min.js': ['temp/app.full.js'] + } + }, + pretty: { + options: { + beautify: true, + preserveComments: 'all', + sourceMap: true, + sourceMapName: 'dist/app.full.min.js.map' + }, + files: { + 'dist/app.full.min.js': ['temp/app.full.js'] + } + } + }, + html2js: { + options: { + base: '.', + module: 'ui-templates', + rename: function (modulePath) { + var moduleName = modulePath.replace('app/common/partials/ui-bootstrap-tpls/', '').replace('.html', ''); + return 'template' + '/' + moduleName + '.html'; + } + }, + main: { + src: ['app/common/partials/ui-bootstrap-tpls/**/*.html'], + dest: 'app/common/generated_code/ui-templates.js' + } + }, + htmlmin: { + main: { + options: { + collapseBooleanAttributes: true, + collapseWhitespace: true, + removeAttributeQuotes: true, + removeComments: true, + removeEmptyAttributes: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true + }, + files: { + 'dist/index.html': 'dist/index.html' + } + } + }, + concurrent: { + build: [ + 'onlyBuild' + ], + /** + * Note: The multiCapabilities feature of protractor does not seem compatible + * with grunt concurrent, unless everything passes perfectly + */ + test: { + tasks: [ + 'unitTests', + ] + }, + serve: { + tasks: [ + 'unitTests', + 'onlyServe' + ], + options: { + logConcurrentOutput: true + } + } + } //end concurrent + + }); //end grunt.initConfig + + //Runs the linters through our code + grunt.registerTask('lintAll', [ + 'dom_munger:read', + 'jshint', + 'lesslint', + 'csslint']); + + grunt.registerTask('unitTests', [ + 'lintAll', + 'dom_munger:read', + ]); + + /** + * Used by the concurrent task to split up build task. + */ + grunt.registerTask('onlyBuild', [ + 'clean:before', + 'dom_munger', + 'less', //Compiles less files to css. Creates one css file for each less file in a respective folder in temp/path/name.css + 'ngtemplates', //creates temp/templates.js that maps templates safely for minification + 'cssmin', //minifies css into dist/app.full.min.css + 'concat', + 'ngAnnotate', //Makes AngularJS files minification safe. Reads temp/app.full.js. Writes results to temp/app.full.js + 'uglify:main', //Minifies and uglifies + 'copy:main', + 'htmlmin', + 'clean:after' + ]); + + /** + * Used by the concurrent task to split up serve task. + */ + grunt.registerTask('onlyServe', [ + 'configureProxies:main', + 'connect:main', + 'watch:main' + ]); + + //Builds and runs a live watch server using the distribution build. + grunt.registerTask('serveDist', ['build', 'connect:dist', 'watch:dist']); + + //Runs a test on the distribution build. + //NOTE: this currently fails with an undefined tinyMCE variable + // + grunt.registerTask('testDist', [ + 'build', + 'copy:prepareDistTestFolder', + 'clean:cleanDistTest' + ]); + + //############################################################################ + //############################################################################ + // The tasks below are intended for public command line use + //############################################################################ + //############################################################################ + + /** Makes the distribution build */ + grunt.registerTask('build', ['concurrent:build']); + + /** Runs a live watch server proxied between port 9000 and 8080 */ + grunt.registerTask('serve', ['concurrent:serve']); + + /** Re converts the html/js templates. */ + grunt.registerTask('runHTML2js', ['html2js']); + + /** Runs the test task, including lint, unit tests, and e2e tests */ + grunt.registerTask('test', ['concurrent:test']); + + /** By default, we run the serve task */ + grunt.registerTask('default', ['serve']); + + /** Compiles project's less files into css. */ + grunt.registerTask('makeLess', ['dom_munger', 'less']); +}; diff --git a/portal_client/README.md b/portal_client/README.md new file mode 100644 index 0000000..43892d7 --- /dev/null +++ b/portal_client/README.md @@ -0,0 +1,12 @@ +Portal Client +== + +The portal_client contains all the client side html, javascript, and css files that will be presented to the user + +Yarn +====== +Yarn is the the new package manager used with node.js in replacement of `npm` it uses the same package.json file. + +To download a new client side library use the command `yarn add --save` This will add the files to the `node_modules` library. +Next include the appropriate file into the `assets.js` file inside `portal_client` to make sure it will be injected into the `index.html` file during run time. +The `assets.js` file specifies all the vendor libraries that have to be included into the client code during build. diff --git a/portal_client/app/app.css b/portal_client/app/app.css new file mode 100644 index 0000000..a17a083 --- /dev/null +++ b/portal_client/app/app.css @@ -0,0 +1,523 @@ +/*csslint important: false*/ + +#mainBody { + background-color: #ff6600; + background: url(images/background.png); + background-repeat: no-repeat; + background-attachment: fixed; + background-position: center; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; +} + +.fill-height { + height: 100% !important; +} + +.header-container-left, +.header-container, +.header-container-right { + display: inline-block; + vertical-align: middle; + margin: 0 5px; + height: 40px; +} + +.header-container-left { + left: 0; +} + +.header-container-right { + position: absolute; + right: 0; +} + +.vertical-align-center, +.header-container-right button { + -moz-transform: translateY(-50%); + -webkit-transform: translateY(-50%); + -o-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); + position: relative; + top: 50%; +} + +.horizontal-align-center { + position: relative; + left: 50%; + -moz-transform: translateX(-50%); + -webkit-transform: translateX(-50%); + -ms-transform: translateX(-50%); + -o-transform: translateX(-50%); + transform: translateX(-50%); +} + +.center-text { + text-align: center; +} + +#filterBoxAlignment{ + bottom: -2px; + left:1.5px; + background-color: #FFFFFF; +} +#bottomFilter{ + height: 35px; + bottom: 3px; + left: 1px; + background-color: #EBEBEB; +} +#box{ + + bottom:50px; +} +.left-text { + text-align: left; +} + +html, +body { + margin: 0; + padding: 0; + border: 0; +} + +body { + position: absolute; + width: 100%; + height: 100%; + overflow: hidden !important; +} + + +.fill-height-scroll, +.fill-height-scroll .tab-pane { + height: 100%; + overflow:auto; +} + +.fill-height-scroll-y, +.fill-height-scroll-y .tab-pane { + height: 100%; + overflow-y:auto; + overflow-x:no-content; +} + +.scroll { + overflow: auto; +} + +.box { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; +} + +.tab-pane { + padding: 5px; +} + +.ellipsis { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.margin-none { + margin: 0; +} + +.margin-sm { + margin: 3px; +} + +.margin-md { + margin: 5px; +} + +.margin-sides-xs { + margin-left: 2px; + margin-right: 2px; +} + +.margin-sides-sm { + margin-left: 4px; + margin-right: 4px; +} + +.margin-right-xs { + margin-right: 1px; +} + +.margin-right-sm { + margin-right: 3px; +} + +.margin-right-md { + margin-right: 5px; +} + +.margin-right-lg { + margin-right: 10px; +} + + +.col-padding-none, +.padding-none { + padding: 0 !important; +} + +.padding-left-none { + padding-left: 0; +} + +.padding-med { + padding: 3px; +} + +.padding-sides-small { + padding-left: 1px; + padding-right: 1px +} + +.padding-sides-med { + padding-left: 3px; + padding-right: 3px; +} + +.rounded-border { + box-sizing: border-box; + border: 1px solid #ddd; + border-radius: 8px; +} +/* +* lifecycle and ex org table css rules +*/ + +#availableLC{ + height: 194px; + width: 472px; +} +#availableOT{ + height: 430px; + width: 472px; +} + + +.thin-border { + border: 1px solid #ddd; +} + +.thin-border-top { + border-top: 1px solid #ddd; +} + +.thin-border-bottom { + border-bottom: 1px solid #ddd; +} + +.thin-border-left { + border-left: 1px solid #ddd; +} + +.thin-border-right { + border-right: 1px solid #ddd; +} + +.thin-border-sides { + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; +} + +.progress-bar-label { + color: #555; + left:50%; + top:50%; + -o-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + position:absolute; +} + +/*Allows label to be centered in progress bar using absolute positioning*/ +.progress { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + position: relative; +} + +.progress-bar { + box-shadow: none; +} + +.tab-pane { + padding: 0; +} + +.icon-xs svg { + float: left;/*Causes vertical centering to work with svg element*/ + width: 16px; + height: 16px; +} + +.text-small { + font-size: 10px; +} + +.bold { + font-weight: bold; +} + +/* This controls the border around the buttons when they have a selection focus +button :focus { + outline: none!important; +}*/ + +.gridStyle { + box-sizing: border-box; + border: 1px solid rgb(212,212,212); +} + +.float-left { + float: left; +} + +.float-right { + float: right; +} + +.nav-tabs li:first-of-type { + padding-left: 4px; /** TODO Replace this with responsive utility*/ +} + +/* Override the default gray because it is hard to see. */ +/* TODO: possible change to a different color/shade */ +.modal-footer { + border-top-color: black; +} + +.modal-header { + border-bottom-color: black; +} + +.rotate90 { +/* Safari */ +-webkit-transform: rotate(90deg); +/* Firefox */ +-moz-transform: rotate(90deg); +/* IE */ +-ms-transform: rotate(90deg); +/* Opera */ +-o-transform: rotate(90deg); +transform: rotate(90deg); +/* One of these catches chrome */ +} + +.rotate180 { +/* Safari */ +-webkit-transform: rotate(180deg); +/* Firefox */ +-moz-transform: rotate(180deg); +/* IE */ +-ms-transform: rotate(180deg); +/* Opera */ +-o-transform: rotate(180deg); +transform: rotate(180deg); +/* One of these catches chrome */ +} + +/* Prevents drag and shift text selection */ +.unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor:default; +} + +/* +.modal.fade .modal-dialog, .modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -moz-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +}*/ + +#modalDialogContainer>div{ + overflow-y: hidden; + overflow-x: hidden; +} + +.modal-body { + overflow: hidden; + padding-top: 5; + padding-bottom: 0; +} + +.input-group-addon:not(:last-child), +.input-group-btn:not(:last-child), +.input-group .form-control:not(:last-child) { + border-right: none; +} + + +.input-group-btn:last-child>.btn, +.input-group-btn:last-child>.btn-group { + margin-left: 0; +} + +.no-scroll-table .ngViewport { + height: auto !important; +} + +.no-scroll-table .ngCanvas, +.no-scroll-table .ngViewport, +.no-scroll-table .ngRow, +.no-scroll-table .ngFooterPanel, +.no-scroll-table .ngTopPanel{ + width: 100% !important; +} + +.no-scroll-table .ngRow { + border-bottom: none !important; + border-radius: 5px !important; +} + +.panel-body-relative { + position: relative; +} + +.normal-filter { + background-color: white; + height: 40px; + width: 200px; + border-top-right-radius: 5px; + border-bottom-left-radius: 5px; + padding-bottom: 40px; +} + +.bottom-filter { + background-color: white; + height: 40px; + position: absolute; + bottom: 0; + width: 200px; + border-top-right-radius: 5px; + border-bottom-left-radius: 5px; +} + +.bottom-filter-pad { + padding-bottom: 40px; /** Cooresponds with .bottom-filter */ +} + +/* Causes an element to fill it's absolute container fully */ +.fill-container-absolutely { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +/** Fixes issue with loading spinner showing up in front of modal dialogues */ +.spinner { + z-index: 0 !important; +} + +/** + * Hides the pin button unless you hover over the header for ng-grid + */ +.ngUnPinnedIcon { + visibility: hidden; +} + +.ngHeaderCell:hover .ngUnPinnedIcon { + visibility: visible; +} + +.glyphicon-tile.glyphicon { + text-align: center; + font-size: 8em; + color: lightgray; +} + +.label-as-badge { + border-radius: 1em; +} +.loadingFade { + position:absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #ababab; + z-index: 9000; + -moz-opacity: 0.8; + opacity: .70; + filter: alpha(opacity=80); +} + +.loadingModal { + position: absolute; + top: 45%; + left: 48%; + z-index: 9001; + text-align:center; +} + +.splitter{ + background: #c8c8c8; + width: 1px; +} + +.ghost { + background: #c8c8c8; + opacity: 0.5; + +} + +.ui-icon-gripsmall-diagonal-se { + right:0 !important; + bottom:0 !important; + background-position: -64px -224px !important; +} + +.hover-cursor { + cursor: pointer; + cursor: hand; +} + +.icon-grid-cell { + padding-top: 3px; + padding-left: 3px; +} + +@media (min-width: 992px) { + .modal-xl { + width: 95%; + } +} + +.ui-grid-sort-priority-number { + font-size: 0; +} + +.stretch input { + width: 100% +} + +.speech { + bottom: 0; + right: 0; + position: absolute; +} \ No newline at end of file diff --git a/portal_client/app/app.js b/portal_client/app/app.js new file mode 100644 index 0000000..56446d2 --- /dev/null +++ b/portal_client/app/app.js @@ -0,0 +1,130 @@ +var portal = angular.module('portal', [ + 'ui.bootstrap', + 'ui.router', + 'ui.grid', + 'ui.grid.saveState', + 'ui.grid.selection', + 'ui.grid.edit', + 'ui.grid.resizeColumns', + 'ui.grid.moveColumns', + 'ui.grid.expandable', + 'ui.grid.pinning', + 'ui.grid.autoResize', + 'ui.grid.exporter', + 'ui.grid.cellNav', + 'ui.grid.pagination', + 'ui.grid.treeView', + 'ui.tinymce', + 'ngResource', + 'ngCookies', + 'ngSanitize', + 'ngAnimate', + 'ngMessages', + 'ngMaterial', + 'ngMaterialDatePicker', + 'debounce', + 'portal.login', + 'portal.dashboard', + 'portal.dashboard.gitBank', + 'portal.dashboard.userBank', + 'portal.dashboard.credBank', + 'portal.dashboard.logBank', + 'portal.dashboard.scriptBank', + 'portal.dashboard.emailBank', + 'portal.dashboard.envBank', + 'portal.dashboard.permissionBank', + 'portal.common.constants', + 'portal.common.factories.pubsub', + 'portal.common.services.map', + 'portal.common.services.model.cache', + 'portal.common.services.model.rest', + 'portal.common.services.portalUtils', + 'portal.common.services.authorize', + 'portal.common.services.triggers', + 'portal.common.directives.portalTabset', + 'portal.common.directives.compareTo', + 'portal.common.directives.portalWebsocket', + 'portal.common.directives.permissions', + 'portal.common.partials.sessionEnd', + 'portal.common.partials.uploadpatchfile', + 'portal.common.partials.addGit', + 'portal.common.partials.addUser', + 'portal.common.partials.addDirectory', + 'portal.common.partials.addCreds', + 'portal.common.partials.addScript', + 'portal.common.partials.addEnvVars', + 'portal.common.partials.configuration', + 'portal.common.partials.fileinfo', + 'portal.common.partials.filevalidate', + 'portal.common.partials.addPermission', + 'portal.common.partials.viewScriptStatus', + 'portal.common.partials.loggerinfo', + 'portal.common.partials.smtpinfo', + 'portal.common.partials.addEmailTmpl', + 'portal.common.partials.seedinfo' +]); + +angular.module('portal').config(function ($stateProvider, $urlRouterProvider, $httpProvider) { + $urlRouterProvider.otherwise('/dashboard'); + /* Add New States Above, or else in the module, but not in both */ + $urlRouterProvider.when('', 'dashboard'); + + $httpProvider.defaults.useXDomain = true; + delete $httpProvider.defaults.headers.common['X-Requested-With']; + + $stateProvider.state('main', { + url: '/main', + pageTitle: 'Main', + templateUrl: 'main/main.html', + resolve: { + authorize: ['authorize', + function (authorize) { + return authorize.validate({ + authorities: { + account: ['USER', 'ADMIN'] + }, + redirect: 'dashboard' + }); + } + ], + } + }).state('login', { + url: '/login', + pageTitle: 'Login', + templateUrl: 'login/login.html' + }).state('prohibited', { + url: '/prohibited', + pageTitle: 'Access Denied', + templateUrl: 'prohibited/prohibited.html' + }).state('main.prohibited', { + url: '/prohibited', + pageTitle: 'Access Denied', + templateUrl: 'prohibited/prohibited.html' + }); + +}); //end angular.module.config + +angular.module('portal').run(function($rootScope, $state, $animate, $window, $location, $transitions) { + $transitions.onSuccess({to: 'dashboard.**' }, function(trans){ + $rootScope.$broadcast('$stateChangeSuccess', trans.router.globals.$current.name); + }); + + $rootScope.$on('$locationChangeSuccess', function(){ + $rootScope.actualLocation = $location.path(); + }); + + // Allows easy unregistering of listener callbacks + // See https://stackoverflow.com/a/14898930 + $rootScope.$off = function (name, listener) { + var namedListeners = $rootScope.$$listeners[name]; + if (namedListeners) { + // Loop through the array of named listeners and remove them from the array. + for (var i = 0; i < namedListeners.length; i++) { + if (namedListeners[i] === listener) { + return namedListeners.splice(i, 1); + } + } + } + }; +});//end angular.module.run + diff --git a/portal_client/app/common/constants/constants.js b/portal_client/app/common/constants/constants.js new file mode 100644 index 0000000..dcc8306 --- /dev/null +++ b/portal_client/app/common/constants/constants.js @@ -0,0 +1,43 @@ +angular.module('portal.common.constants',[]) + .constant('Constants',{ + Permissions: { + VIEW_SCRIPT_TAB : 'VIEW_SCRIPT_TAB', + VIEW_GIT_TAB : 'VIEW_GIT_TAB', + VIEW_USER_TAB : 'VIEW_USER_TAB', + VIEW_CREDS_TAB : 'VIEW_CREDS_TAB', + VIEW_LOG_TAB : 'VIEW_LOG_TAB', + VIEW_PERMISSION_TAB : 'VIEW_PERMISSION_TAB', + VIEW_EMAIL_TAB : 'VIEW_EMAIL_TAB', + VIEW_ENV_VARS_TAB : 'VIEW_ENV_VARS_TAB', + VIEW_CONFIG : 'VIEW_CONFIG', + CHANGE_CONFIG : 'CHANGE_CONFIG', + VIEW_SMTP_CONFIG : 'VIEW_SMTP_CONFIG', + EDIT_SMTP_CONFIG : 'EDIT_SMTP_CONFIG', + VIEW_HASH_TYPES : 'VIEW_HASH_TYPES', + CHANGE_HASH_TYPES : 'CHANGE_HASH_TYPES', + ADD_USERS : 'ADD_USERS', + DELETE_USERS : 'DELETE_USERS', + PURGE_LOGS : 'PURGE_LOGS', + ADD_PERMS : 'ADD_PERMS', + DELETE_PERMS : 'DELETE_PERMS', + ADD_CREDENTIALS : 'ADD_CREDENTIALS', + DELETE_CREDENTIALS : 'DELETE_CREDENTIALS', + ADD_GIT_REPO : 'ADD_GIT_REPO', + PULL_GIT_REPO : 'PULL_GIT_REPO', + DELETE_GIT_REPO : 'DELETE_GIT_REPO', + ADD_EMAIL : 'ADD_EMAIL', + DELETE_EMAIL : 'DELETE_EMAIL', + RUN_SCRIPT : 'RUN_SCRIPT', + ADD_SCRIPT : 'ADD_SCRIPT', + DELETE_SCRIPT : 'DELETE_SCRIPT', + VIEW_SCRIPT_SCHED_STATUS : 'VIEW_SCRIPT_SCHED_STATUS', + DELETE_SCRIPT_SCHED_STATUS : 'DELETE_SCRIPT_SCHED_STATUS', + ADD_ENV_VARS : 'ADD_ENV_VARS', + DELETE_ENV_VARS : 'DELETE_ENV_VARS', + VIEW_ENV_VARS : 'VIEW_ENV_VARS', + VIEW_LOG_LEVELS : 'VIEW_LOG_LEVELS', + CHANGE_LOG_LEVELS : 'CHANGE_LOG_LEVELS', + DELETE_FILE : 'DELETE_FILE', + UPDATE_SEED_VALUE : 'UPDATE_SEED_VALUE' + } + }); \ No newline at end of file diff --git a/portal_client/app/common/directives/compare-to/compare-to.js b/portal_client/app/common/directives/compare-to/compare-to.js new file mode 100644 index 0000000..0e016e9 --- /dev/null +++ b/portal_client/app/common/directives/compare-to/compare-to.js @@ -0,0 +1,22 @@ +angular.module('portal.common.directives.compareTo', ['ui.router']) + .directive('compareTo', function(){ + return { + require: 'ngModel', + scope: { + otherModelValue: '=compareTo' + }, + link: function(scope, element, attributes, ngModel) { + + ngModel.$validators.compareTo = function(modelValue) { + if ((modelValue === null || modelValue === undefined) && scope.otherModelValue === null) { + return true; + } + return modelValue === scope.otherModelValue; + }; + + scope.$watch('otherModelValue', function() { + ngModel.$validate(); + }); + } + }; + }); \ No newline at end of file diff --git a/portal_client/app/common/directives/has-permission/has-permission.js b/portal_client/app/common/directives/has-permission/has-permission.js new file mode 100644 index 0000000..ef00a4d --- /dev/null +++ b/portal_client/app/common/directives/has-permission/has-permission.js @@ -0,0 +1,32 @@ +(function() { + 'use strict'; + + angular.module('portal.common.directives.permissions', ['ui.router']) + .directive('hasPermission', function(permissions){ + return { + link: function(scope, element, attrs) { + if (!_.isString(attrs.hasPermission)){ + throw 'hasPermission value must be a string'; + } + var value = attrs.hasPermission.trim(); + var notPermissionFlag = value[0] === '!'; + if (notPermissionFlag) { + value = value.slice(1).trim(); + } + + function toggleVisibility() { + permissions.canView({permission: value}).then(function(allowed){ + if ((allowed && !notPermissionFlag) || (!allowed && notPermissionFlag)) { + element.css('display', 'block'); + } else { + element.css('display', 'none'); + } + }); + } + + toggleVisibility(); + scope.$on('permissionsChanged', toggleVisibility); + } + }; + }); +})(); \ No newline at end of file diff --git a/portal_client/app/common/directives/portal-tab-set/portal-tab-set.html b/portal_client/app/common/directives/portal-tab-set/portal-tab-set.html new file mode 100644 index 0000000..96ef3f7 --- /dev/null +++ b/portal_client/app/common/directives/portal-tab-set/portal-tab-set.html @@ -0,0 +1,14 @@ + + + +
+
+ {{tab.title}} +
+
+
+ +
+
+
+
diff --git a/portal_client/app/common/directives/portal-tab-set/portal-tab-set.js b/portal_client/app/common/directives/portal-tab-set/portal-tab-set.js new file mode 100644 index 0000000..0bdf0eb --- /dev/null +++ b/portal_client/app/common/directives/portal-tab-set/portal-tab-set.js @@ -0,0 +1,129 @@ +(function () { + 'use strict'; + + var _$state; + + /** + * Creates a tabset with a scrollable tab bar + * Based on the incredible example at http://www.bootply.com/l2ChB4vYmC + */ + angular.module('portal.common.directives.portalTabset', ['ui.router']) + .directive('portalTabSet', function ($state) { + _$state = $state; + return { + restrict: 'E', + replace: false, //Nothing shows up if you use replace: true + transclude: true, + scope: { + tabs: '=', + tabIndex: '=?', + }, + templateUrl: 'common/directives/portal-tab-set/portal-tab-set.html', + link: repoTabsetLink + }; + }); + + function repoTabsetLink(scope, element, attrs, fn) { + + //The default starting position of the tab group. + var DEFAULT_TAB_POSITION = 0; + + scope.tabIndex = scope.tabIndex === null || scope.tabIndex === undefined ? 0 : scope.tabIndex; + scope.visibleTabs = []; + + scope.$watch('tabIndex', function(value) { + if (scope.tabIndex === null || scope.tabIndex === undefined) { + return; + } + scope.tabIndex = value; + }); + + scope.tabsetSelectChange = function(index) { + scope.tabIndex = index; + scope.$emit('tabsetSelectChange', index); + }; + + scope.$on('changeTabSelection', function(value) { + scope.tabIndex = value; + }); + + /** + * Determines if the state object for a tab is active. + * + * @param {type} state A tab's state + * @returns {boolean} True if the state is active, false otherwise. + */ + function isActive(state) { + var isState = _$state.is(state); + return isState || (_$state.current.name.indexOf(state) >= 0); // allow for sub tabs. + } + + /** + * Updates the tabs after the state succesfully changes. + * + * @param {type} tabs An array of tabs. See action-pane.js for example + * @returns {undefined} + */ + function updateTabStates(tabs) { + //Update the active tag for each tab to reflect state change + var activeTab = false; + if (angular.isArray(tabs)) { + tabs.forEach(function (tab) { + tab.active = isActive(tab.state); + activeTab = activeTab || tab.active; + }); + //If no tab has been specified, activate the first tab. + if (!activeTab && angular.isDefined(tabs[0])) { + tabs[0].default = true; + tabs[0].active = true; + } + + //Now notify tabs to respect tab activation + scope.$broadcast('tabActivated'); + } + } + + scope.$on('tabsetDidChange', function (evt, tabs) { + scope.tabs = tabs; + update(); + }); + + function update() { + updateVisisbleTabls(); + updateTabStates(scope.tabs); + } + + function updateVisisbleTabls() { + for (var i = 0; i < scope.tabs.length; i++) { + var tab = scope.tabs[i]; + if (tab.visible === undefined || tab.visible) { + scope.visibleTabs[i] = tab; + } + } + } + + scope.$on('$stateChangeSuccess', function () { + updateTabStates(scope.tabs); + }); + + /** + * Changes the state to the specified tab state. If the control key was + * held when the tab was click on, opens the tab's "big" state (fullscreen) in + * a new tab. + * + * @param {$event} evt The click event. Used to determine if ctrl/cmd was held + * during the click. + * @param {type} tab The tab to activate. A tab object from $scope.tabs. + * + * @returns {undefined} + */ + scope.processTabClick = function (evt, tab, index) { + _$state.go(tab.state); + update(); + scope.tabsetSelectChange(index); + }; + + updateVisisbleTabls(); + update(); + } //end dcdTabsetLink function +})(); \ No newline at end of file diff --git a/portal_client/app/common/directives/portal-websocket/portal-websocket.html b/portal_client/app/common/directives/portal-websocket/portal-websocket.html new file mode 100644 index 0000000..26ef5f1 --- /dev/null +++ b/portal_client/app/common/directives/portal-websocket/portal-websocket.html @@ -0,0 +1,3 @@ +
+ {{iconImage}} +
\ No newline at end of file diff --git a/portal_client/app/common/directives/portal-websocket/portal-websocket.js b/portal_client/app/common/directives/portal-websocket/portal-websocket.js new file mode 100644 index 0000000..3f6d9da --- /dev/null +++ b/portal_client/app/common/directives/portal-websocket/portal-websocket.js @@ -0,0 +1,357 @@ +angular.module('portal.common.directives.portalWebsocket', ['ui.router']) +.directive('portalWebsocket', PortalWebsocket); + +function PortalWebsocket(Request, $timeout, portalUtils, PortalPubSub) { + return { + restrict: 'E', + templateUrl: 'common/directives/portal-websocket/portal-websocket.html', + link: function (scope) { + + scope.iconImage = 'import_export'; + scope.stats = 'not started'; + + var PingPong = function (msg) { + this.date = msg.date; + this.pull = msg.pull; + this.data = msg.data; + }; + + var Pinger = function () { + this.progress = 0; + this.count = 0; + this.iterations = null; + this.ping = null; + this.pong = null; + + this.bwConvert = function (bytes, msec) { + var res = bytes / msec * 1000 * 8; // bits/second; + var units; + if (res > 1000000) { + res /= 1024000; // Mbits/sec + units = 'Mbps'; + } else if (res > 1024) { + res /= 1000; // Kbits/sec + units = 'Kbps'; + } else { + units = 'bps'; + } + return this.round(res, 1) + units; + }; + + this.round = function (value, decimals) { + return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals); + }; + + this.isDone = function () { + if (this.count >= this.iterations) { + this.progress = 100; + return true; + } else { + this.progress = this.round(this.count / this.iterations * 100, 0); + return false; + } + }; + + return this; + }; + + var LatencyTask = portalUtils.inherit(function () { + Pinger.call(this); + this.iterations = 100; + this.latencySum = 0; + this.latencyMin = 0; + this.latencyAve = 0; + this.latencyMax = 0; + this.pps = 'n/a'; + + this.getPing = function () { + this.ping = new PingPong({'date': new Date().getTime(), 'pull': 0, 'data': []}); + return this.ping; + }; + + this.onPong = function (pong) { + this.pong = pong; + this.count++; + var now = new Date().getTime(); + var msec = now - this.pong.date; + this.latencySum += msec; + this.latencyAve = this.round(this.latencySum / this.count, 0); + if (this.latencyMin === 0 || msec < this.latencyMin) { + this.latencyMin = msec; + } + if (this.latencyMax === 0 || msec > this.latencyMax) { + this.latencyMax = msec; + } + // calc aggregate ppps + this.pps = this.count / this.latencySum * 1000; + this.pps = this.round(this.pps, 1); + return this.isDone(); + }; + + + return this; + }, Pinger); + + var DownloadTask = portalUtils.inherit(function (latencyTask) { + Pinger.call(this); + this.iterations = 20; + this.downloadBytes = 0; + this.downloadTime = 0; + this.download = 0; + this.pull = 10000; + + this.getPing = function () { + this.ping = new PingPong({'date': new Date().getTime(), 'pull': this.pull, 'data': []}); + return this.ping; + }; + + this.onPong = function (pong) { + this.pong = pong; + this.count++; + var now = new Date().getTime(); + var msec = now - this.pong.date - latencyTask.latencyAve / 2; + var bytes = this.pong.data.length; + this.downloadBytes += bytes; + this.downloadTime += msec; + // calc aggregate download bandwidth + this.download = this.bwConvert(this.downloadBytes, this.downloadTime); + return this.isDone(); + }; + + return this; + }, Pinger); + + var UploadTask = portalUtils.inherit(function (latencyTask, downloadTask) { + Pinger.call(this); + this.iterations = 20; + this.uploadBytes = 0; + this.uploadTime = 0; + this.upload = 0; + + this.getPing = function () { + this.ping = new PingPong({'date': new Date().getTime(), 'pull': 0, 'data': downloadTask.pong.data}); + return this.ping; + }; + + this.onPong = function (pong) { + this.pong = pong; + this.count++; + var now = new Date().getTime(); + var msec = now - this.pong.date - latencyTask.latencyAve / 2; + var bytes = this.ping.data.length; + this.uploadBytes += bytes; + this.uploadTime += msec; + this.upload = this.bwConvert(this.uploadBytes, this.uploadTime); + return this.isDone(); + }; + + return this; + }, Pinger); + + var icon = document.getElementById('icon'); + + var latencyTask = new LatencyTask(); + var downloadTask = new DownloadTask(latencyTask); + var uploadTask = new UploadTask(latencyTask, downloadTask); + var task = latencyTask; + var transport = null; + var stompClient = null; + var subscription = null; + + function onConnectionSuccess(frame) { + + transport = stompClient.ws.transport; + + // subscribe to the /info/pong queue for this user, requires controller annotations: + // @SubscribeMapping('/info/ping') + // @SendToUser('/queue/info/pong') + var headers = {/*ack: 'client'*/}; + subscription = stompClient.subscribe('/user/queue/pong', onPong, headers); + + // if not websocket scale down the number of iterations + if (transport !== 'websocket') { + var div = 5; + latencyTask.iterations /= div; + downloadTask.iterations /= div; + uploadTask.iterations /= div; + } + + // kick it off + task = latencyTask; + sendPing(); + } + + function onPong(frame) { + + // frame.ack(); + scope.$apply(function () { + var done = task.onPong(new PingPong(JSON.parse(frame.body))); + + // switch tasks when finished + if (done && task === latencyTask) { + task = downloadTask; + } else if (done && task === downloadTask) { + task = uploadTask; + } else if (done && task === uploadTask) { + task = null; + } + + if (task !== null) { + + sendPing(); + } else { + finish(); + } + + }); + } + + scope.busy = function () { + return task !== null; + }; + + function connect(transports) { + + + // create stomp client + var options = {transports: transports}; + var socket = new SockJS(Request.baseURI + '/ws/stomp', null, options); + stompClient = Stomp.over(socket); + stompClient.debug = null; + + // connect to server + scope.stats = 'connecting'; + var headers = {}; + stompClient.connect(headers, onConnectionSuccess, onConnectionFailure); + } + + function sendPing() { + setActiveStatus(); + stompClient.send('/start/ping', {}, JSON.stringify(task.getPing())); + } + + var timer = null; + + function flash() { + icon.style.color = (icon.style.color === 'green') ? 'goldenrod' : 'green'; + if (getProgress() < 100) { + timer = $timeout(flash, 250); + } + + } + + function getProgress() { + return Math.round((latencyTask.progress + uploadTask.progress + downloadTask.progress) / 3); + } + + function finish() { + $timeout.cancel(timer); + + // shutdown the websocket connection + subscription.unsubscribe(); + stompClient.disconnect(); + stompClient = null; + + if (transport === 'websocket') { + setSuccessfulStatus(); + } else { + setDegradedStatus(); + } + } + + function onConnectionFailure(error) { + $timeout.cancel(timer); + stompClient = null; // Stomp connections are closed on failure + setFailureStatus(error.toString()); + } + + + function createStats() { + var min = latencyTask.latencyMin; + var ave = latencyTask.latencyAve; + var max = latencyTask.latencyMax; + var pps = latencyTask.pps; + var upload = uploadTask.upload; + var download = downloadTask.download; + var latency = min + '/' + ave + '/' + max; + + // always start with the transport + var stats = transport; + if (latencyTask.progress > 0) { + stats += ', ' + latency + 'ms, ' + pps + 'pps'; + } + if (downloadTask.progress > 0) { + stats += ', ' + download; + } + if (uploadTask.progress > 0) { + stats += '/' + upload; + } + if (getProgress() < 100) { + stats += ', ' + getProgress() + '%'; + } + return stats; + } + + function setActiveStatus() { + + // make available to html + scope.stats = createStats(); + + // icon stays flashing + + // notify login dialog that we are not finished + PortalPubSub.publish('StompTestStatus', {status: 'active', stats: scope.stats}); + } + + + // green + function setSuccessfulStatus() { + + // make available to html + scope.stats = createStats(); + + // flashing has stopped, set green check + scope.iconImage = 'check'; + icon.style.color = 'green'; + + // publish status (to login dialog) + PortalPubSub.publish('StompTestStatus', {status: 'successful', stats: scope.stats}); + } + + // yellow + function setDegradedStatus() { + + // make available to html + scope.stats = createStats(); + + // flashing has stopped, set yellow info flag + scope.iconImage = 'info'; + icon.style.color = 'goldenrod'; + + // publish status (to login dialog) + PortalPubSub.publish('StompTestStatus', {status: 'degraded', stats: scope.stats}); + } + + //red + function setFailureStatus(msg) { + + // set hover over + scope.stats = 'failed: ' + msg; + + // flashing has stopped, set red error flag + scope.iconImage = 'error'; + icon.style.color = 'red'; + + // publish status (to login dialog) + PortalPubSub.publish('StompTestStatus', {status: 'failure', stats: scope.stats}); + + } + + // start the test + flash(); + var transports = [/*'websocket', 'xhr-streaming', 'eventsource', 'xhr-polling', 'jsonp-polling'*/]; + connect(transports); + + } + }; +} \ No newline at end of file diff --git a/portal_client/app/common/factories/portal-pubsub/portal-pubsub.js b/portal_client/app/common/factories/portal-pubsub/portal-pubsub.js new file mode 100644 index 0000000..f339854 --- /dev/null +++ b/portal_client/app/common/factories/portal-pubsub/portal-pubsub.js @@ -0,0 +1,9 @@ +(function (module) { + 'use strict'; + module('portal.common.factories.pubsub', []).factory('PortalPubSub', [function () { + /*jshint ignore:start*/ + var pub = new PubSub(); + return pub; + /*jshint ignore:end*/ + }]); +}(angular.module)); \ No newline at end of file diff --git a/portal_client/app/common/partials/add-creds/add-creds.html b/portal_client/app/common/partials/add-creds/add-creds.html new file mode 100644 index 0000000..e1e9052 --- /dev/null +++ b/portal_client/app/common/partials/add-creds/add-creds.html @@ -0,0 +1,91 @@ + +
+ +
+ {{header}} + + + close + +
+
+ +
+
+ + + +
+
Credentials name is required
+
+
+
+
+ + + + {{cred.value}} + +
+
Credential type is required
+
+
+
+
+
+ + + +
+
Username is required
+
+
+ + + + +
+
Password is required
+
+
+ + + remove_red_eye + View password + + +
+
+ + + +
+ Username on the remote system the ssh key belongs to +
User identity is required
+
+
+ + + +
+
SSH Key is required
+
+
+ + + + +
+
+
+ +
+
+ +
{{error}}
+ OK + Cancel +
+
+
+ diff --git a/portal_client/app/common/partials/add-creds/add-creds.js b/portal_client/app/common/partials/add-creds/add-creds.js new file mode 100644 index 0000000..5034f36 --- /dev/null +++ b/portal_client/app/common/partials/add-creds/add-creds.js @@ -0,0 +1,60 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.addCreds', ['ui.router']).controller('AddCredsCtrl', AddUserCtrlController); + + function AddUserCtrlController($scope, modalInstance, $state, cache, $mdDialog, Account, Credentials, credentialStores, credentialProvider, portalUtils) { + + $scope.header = modalInstance.header; + + $scope.passwordType = 'password'; + + $scope.submitted = false; + + $scope.loadingBlock = false; + + $scope.isNew = modalInstance.credentials === undefined ? true : false; + + $scope.credentials = $scope.isNew ? new Credentials() : modalInstance.credentials; + + $scope.valid = false; + + $scope.mCache = cache.request([new credentialStores.CredentialTypes()]).then(function(){ + $scope.credentialTypes = $scope.mCache.credentialTypes.data.values; + }); + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + $scope.loadingBlock = true; + var status = $scope.isNew ? 'added': 'edited'; + $scope.submitted = true; + credentialProvider.addCredentials($scope.credentials).then(function(data){ + $scope.loadingBlock = false; + portalUtils.toast('Credentials ' + status + ' successfully', ['top', 'left'], 2000); + $mdDialog.hide(); + }).catch(function(err){ + $scope.loadingBlock = false; + $scope.error = err.message; + }); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + } //end AddUserCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/add-directory/add-directory.html b/portal_client/app/common/partials/add-directory/add-directory.html new file mode 100644 index 0000000..04770b7 --- /dev/null +++ b/portal_client/app/common/partials/add-directory/add-directory.html @@ -0,0 +1,32 @@ + + +
+ {{header}} to {{currentDir}} + + + close + +
+
+ +
+ + + + +
+
+
+ +
+
+
+
+ +
+ +
{{error}}
+ OK + Cancel +
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/add-directory/add-directory.js b/portal_client/app/common/partials/add-directory/add-directory.js new file mode 100644 index 0000000..6f64984 --- /dev/null +++ b/portal_client/app/common/partials/add-directory/add-directory.js @@ -0,0 +1,67 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.addDirectory', ['ui.router']) + .controller('AddDirectoryCtrl', AddDirectoryCtrlController); + + function AddDirectoryCtrlController($scope, modalInstance, $state, cache, $mdDialog, fileProvider, UploadFile, portalUtils) { + + $scope.header = modalInstance.header; + $scope.currentDir = modalInstance.currentDir; + + $scope.loadingBlock = false; + + $scope.model = { }; + + $scope.valid = false; + + $scope.$watch('model', function(){ + + if ($scope.model.fileDir === undefined) { + $scope.valid = false; + return; + } + + $scope.valid = true; + + }, true); + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + $scope.loadingBlock = true; + fileProvider.makeDirectory($scope.currentDir, $scope.model.fileDir).then(function(data) { + $scope.loadingBlock = false; + portalUtils.toast('Directory added successfully', ['top', 'left'], 2000); + $mdDialog.hide(); + }).catch(function(err) { + $scope.loadingBlock = false; + $scope.error = err.message; + }); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + $scope.$on('$destroy', function () { + if ($scope.mCache !== undefined) { + $scope.mCache.unlisten(); + } + }); + + } //end AddDirectoryCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/add-email-tmpl/add-email-tmpl.html b/portal_client/app/common/partials/add-email-tmpl/add-email-tmpl.html new file mode 100644 index 0000000..0f94d06 --- /dev/null +++ b/portal_client/app/common/partials/add-email-tmpl/add-email-tmpl.html @@ -0,0 +1,39 @@ + +
+ +
+ {{header}} + + + close + +
+
+ +
+ + + +
+
Email template name is required
+
+
+ + + +
+
Email subject is required
+
+
+ Email Body + +
+
+ +
{{error}}
+ OK + Cancel +
+
+
+ diff --git a/portal_client/app/common/partials/add-email-tmpl/add-email-tmpl.js b/portal_client/app/common/partials/add-email-tmpl/add-email-tmpl.js new file mode 100644 index 0000000..21a7e46 --- /dev/null +++ b/portal_client/app/common/partials/add-email-tmpl/add-email-tmpl.js @@ -0,0 +1,67 @@ +(function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.addEmailTmpl', ['ui.router']).controller('AddEmailTmplCtrl', addEmailTmplCtrlController); + + function addEmailTmplCtrlController($scope, modalInstance, $state, cache, $mdDialog, EmailTemplate, + systemProvider, scriptStores, portalUtils, $timeout) { + + $scope.trustAsHtml = portalUtils.trustAsHtml; + + $scope.header = modalInstance.header; + + $scope.submitted = false; + + $scope.loadingBlock = false; + + $scope.isNew = modalInstance.template === undefined; + + $scope.emailTmpl = $scope.isNew ? new EmailTemplate() : new EmailTemplate(modalInstance.template); + + /*jshint ignore:start*/ + $scope.tinymceOptions = { + inline: false, + skin: 'lightgray', + theme : 'modern', + branding: false, + height: 300, + max_height: 300, + width: 600, + max_width: 600, + resize: false + }; + /*jshint ignore:end*/ + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + $scope.loadingBlock = true; + var status = $scope.isNew ? 'Added': 'Edited'; + systemProvider.addEmailTemplate($scope.emailTmpl).then(function(data){ + portalUtils.toast('Email Template ' + status + ' Successfully', ['top', 'left'], 2000); + $mdDialog.hide(); + }).catch(function(err) { + $scope.loadingBlock = false; + $scope.error = err.message; + }); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + } //end addEmailTmplCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/add-env/add-env.html b/portal_client/app/common/partials/add-env/add-env.html new file mode 100644 index 0000000..1ace645 --- /dev/null +++ b/portal_client/app/common/partials/add-env/add-env.html @@ -0,0 +1,50 @@ + +
+ +
+ {{header}} + + New Environment Variable + note_add + + + + close + +
+
+ + + +
+
+ + + + + + + + + +
+
+
+ + Remove Environment Variable + delete + +
+
+
+
+
+
+ +
{{error}}
+ OK + Cancel +
+
+
+ diff --git a/portal_client/app/common/partials/add-env/add-env.js b/portal_client/app/common/partials/add-env/add-env.js new file mode 100644 index 0000000..4d990cb --- /dev/null +++ b/portal_client/app/common/partials/add-env/add-env.js @@ -0,0 +1,86 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.addEnvVars', ['ui.router']).controller('AddEnvVarCtrl', addEnvVarCtrlController); + + function addEnvVarCtrlController($scope, modalInstance, cache, $mdDialog, scriptObjects, + systemProvider, systemStores, portalUtils) { + + $scope.header = modalInstance.header; + + $scope.submitted = false; + + $scope.loadingBlock = false; + + $scope.isNew = modalInstance.envVars.length === 0; + + $scope.envVars = []; + + if (!$scope.isNew) { + for (var i = 0; i < modalInstance.envVars.length; i++) { + $scope.envVars.push( new scriptObjects.ScriptEnvironmentVariable(modalInstance.envVars[i]) ); + } + } + + $scope.valid = false; + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + + $scope.loadingBlock = true; + var status = $scope.isNew ? 'added': 'edited'; + $scope.submitted = true; + + systemProvider.addEnvVars($scope.envVars).then(function(data) { + $scope.loadingBlock = false; + portalUtils.toast('Script ' + status + ' successfully', ['top', 'left'], 2000); + $mdDialog.hide(); + }).catch(function(err) { + $scope.loadingBlock = false; + $scope.error = err.message; + }); + }; + + $scope.addEnvVar = function() { + var env = new scriptObjects.ScriptEnvironmentVariable(); + $scope.envVars.push(env); + }; + + $scope.moveUp = function(index, array) { + var ent1 = array[index]; + var ent2 = array[index-1]; + array[index] = ent2; + array[index-1] = ent1; + }; + + $scope.moveDown = function(index, array) { + var ent1 = array[index]; + var ent2 = array[index+1]; + array[index] = ent2; + array[index+1] = ent1; + }; + + $scope.delete = function (id, array) { + array.splice(id, 1); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + } //end addEnvVarCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/add-git/add-git.html b/portal_client/app/common/partials/add-git/add-git.html new file mode 100644 index 0000000..ad8007c --- /dev/null +++ b/portal_client/app/common/partials/add-git/add-git.html @@ -0,0 +1,74 @@ + +
+ +
+ {{header}} + + + close + +
+
+ +
+
+ + + +
+
Name is required
+
+
+
+
+ + + +
+
Git SCM url is required
+
+
+
+
+ + + +
+
Git SCM url is required
+
+
+
+
+ + + +
+
Checkout directory is required
+
+
+
+
+ + + + {{displayKey(cred)}} + +
+
Credentials are required
+
+
+
+
+
+
+ +
+
+ +
{{error}}
+ OK + Cancel +
+
+
+ diff --git a/portal_client/app/common/partials/add-git/add-git.js b/portal_client/app/common/partials/add-git/add-git.js new file mode 100644 index 0000000..1126f70 --- /dev/null +++ b/portal_client/app/common/partials/add-git/add-git.js @@ -0,0 +1,67 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.addGit', ['ui.router']).controller('AddGitCtrl', addGitCtrlController); + + function addGitCtrlController($scope, modalInstance, $state, cache, $mdDialog, GitInfo, Account, accountProvider, gitProvider, credentialStores, portalUtils) { + + $scope.trustAsHtml = portalUtils.trustAsHtml; + + $scope.header = modalInstance.header; + + $scope.submitted = false; + + $scope.loadingBlock = false; + + $scope.isNew = modalInstance.gitInfo === undefined ? true : false; + + $scope.git = $scope.isNew ? new GitInfo() : modalInstance.gitInfo; + + $scope.valid = false; + + $scope.mCache = cache.request([new credentialStores.Credentials()]).then(function(){ + $scope.credentials = $scope.mCache.credentials.data.values; + }); + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + $scope.loadingBlock = true; + var status = $scope.isNew ? 'added': 'edited'; + $scope.submitted = true; + gitProvider.addGitRepo($scope.git).then(function(data){ + $scope.loadingBlock = false; + portalUtils.toast('Git ' + status + ' successfully', ['top', 'left'], 2000); + $mdDialog.hide(); + }).catch(function(err){ + $scope.loadingBlock = false; + $scope.error = err.message; + }); + }; + + $scope.displayKey = function(cred) { + if (cred === undefined) { + return ''; + } + return cred.name + (cred.credentialType === 'SSH' ? ' (SSH Key)' : ' (Username/Password)'); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + } //end addGitCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/add-permission/add-permission.html b/portal_client/app/common/partials/add-permission/add-permission.html new file mode 100644 index 0000000..039f6e3 --- /dev/null +++ b/portal_client/app/common/partials/add-permission/add-permission.html @@ -0,0 +1,67 @@ + +
+ +
+ {{header}} + + + close + +
+
+ +
+
+ + + +
+
Unique name required
+
+
+ + + +
+
Display name required
+
+
+
+
+ + +

Permission Group Enabled

+ + {{permission.enabled ? 'True' : 'False'}} +
+
+
+
+
+ + + + + +

{{perm.displayName}}

+ + {{perm.allowed ? 'Enabled' : 'Disabled'}} +
+
+
+
+
+
+
+
+ +
+
+ +
{{error}}
+ OK + Cancel +
+
+
+ diff --git a/portal_client/app/common/partials/add-permission/add-permission.js b/portal_client/app/common/partials/add-permission/add-permission.js new file mode 100644 index 0000000..fdff87b --- /dev/null +++ b/portal_client/app/common/partials/add-permission/add-permission.js @@ -0,0 +1,92 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.addPermission', ['ui.router']).controller('AddPermissionCtrl', AddPermissionCtrlController); + + function AddPermissionCtrlController($scope, modalInstance, $state, cache, $mdDialog, permissionObjects, + permissionStores, permissionProvider, portalUtils) { + + $scope.header = modalInstance.header; + + $scope.submitted = false; + $scope.loadingBlock = false; + + $scope.isNew = modalInstance.permission === undefined ? true : false; + + $scope.permission = $scope.isNew ? new permissionObjects.PermissionGroup() : new permissionObjects.PermissionGroup(modalInstance.permission); + + $scope.valid = false; + $scope.permissionCategories = []; + + $scope.mCache = cache.request([new permissionStores.PermissionCategories()]).then(function(){ + $scope.permissionCategories = $scope.mCache.permissionCategories.data.values; + if (!$scope.isNew) { + $.each($scope.permission.permissions, function(key, value) { + for (var i = 0; i < $scope.permissionCategories.length; i++) { + var cat = $scope.permissionCategories[i]; + if (cat.name !== value.permissionCategory) { + continue; + } + for (var j = 0; j < cat.permissions.length; j++) { + var perm = cat.permissions[j]; + if (value.permissionId === perm.id) { + perm.allowed = value.allowed; + } + } + } + }); + } + }).catch(function(err){ + portalUtils.toast('Unable to get permission categories: ' + err.message, ['top', 'left'], 2000); + }); + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + $scope.loadingBlock = true; + var status = $scope.isNew ? 'added': 'edited'; + $scope.submitted = true; + + $scope.permission.permissions = []; + $.each($scope.permissionCategories, function(key, cat) { + $.each(cat.permissions, function(key2, perm) { + var newPerm = new permissionObjects.PermissionGroupPermission({ + permissionId: perm.id, + permissionGroupId: $scope.permission.id, + name: perm.name, + displayName: perm.displayName, + allowed: perm.allowed === undefined ? false : perm.allowed + }); + $scope.permission.permissions.push(newPerm); + }); + }); + + permissionProvider.updatePermissions($scope.isNew, $scope.permission).then(function(results) { + $scope.loadingBlock = false; + portalUtils.toast('Permission ' + status + ' successfully', ['top', 'left'], 2000); + $mdDialog.hide(); + }).catch(function(err){ + $scope.loadingBlock = false; + $scope.error = err.message; + }); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + } //end AddPermissionCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/add-script/add-script-bottom.html b/portal_client/app/common/partials/add-script/add-script-bottom.html new file mode 100644 index 0000000..67fda67 --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script-bottom.html @@ -0,0 +1,63 @@ + + +
+ +
+ Schedule + + + New Schedule + note_add + +
+
+
+
+ + +
+ + + + {{sched.value}} + + + + + + + + + + + + + + +
+ + Move Up + arrow_upward + + + Move Down + arrow_downward + + + Remove Parameter + delete + +
+
+
+
+
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/add-script/add-script-left-local.html b/portal_client/app/common/partials/add-script/add-script-left-local.html new file mode 100644 index 0000000..9038702 --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script-left-local.html @@ -0,0 +1,9 @@ +
+ + + +
+
Script command is required
+
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/add-script/add-script-left-remote.html b/portal_client/app/common/partials/add-script/add-script-left-remote.html new file mode 100644 index 0000000..5ba1576 --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script-left-remote.html @@ -0,0 +1,27 @@ +
+ + + + {{displayKey(cred)}} + +
+
Credentials are required
+
+
+
+
+ + + +
+
SSH URL is required
+
+
+ + + +
+
SSH URL is required
+
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/add-script/add-script-left-scp.html b/portal_client/app/common/partials/add-script/add-script-left-scp.html new file mode 100644 index 0000000..9601f9f --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script-left-scp.html @@ -0,0 +1,78 @@ +
+ + From Local + +
+
+ + + + Host name of the machine script is copying from. For local type 'LOCAL' +
+
From host address is required
+
+
+ + + + Host port of the machine script is copying from. For local type '0' +
+
From Port is required
+
+
+
+
+ + + + {{displayKey(cred)}} + +
+
From credentials are required
+
+
+
+
+ + To Local + +
+
+ + + + Host name of the machine script is copying to. For local type 'LOCAL' +
+
To host address is required
+
+
+ + + + Host port of the machine script is copying to. For local type '0' +
+
To Port is required
+
+
+
+
+ + + + {{displayKey(cred)}} + +
+
From credentials are required
+
+
+
+
+ + + + File name or regular expression for file to send' +
+
File name or regular expression required
+
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/add-script/add-script-left.html b/portal_client/app/common/partials/add-script/add-script-left.html new file mode 100644 index 0000000..eed10a5 --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script-left.html @@ -0,0 +1,55 @@ + + +
+ +
+ Script + +
+
+
+
+ + + +
+
Name is required
+
+
+
+ + + + {{st.value}} + +
+
Script type is required
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + Base directory to find files in + +
+
+ + + + Directory to send/save the files to + +
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/add-script/add-script-right-chain.html b/portal_client/app/common/partials/add-script/add-script-right-chain.html new file mode 100644 index 0000000..87de336 --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script-right-chain.html @@ -0,0 +1,60 @@ +
+ +
+ Script Chain + + + New Script Chain + note_add + +
+
+
+
+ + +
+
+ + + + {{s.name}} + +
+
Script Required
+
+
+ + + + Exit status of the parent script to execute this script +
+
Exit Status is Required
+
+
+ + + (Optional) How many minutes after the parent script ends should this script execute + + +
+
+
+ + Move Up + arrow_upward + + + Move Down + arrow_downward + + + Remove Parameter + delete + +
+
+
+
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/add-script/add-script-right-email.html b/portal_client/app/common/partials/add-script/add-script-right-email.html new file mode 100644 index 0000000..4bc1799 --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script-right-email.html @@ -0,0 +1,89 @@ +
+ +
+ Script Email + + + New Script Email + note_add + +
+
+
+
+
+
+ + + + {{tmpl.emailName}} + +
+
Email Template Required
+
+
+ + Add Recipient + add + +
+ + +
+ + + + {{u.username}} + +
+
Recipient Required
+
+
+
+
+
+
+
+ + Confirm Email Template + check + + + Remove Email Template + delete + +
+
+
+ + +
+
+ + + + +
+ + +
+ + + + +
+
+
+
+
+ + Remove Email Template + delete + +
+
+
+
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/add-script/add-script-right-env-vars.html b/portal_client/app/common/partials/add-script/add-script-right-env-vars.html new file mode 100644 index 0000000..40f01c9 --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script-right-env-vars.html @@ -0,0 +1,47 @@ +
+ +
+ Script Environment Variables + + + New Script Environment Variable + note_add + +
+
+
+
+ + +
+
+ + + + + + + + + +
+
+
+ + Move Up + arrow_upward + + + Move Down + arrow_downward + + + Remove Parameter + delete + +
+
+
+
+
+
diff --git a/portal_client/app/common/partials/add-script/add-script-right-parameters.html b/portal_client/app/common/partials/add-script/add-script-right-parameters.html new file mode 100644 index 0000000..baf6a07 --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script-right-parameters.html @@ -0,0 +1,41 @@ +
+ +
+ {{script.scriptType !== 'LOCAL' ? 'Commands' : 'Parameters'}} + + + {{script.scriptType !== 'LOCAL' ? 'New Command' : 'New Parameter'}} + note_add + +
+
+
+
+ + +
+
+ + + +
+
+
+ + Move Up + arrow_upward + + + Move Down + arrow_downward + + + Remove Parameter + delete + +
+
+
+
+
+
diff --git a/portal_client/app/common/partials/add-script/add-script-right.html b/portal_client/app/common/partials/add-script/add-script-right.html new file mode 100644 index 0000000..1e0bb9d --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script-right.html @@ -0,0 +1,21 @@ + + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ \ No newline at end of file diff --git a/portal_client/app/common/partials/add-script/add-script.css b/portal_client/app/common/partials/add-script/add-script.css new file mode 100644 index 0000000..7473414 --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script.css @@ -0,0 +1,19 @@ +.add-script-even { + background-color: #f3f3f3; +} + +answerInput.md-input-container.md-input { + height: 65px !important; +} + +.md-parameter-toolbar { + background-color: rgb(219, 233, 240) !important; + color: black !important; + padding-left: 5px !important; + padding-right: 5px !important; +} + +.md-icon-default-color { + color: grey !important; + color: rgba(0, 0, 0, 0.54) !important; +} diff --git a/portal_client/app/common/partials/add-script/add-script.html b/portal_client/app/common/partials/add-script/add-script.html new file mode 100644 index 0000000..da706ab --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script.html @@ -0,0 +1,31 @@ + +
+ +
+ {{header}} + + + close + +
+
+ +
+
+
+ +
+
+
+
+ +
+
+ +
{{error}}
+ OK + Cancel +
+
+
+ diff --git a/portal_client/app/common/partials/add-script/add-script.js b/portal_client/app/common/partials/add-script/add-script.js new file mode 100644 index 0000000..ffa47ce --- /dev/null +++ b/portal_client/app/common/partials/add-script/add-script.js @@ -0,0 +1,218 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.addScript', ['ui.router']).controller('AddScriptCtrl', addScriptCtrlController); + + function addScriptCtrlController($scope, modalInstance, $state, cache, $mdDialog, scriptObjects, EmailTemplate, Account, + scriptProvider, scriptStores, credentialStores, portalUtils, systemStores, accountStores) { + + $scope.trustAsHtml = portalUtils.trustAsHtml; + + $scope.header = modalInstance.header; + + $scope.submitted = false; + + $scope.loadingBlock = false; + + $scope.isNew = modalInstance.script === undefined ? true : false; + + $scope.script = $scope.isNew ? new scriptObjects.Script() : new scriptObjects.Script(modalInstance.script); + + $scope.model = { + fromLocal: $scope.isNew || $scope.script.scriptType !== 'SCP' ? false : $scope.script.scp.fromHost === 'LOCAL', + toLocal: $scope.isNew || $scope.script.scriptType !== 'SCP' ? false : $scope.script.scp.toHost === 'LOCAL', + addEmail : false, + recipients: [] + }; + + $scope.valid = false; + + $scope.mCache = cache.request([ + new scriptStores.ScriptTypes(), + new scriptStores.ScheduleFrequencies(), + new credentialStores.Credentials(), + new scriptStores.Scripts(), + new systemStores.EmailTemplates(), + new systemStores.SMTPConfig(), + new accountStores.UserPool() + ]).then(function(){ + $scope.scriptTypes = $scope.mCache.scriptTypes.data.values; + $scope.schedFreq = $scope.mCache.scheduleFrequencies.data.values; + $scope.credentials = $scope.mCache.credentials.data.values; + $scope.availScripts = $scope.mCache.scripts.data.values; + $scope.users = $scope.mCache.userPool.data.values; + $scope.emails = $scope.mCache.emailTmpls.data.values; + $.each($scope.mCache.smtpConfig.data.values, function(key, value){ + if (value.name === 'smtp.enabled') { + $scope.model.smtpEnabled = value.value === 'true' || value.value === true; + return; + } + }); + }); + + $scope.fromLocalChange = function() { + if ($scope.model.fromLocal) { + $scope.script.scp.fromHost = 'LOCAL'; + $scope.script.scp.fromPort = 0; + $scope.script.scp.fromCredentials = $scope.credentials[0]; + } else { + $scope.script.scp.fromHost = undefined; + $scope.script.scp.fromPort = undefined; + $scope.script.scp.fromCredentials = undefined; + } + }; + + $scope.toLocalChange = function() { + if ($scope.model.toLocal) { + $scope.script.scp.toHost = 'LOCAL'; + $scope.script.scp.toPort = 0; + $scope.script.scp.toCredentials = $scope.credentials[0]; + } else { + $scope.script.scp.toHost = undefined; + $scope.script.scp.toPort = undefined; + $scope.script.scp.toCredentials = undefined; + } + }; + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + + $scope.loadingBlock = true; + var status = $scope.isNew ? 'added': 'edited'; + $scope.submitted = true; + + $.each($scope.script.schedule, function(index, value) { + $scope.script.schedule[index].startDatetime = convertTime(value.startDatetime); + $scope.script.schedule[index].endDatetime = convertTime(value.endDatetime); + $scope.script.schedule[index].lastRun = convertTime(value.lastRun); + }); + + scriptProvider.addScript($scope.script).then(function(data) { + $scope.loadingBlock = false; + portalUtils.toast('Script ' + status + ' successfully', ['top', 'left'], 2000); + $mdDialog.hide(); + }).catch(function(err) { + $scope.loadingBlock = false; + $scope.error = err.message; + }); + }; + + function convertTime(value) { + if (value === null || value === undefined) { + return undefined; + } + + var dte; + if(moment.isMoment(value)) { + dte = value.unix(); + } else if ($.isNumeric(value)) { + dte = value; + } else if (typeof value.getTime === 'function'){ + dte = value.getTime(); + } else { + //When adding a new file the return date comes back like this format Nov 2, 2017 10:53:19 AM + //We need to parse it back to a moment object to parse it to the correct format + dte = moment(value, 'MMM D, YYY h:mm:ss A').unix(); + } + + return dte; + } + + $scope.displayKey = function(cred) { + if (cred === undefined) { + return ''; + } + return cred.name + (cred.credentialType === 'SSH' ? ' (SSH Key)' : ' (Username/Password)'); + }; + + $scope.addParameter = function() { + var param = new scriptObjects.ScriptParameter(); + $scope.script.parameters.push(param); + }; + + $scope.addSchedule = function() { + var sched = new scriptObjects.ScriptSchedule(); + $scope.script.schedule.unshift(sched); + }; + + $scope.addScriptChain = function() { + var chain = new scriptObjects.ScriptChain(); + chain.parentScript = $scope.script.id; + $scope.script.scriptChain.push(chain); + }; + + $scope.addEnvVar = function() { + var env = new scriptObjects.ScriptEnvironmentVariable(); + $scope.script.scriptVariables.push(env); + }; + + $scope.addScriptEmail = function() { + $scope.model.addEmail = true; + }; + + $scope.removeTmpl = function() { + $scope.model.email = undefined; + $scope.model.recipients = []; + $scope.model.addEmail = false; + }; + + $scope.confirmTemplate = function() { + var email = new EmailTemplate($scope.model.email); + $.each($scope.model.recipients, function(key, value) { + email.recipients.push(new Account(value)); + }); + $scope.script.scriptEmails.push(email); + $scope.removeTmpl(); + }; + + $scope.emailTmpl = function(email, index) { + $scope.script.scriptEmails[index] = email; + }; + + $scope.userUpdate = function(email, recip, index) { + email.recipients[index] = recip; + }; + + $scope.addRecipient = function(email) { + var user = new Account(); + email.recipients.push(user); + }; + + $scope.moveUp = function(index, array) { + var ent1 = array[index]; + var ent2 = array[index-1]; + array[index] = ent2; + array[index-1] = ent1; + }; + + $scope.moveDown = function(index, array) { + var ent1 = array[index]; + var ent2 = array[index+1]; + array[index] = ent2; + array[index+1] = ent1; + }; + + $scope.delete = function (id, array) { + array.splice(id, 1); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + } //end addScriptCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/add-user/add-user.html b/portal_client/app/common/partials/add-user/add-user.html new file mode 100644 index 0000000..f0ab545 --- /dev/null +++ b/portal_client/app/common/partials/add-user/add-user.html @@ -0,0 +1,102 @@ + +
+ +
+ {{header}} + + + close + +
+
+ +
+
+ + + +
+
Email is required
+
Email is not in correct format
+
+
+
+
+ + + +
+
Password is required
+
+
+ + + remove_red_eye + View password + + + + + +
+
Passwords do not match
+
Must confirm password
+
+
+ + + remove_red_eye + View password + + +
+ +
+ + + + + + + + + +
+
+ + + + User + Administrator + +
+
Role is required
+
+
+
+
+ + + + + + + + + +
+
+
+
+ +
+
+ +
{{error}}
+ OK + Cancel +
+
+
+ diff --git a/portal_client/app/common/partials/add-user/add-user.js b/portal_client/app/common/partials/add-user/add-user.js new file mode 100644 index 0000000..d37843f --- /dev/null +++ b/portal_client/app/common/partials/add-user/add-user.js @@ -0,0 +1,85 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.addUser', ['ui.router']).controller('AddUserCtrl', AddUserCtrlController); + + function AddUserCtrlController($scope, modalInstance, $state, cache, $mdDialog, Account, accountProvider, portalUtils, + permissionStores, configStores, Triggers) { + + $scope.header = modalInstance.header; + + $scope.passwordType = 'password'; + $scope.confPasswordType = 'password'; + + $scope.requirePassword = modalInstance.requirePassword; + + $scope.submitted = false; + + $scope.loadingBlock = false; + + $scope.isNew = modalInstance.account === undefined ? true : false; + + $scope.account = $scope.isNew ? new Account() : modalInstance.account; + + $scope.valid = false; + + $scope.mCache = cache.request([new configStores.Config('userExpirationDays'), + new permissionStores.AllPermissionGroups()]).then(function(){ + $scope.expirationDays = parseInt($scope.mCache.config.data.userExpirationDays); + $scope.permissionGroups = $scope.mCache.allPermissionGroups.data.values; + }).catch(function(err){ + portalUtils.toast('Unable to get expiration days: ' + err.message, ['top', 'left'], 4000); + }); + + if ($scope.isNew) { + $scope.account.effectiveDate = new Date(); + } else { + $scope.account.effectiveDate = moment($scope.account.effectiveDate); + $scope.account.expirationDate = moment($scope.account.expirationDate); + } + + $scope.$watch('expirationDays', function(value){ + if ($scope.isNew) { + $scope.account.expirationDate = moment().add($scope.expirationDays, 'days'); + } + }); + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + $scope.loadingBlock = true; + var status = $scope.isNew ? 'added': 'edited'; + $scope.submitted = true; + accountProvider.addUser($scope.account).then(function(data){ + $scope.loadingBlock = false; + portalUtils.toast('User ' + status + ' successfully', ['top', 'left'], 2000); + $mdDialog.hide(); + }).catch(function(err){ + $scope.loadingBlock = false; + $scope.error = err.message; + }); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + Triggers.listen([Triggers.PERMISSIONS_ADDED, Triggers.PERMISSIONS_DELETED], function(trigger, payload){ + new permissionStores.AllPermissionGroups().refresh(); + }); + + } //end AddUserCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/configuration/configuration.html b/portal_client/app/common/partials/configuration/configuration.html new file mode 100644 index 0000000..593f68e --- /dev/null +++ b/portal_client/app/common/partials/configuration/configuration.html @@ -0,0 +1,146 @@ + +
+ + +
+ Configuration + + + close + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + {{ll.name}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + remove_red_eye + View password + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + + remove_red_eye + View password + +
+
+
+
+
+
+
+ +
{{error}}
+ Save + Cancel +
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/configuration/configuration.js b/portal_client/app/common/partials/configuration/configuration.js new file mode 100644 index 0000000..b48d9c6 --- /dev/null +++ b/portal_client/app/common/partials/configuration/configuration.js @@ -0,0 +1,73 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.configuration', ['ui.router']).controller('ConfigurationCtrl', configurationCtrlController); + + function configurationCtrlController($scope, account, $state, cache, $mdDialog, configStores, configProvider, + portalUtils, Constants, permissions) { + + $scope.logLevels= [ + {id:'OFF', name: 'Off'}, + {id:'FATAL', name: 'Fatal'}, + {id:'ERROR', name:'Error'}, + {id:'WARN', name:'Warn'}, + {id:'INFO', name:'Info'}, + {id:'DEBUG', name:'Debug'}, + {id:'TRACE', name:'Trace'}, + {id:'ALL', name: 'All'} + ]; + + $scope.saving = false; + $scope.dbPasswordType = 'password'; + $scope.dbRootPasswordType = 'password'; + $scope.canEdit = false; + $scope.mCache = cache.request([new configStores.Configs()]).then(function(){ + $scope.config = $scope.mCache.configs.data; + }); + + permissions.canView({permission: Constants.Permissions.CHANGE_CONFIG}).then(function(allowed){ + $scope.canEdit = allowed; + }); + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + portalUtils.confirmBox('saveConfig', 'Save Configuration', + ['Are you sure you want to save the configuration values?', 'New values will not take effect until server restart'], + false, function(value) { + if (value.confirm) { + $scope.saveConfig(); + } + }); + }; + + $scope.saveConfig = function() { + configProvider.saveConfiguration($scope.config).then(function(value){ + portalUtils.toast('Configuration values saved successfully', ['top', 'left'], 2000); + new configStores.Configs().refresh(); + $mdDialog.hide(); + }).catch(function(err){ + portalUtils.toast('Failed to save configuration: ' + err.message, ['top', 'left'], 4000); + new configStores.Configs().refresh(); + }); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + } //end configurationCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/file-info/file-info.html b/portal_client/app/common/partials/file-info/file-info.html new file mode 100644 index 0000000..a330aff --- /dev/null +++ b/portal_client/app/common/partials/file-info/file-info.html @@ -0,0 +1,87 @@ + +
+ +
+ {{header}} + + + close + +
+
+ + + + + +
+

Please enter hash value and file location to validate the file

+
+
+ +
+

Please enter new file location to move the file to

+
+
+ +
+ + + + + + + +
+
File location is required
+
+
+ + + + +
+
+ + + + +
+
Hash value required
+
+
+
+ + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+ +
{{error}}
+ Validate + Move + Download + Close +
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/file-info/file-info.js b/portal_client/app/common/partials/file-info/file-info.js new file mode 100644 index 0000000..e44c8c4 --- /dev/null +++ b/portal_client/app/common/partials/file-info/file-info.js @@ -0,0 +1,111 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.fileinfo', ['ui.router']).controller('FileInfoCtrl', fileInfoController); + + function fileInfoController($scope, modalInstance, $state, cache, $mdDialog, PatchFile, portalUtils, fileProvider) { + + $scope.submitted = false; + $scope.loadingBlock = false; + $scope.moveFile = modalInstance.moveFile; + + $scope.header = modalInstance.header; + $scope.originalFile = modalInstance.file; + $scope.file = new PatchFile(modalInstance.file); + if (!$scope.file.valid) { + $scope.file.md5 = ''; + $scope.file.sha1 = ''; + $scope.file.sha512 = ''; + $scope.file.fileLocation = ''; + } + + $scope.ok = function () { + $mdDialog.hide(); + }; + + $scope.download = function() { + var url = '/portal/file/download.do?hashValue=' + $scope.file.sha512; + window.open(url, ''); + }; + + $scope.relocate = function() { + portalUtils.confirmBox('relocateFile', 'Relocate File', ['Are you sure you want to relocate file:', $scope.file.fileName, 'To directory:', $scope.file.fileLocation], + false, function(value) { + if (value.confirm) { + $scope.loadingBlock = true; + fileProvider.moveFile($scope.file).then(function() { + $scope.loadingBlock = false; + portalUtils.toast('File ' + $scope.file.fileName + ' update successfully', ['top', 'left'], 2000); + }).catch(function(err) { + $scope.loadingBlock = false; + $scope.error = err.message; + }); + } + }); + }; + + $scope.validate = function() { + $scope.submitted = true; + if ($scope.file.valid) { + $mdDialog.hide(); + return true; + } + + $scope.error = ''; + if ($scope.file.fileLocation.trim() === '') { + $scope.error = 'File location needed to validate'; + return false; + } + + if ($scope.file.newHash.trim() === '') { + $scope.error = 'Hash value needed'; + return false; + } + + if ($scope.file.newHash.trim().length === 32) { + $scope.file.md5 = $scope.file.newHash; + $scope.file.sha1 = $scope.originalFile.sha1; + $scope.file.sha512 = $scope.originalFile.sha512; + } else if ($scope.file.newHash.trim().length === 40) { + $scope.file.sha1 = $scope.file.newHash; + $scope.file.sha512 = $scope.originalFile.sha512; + $scope.file.md5 = $scope.originalFile.md5; + } else if ($scope.file.newHash.trim().length === 128) { + $scope.file.sha512 = $scope.file.newHash; + $scope.file.md5 = $scope.originalFile.md5; + $scope.file.sha512 = $scope.originalFile.sha512; + } else { + $scope.error = 'Invalid hash provided'; + return false; + } + + $scope.loadingBlock = true; + fileProvider.validate($scope.file).then(function(data) { + $scope.loadingBlock = false; + portalUtils.toast('File ' + $scope.file.fileName + ' update successfully', ['top', 'left'], 2000); + $scope.ok(); + return true; + }).catch(function(err) { + $scope.loadingBlock = false; + $scope.error = err.message; + return false; + }); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + } //end fileInfoController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/file-validate/file-validate.html b/portal_client/app/common/partials/file-validate/file-validate.html new file mode 100644 index 0000000..0dd501a --- /dev/null +++ b/portal_client/app/common/partials/file-validate/file-validate.html @@ -0,0 +1,22 @@ + + +
+ {{header}} + + + close + +
+
+ + + + + + Ok + +
\ No newline at end of file diff --git a/portal_client/app/common/partials/file-validate/file-validate.js b/portal_client/app/common/partials/file-validate/file-validate.js new file mode 100644 index 0000000..83c8973 --- /dev/null +++ b/portal_client/app/common/partials/file-validate/file-validate.js @@ -0,0 +1,41 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.filevalidate', ['ui.router']).controller('FileValidateCtrl', fileValidateController); + + function fileValidateController($scope, modalInstance, $state, $mdDialog, $sce) { + $scope.header = modalInstance.header; + $scope.getContent = function() { + var content = ''; + $.each(modalInstance.results, function(key, value) { + content += '

' + key + ':

'; + for (var i = 0; i < value.length; i++) { + content += '' + value[i] + '

'; + } + }); + $scope.content = content; + }; + + $scope.getContent(); + + $scope.trustAsHtml = function (string){ + return $sce.trustAsHtml(string); + }; + + $scope.ok = function () { + $mdDialog.hide(); + }; + + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + } //end fileValidateController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/logger-info/logger-info.html b/portal_client/app/common/partials/logger-info/logger-info.html new file mode 100644 index 0000000..486c2c5 --- /dev/null +++ b/portal_client/app/common/partials/logger-info/logger-info.html @@ -0,0 +1,46 @@ + + +
+ Loggers + + + close + +
+
+ + + + + +

All Loggers

+   + + {{ll.name}} + +
+
+
+ + + + {{tab.title}} + + +

{{log.logger}}

+   + + {{ll.name}} + +
+
+
+
+
+
+ +
{{error}}
+ Save + Cancel +
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/logger-info/logger-info.js b/portal_client/app/common/partials/logger-info/logger-info.js new file mode 100644 index 0000000..7df6bcf --- /dev/null +++ b/portal_client/app/common/partials/logger-info/logger-info.js @@ -0,0 +1,94 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.loggerinfo', ['ui.router']).controller('LoggerInfoCtrl', loggerInfoCtrlController); + + function loggerInfoCtrlController($scope, $state, cache, $mdDialog, loggerStores, loggerProvider, portalUtils, permissions, Constants) { + $scope.saving = false; + + permissions.canView({permission: Constants.Permissions.CHANGE_LOG_LEVELS}).then(function(allowed) { + $scope.canEdit = allowed; + }); + + $scope.tabs = []; + + $scope.model = { + logLevels: [ + {id:'OFF', name: 'Off'}, + {id:'FATAL', name: 'Fatal'}, + {id:'ERROR', name:'Error'}, + {id:'WARN', name:'Warn'}, + {id:'INFO', name:'Info'}, + {id:'DEBUG', name:'Debug'}, + {id:'TRACE', name:'Trace'}, + {id:'ALL', name: 'All'} + ] + }; + + $scope.mCache = cache.request([new loggerStores.Loggers()]).then(function(){ + $scope.loggers = $scope.mCache.loggers.data.values; + var tabIndex = -1; + for (var i = 0; i < $scope.loggers.length; i++) { + if (i % 5 === 0) { + tabIndex += 1; + $scope.tabs.push({ + title: 'Page' + (tabIndex + 1), + loggers: [] + }); + } + $scope.tabs[tabIndex].loggers.push($scope.loggers[i]); + } + }); + + $scope.setAllLoggers = function() { + $.each($scope.loggers, function(key, value) { + value.level = $scope.model.alllevel; + }); + }; + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + portalUtils.confirmBox('saveLoggers', 'Update Log Levels', + ['Are you sure you want to change the logging level?'], + false, function(value) { + if (value.confirm) { + $scope.saveLoggingLevel(); + } + }); + }; + + $scope.saveLoggingLevel = function() { + loggerProvider.setLoggers($scope.loggers).then(function(value){ + portalUtils.toast('Log levels set successfully', ['top', 'left'], 2000); + new loggerStores.Loggers().refresh(); + $mdDialog.hide(); + }).catch(function(err){ + portalUtils.toast('Failed to set log levels: ' + err.message, ['top', 'left'], 4000); + new loggerStores.Loggers().refresh(); + }); + }; + + $scope.saveConfig = function() { + + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + } //end loggerInfoCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/script-status/view-script-status.html b/portal_client/app/common/partials/script-status/view-script-status.html new file mode 100644 index 0000000..8530867 --- /dev/null +++ b/portal_client/app/common/partials/script-status/view-script-status.html @@ -0,0 +1,68 @@ + +
+ +
+ {{header}} + + + close + +
+
+ +
+ + +
+ Run {{scriptStatus.length - $index}} + + Status:  + {{status.exitStatus !== 0 ? 'Failed' : 'Success'}} + + Start: {{formatTime(status.startTime)}} + + End: {{formatTime(status.endTime)}} + + + {{ status.expanded ? 'expand_less' : 'expand_more' }} + {{status.expanded ? 'Collapse' : 'Expand'}} + +
+
+
{{status.results}}
+ +

Files available for download

+ + + File Name + + +
+

{{file.fileName}}

+ +
+ + file_download + Download File + + + delete + Delete File + +
+
+
+
+
+
+
+
+ No script job status found for select script job +
+
+ +
{{error}}
+ Cancel +
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/script-status/view-script-status.js b/portal_client/app/common/partials/script-status/view-script-status.js new file mode 100644 index 0000000..fbdb5f5 --- /dev/null +++ b/portal_client/app/common/partials/script-status/view-script-status.js @@ -0,0 +1,84 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.viewScriptStatus', ['ui.router']).controller('ScriptStatusCtrl', ScriptStatusCtrl); + + function ScriptStatusCtrl($scope, modalInstance, cache, $mdDialog, scriptObjects, scriptProvider, fileProvider, portalUtils, + Triggers, Constants) { + + $scope.header = modalInstance.header; + $scope.scriptStatus = []; + + + $scope.Constants = Constants; + $scope.confirmQuestionBox = true; + + $scope.init = function() { + scriptProvider.getScriptStatus(modalInstance.jobId).then(function(data){ + $scope.scriptStatus = []; + for (var i = 0; i < data.length; i++) { + $scope.scriptStatus.push(new scriptObjects.ScriptStatus(data[i])); + } + }); + }; + $scope.init(); + + $scope.formatTime = function(value) { + if (value === null || value === undefined) { + return 'None'; + } + return moment(value).format('MM/DD/Y HH:mm:ss'); + }; + + $scope.delete = function(entity) { + if ($scope.confirmQuestionBox) { + portalUtils.confirmBox('deleteFile', 'Delete File', ['Are you sure you want to delete the file', entity.fileName], + true, function(value) { + if (value.confirm) { + $scope.confirmQuestionBox = !value.checkbox; + $scope.deleteFile(entity); + } + }); + } else { + $scope.deleteFile(entity); + } + }; + + $scope.deleteFile = function(entity) { + fileProvider.deleteFile(entity).then(function() { + portalUtils.toast('File deleted successfully', ['top', 'left'], 2000); + }).catch(function(err){ + portalUtils.toast('File failed to delete: ' + err.message, ['top', 'left'], 4000); + }); + }; + + $scope.ok = function () { + + }; + + $scope.download = function(row) { + var url = '/portal/file/download.do?id=' + row.id; + window.open(url, ''); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + Triggers.listen([Triggers.FILE_DELETED], function(trigger, payload){ + $scope.init(); + }); + + } //end addEnvVarCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/seed-info/seed-info.html b/portal_client/app/common/partials/seed-info/seed-info.html new file mode 100644 index 0000000..bef6492 --- /dev/null +++ b/portal_client/app/common/partials/seed-info/seed-info.html @@ -0,0 +1,26 @@ + + +
+ AES Seed + + + close + +
+
+ + + + + + + + + + + +
{{error}}
+ Save + Cancel +
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/seed-info/seed-info.js b/portal_client/app/common/partials/seed-info/seed-info.js new file mode 100644 index 0000000..ee4ee97 --- /dev/null +++ b/portal_client/app/common/partials/seed-info/seed-info.js @@ -0,0 +1,56 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.seedinfo', ['ui.router']).controller('SeedInfoCtrl', seedInfoCtrlController); + + function seedInfoCtrlController($scope, $state, cache, $mdDialog, systemProvider, portalUtils, permissions, Constants) { + $scope.saving = false; + + permissions.canView({permission: Constants.Permissions.UPDATE_SEED_VALUE}).then(function(allowed) { + $scope.canEdit = allowed; + }); + + $scope.model = {}; + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + portalUtils.confirmBox('updateSeed', 'Update AES Seed', + ['Are you sure you want to update the AES Seed', + 'This will go through and update all the saved encrypted values', + 'and cannot be undone once performed'], + false, function(value) { + if (value.confirm) { + $scope.updateSeed(); + } + }); + }; + + $scope.updateSeed = function() { + systemProvider.updateSeed($scope.model.seed).then(function(){ + portalUtils.toast('Seed updated successfully', ['top', 'left'], 2000); + $mdDialog.hide(); + }, function(err){ + $scope.error = err.message; + }); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + } //end SeedInfoCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/session-end/session-end.html b/portal_client/app/common/partials/session-end/session-end.html new file mode 100644 index 0000000..d745378 --- /dev/null +++ b/portal_client/app/common/partials/session-end/session-end.html @@ -0,0 +1,20 @@ + +
+ +
+ Alert! + +
+
+ +
+
+
+
+ +
{{error}}
+ {{btnCnlText}} + {{btnText}} +
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/session-end/session-end.js b/portal_client/app/common/partials/session-end/session-end.js new file mode 100644 index 0000000..73dd51b --- /dev/null +++ b/portal_client/app/common/partials/session-end/session-end.js @@ -0,0 +1,16 @@ +(function () { + 'use strict'; + + angular.module('portal.common.partials.sessionEnd', ['ui.router']) + .controller('SessionEndCtrl', SessionEndCtrlController); + + function SessionEndCtrlController($scope, modalInstance, $state, cache) { + $scope.onOkay = modalInstance.onOkay; + $scope.message = modalInstance.message; + $scope.btnText = modalInstance.btnText; + + $scope.btnCnlText = modalInstance.btnCnlText; + $scope.onCancel = modalInstance.onCancel; + } //end SessionEndCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/smtp-info/smtp-info.html b/portal_client/app/common/partials/smtp-info/smtp-info.html new file mode 100644 index 0000000..944ab99 --- /dev/null +++ b/portal_client/app/common/partials/smtp-info/smtp-info.html @@ -0,0 +1,93 @@ + + +
+ SMTP Configuration + + + close + +
+
+ + + + + + + +
+ + + + + {{conf.value ? 'Yes' : 'No'}} +
+ + + + + remove_red_eye + View password + +
+
+
+
+
+
+ + + + +
+ + + + + {{conf.value ? 'Yes' : 'No'}} +
+ + + + + remove_red_eye + View password + +
+
+
+
+
+
+ + + + +
+ + + + + {{conf.value ? 'Yes' : 'No'}} +
+ + + + + remove_red_eye + View password + +
+
+
+
+
+
+
+
+ +
{{error}}
+ Save + Cancel +
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/smtp-info/smtp-info.js b/portal_client/app/common/partials/smtp-info/smtp-info.js new file mode 100644 index 0000000..2f79caa --- /dev/null +++ b/portal_client/app/common/partials/smtp-info/smtp-info.js @@ -0,0 +1,140 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.smtpinfo', ['ui.router']).controller('SMTPInfoCtrl', smtpInfoCtrlController); + + function smtpInfoCtrlController($scope, $state, cache, $mdDialog, systemProvider, systemStores, + portalUtils, permissions, Constants, Triggers) { + $scope.saving = false; + + permissions.canView({permission: Constants.Permissions.EDIT_SMTP_CONFIG}).then(function(allowed){ + $scope.canEdit = allowed; + }); + + $scope.smtpConfig = [ + { + name: 'smtp.enabled', + displayValue: 'SMTP Enabled', + value: false, + type: 'boolean' + },{ + name: 'mail.transport.protocol', + displayValue: 'Transport Protocol', + value: 'smtp', + type: 'text' + },{ + name: 'mail.smtp.host', + displayValue: 'SMTP Server Url', + value: '', + type: 'text' + },{ + name: 'mail.smtp.port', + displayValue: 'SMTP Server Port', + value: 0, + type: 'number' + },{ + name: 'mail.debug', + displayValue: 'Enable Mail Debug Logging', + value: false, + type: 'boolean' + },{ + name: 'mail.smtp.starttls.enable', + displayValue: 'Enable Start TLS', + value: false, + type: 'boolean' + },{ + name: 'mail.smtp.starttls.required', + displayValue: 'Require Start TLS', + value: false, + type: 'boolean' + },{ + name: 'mail.smtp.ssl.trust', + displayValue: 'SSL Trust Certificate', + value: '*', + type: 'text' + },{ + name: 'mail.smtp.auth', + displayValue: 'Require Authentication', + value: false, + type: 'boolean' + },{ + name: 'smtp.from.user', + displayValue: 'From User', + value: '', + type: 'text' + },{ + name: 'smtp.username', + displayValue: 'SMTP Username', + value: '', + type: 'text' + },{ + name: 'smtp.password', + displayValue: 'SMTP Password', + value: '', + type: 'password', + meta: 'password' + }, + ]; + + $scope.mCache = cache.request([new systemStores.SMTPConfig()]).then(function(){ + var conf = $scope.mCache.smtpConfig.data.values; + + $.each($scope.smtpConfig, function(key, value) { + $.each(conf, function(key2, value2) { + if (value.name === value2.name) { + if (value.type === 'number') { + value.value = parseInt(value2.value); + } else if (value.type === 'boolean') { + value.value = value2.value === 'true' || value2.value === true; + } else { + value.value = value2.value; + } + return; + } + }); + }); + }); + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + portalUtils.confirmBox('saveSmtpConfig', 'Update SMTP Configuration', + ['Are you sure you want to update the smtp configuration?'], + false, function(value) { + if (value.confirm) { + $scope.saveSMTPConf(); + } + }); + }; + + $scope.saveSMTPConf = function() { + systemProvider.updateSmtpConfig($scope.smtpConfig).then(function() { + $mdDialog.hide(); + }).catch(function(err) { + $scope.error = err.message; + portalUtils.toast('Failed to save configuration: ' + err.message, ['top', 'left'], 4000); + }); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + Triggers.listen([Triggers.SMTP_CONFIG_UPDATED], function(trigger, payload) { + new systemStores.SMTPConfig().refresh(); + }); + + } //end smtpInfoCtrlController function +})(); \ No newline at end of file diff --git a/portal_client/app/common/partials/templates/confirm-window.tmpl.html b/portal_client/app/common/partials/templates/confirm-window.tmpl.html new file mode 100644 index 0000000..03afa10 --- /dev/null +++ b/portal_client/app/common/partials/templates/confirm-window.tmpl.html @@ -0,0 +1,25 @@ + + +
+ {{confirmTitle}} + + + close + +
+
+ +
+ +

{{message}}

+
+
+ Do not show this message again +
+
+
+ + Yes + No + +
\ No newline at end of file diff --git a/portal_client/app/common/partials/templates/purge-logs.tmpl.html b/portal_client/app/common/partials/templates/purge-logs.tmpl.html new file mode 100644 index 0000000..fe26628 --- /dev/null +++ b/portal_client/app/common/partials/templates/purge-logs.tmpl.html @@ -0,0 +1,39 @@ + + +
+ {{title}} + + + close + +
+
+ +
+

{{message}}

+ + + + + + + + +
+
+
+
+ +
+ +
{{error}}
+
+ Ok + Cancel +
+
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/upload-patch-file/upload-patch-file.html b/portal_client/app/common/partials/upload-patch-file/upload-patch-file.html new file mode 100644 index 0000000..b732d97 --- /dev/null +++ b/portal_client/app/common/partials/upload-patch-file/upload-patch-file.html @@ -0,0 +1,31 @@ + + +
+ {{header}} to {{currentDir}} + + + close + +
+
+ +
+ +
+
+ + + + +
+
+
+ +
+
+ +
{{error}}
+ OK + Cancel +
+
\ No newline at end of file diff --git a/portal_client/app/common/partials/upload-patch-file/upload-patch-file.js b/portal_client/app/common/partials/upload-patch-file/upload-patch-file.js new file mode 100644 index 0000000..ac43893 --- /dev/null +++ b/portal_client/app/common/partials/upload-patch-file/upload-patch-file.js @@ -0,0 +1,73 @@ + (function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.common.partials.uploadpatchfile', ['ui.router']) + .controller('UploadPatchFileCtrl', UploadPatchFileCtrlController); + + function UploadPatchFileCtrlController($scope, modalInstance, $state, cache, $mdDialog, fileProvider, UploadFile, portalUtils) { + + $scope.header = modalInstance.header; + $scope.currentDir = modalInstance.currentDir; + + $scope.uploadFile = undefined; + + $scope.loadingBlock = false; + + $scope.model = { }; + + $scope.valid = false; + + $scope.$watch('model', function(){ + if ($scope.model.hashValue === undefined || $scope.model.hashValue.trim() === '') { + $scope.valid = false; + return; + } + + if ($scope.uploadFile === undefined) { + $scope.valid = false; + return; + } + + $scope.valid = true; + + }, true); + + /** + * OK button handler + * @returns {undefined} + */ + $scope.ok = function () { + $scope.loadingBlock = true; + fileProvider.uploadFile([new UploadFile().init($scope.uploadFile)], $scope.model.hashValue, $scope.currentDir).then(function(data) { + $scope.loadingBlock = false; + portalUtils.toast('File uploaded successfully', ['top', 'left'], 2000); + $mdDialog.hide(); + }).catch(function(err) { + $scope.loadingBlock = false; + $scope.error = err.message; + }); + }; + + /** + * Cancel button handler. + * @returns {undefined} + */ + $scope.cancel = function () { + //Dismiss the modal + $mdDialog.cancel(); + }; + + $scope.$on('$destroy', function () { + if ($scope.mCache !== undefined) { + $scope.mCache.unlisten(); + } + }); + + } //end UploadPatchFileCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/authorize/authorize.js b/portal_client/app/common/services/authorize/authorize.js new file mode 100644 index 0000000..fef8b98 --- /dev/null +++ b/portal_client/app/common/services/authorize/authorize.js @@ -0,0 +1,125 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Deferred authorization handler. + */ + angular.module('portal.common.services.authorize', ['ui.router']) + .service('authorize', authorizeService); + + function authorizeService($state, $q, cache, accountStores) { + + /** + * Defines the different types of auth checkers. + * @type type + */ + var authHandlers = { + account: function (args) { + var deferred = $q.defer(); + var mCache = cache.request([new accountStores.Account()]).once(function () { + var authorized = false; + for (var i = 0; i < args.length; i++) { + if (mCache.account.data.authoritiesMap.containsKey(args[i])) { + authorized = true; + break; + } + } + deferred.resolve(authorized); + }).catch(function () { + deferred.reject(); + }); + return deferred.promise; + }, + permission: function (args) { + var deferred = $q.defer(); + var mCache = cache.request([new accountStores.Account()]).once(function () { + var acct = mCache.account.data; + if (!acct.enabled) { + deferred.resolve(false); + return; + } + var authorized = false; + for (var i = 0; i < args.length; i++) { + if (acct.hasPermission(args[i])) { + authorized = true; + break; + } + } + deferred.resolve(authorized); + }).catch(function (err) { + deferred.reject(); + }); + return deferred.promise; + } + }; + + + /** + * Chains deferrals so we don't have to nest them. + * + * Yes, this is kind of complicated, its async recursion. Tried to help with + * comments but only so much you can do. + * + * @param {type} authorities + * @param {type} keys + * @param {type} i + * @param {type} isValid + * @returns {$q@call;defer.promise} + */ + function validate(authorities, keys, i, isValid) { + var deferred = $q.defer(); + //If still within bounds of authorities array + //call the auth handler and if we are still valid + //No point in continuing if we are no longer valid. + if (i < keys.length && isValid) { + authHandlers[keys[i]](authorities[keys[i]]).then(function (valid) { + //When the auth handler returns, chain up the next one + //by calling validate again. + //Its important to combine the valid status as we go. + return validate(authorities, keys, i + 1, isValid && valid); + }).then(function (valid) { + //calling deferred in the then allows us to close our chains. + deferred.resolve(valid); + }).catch(function(){ + deferred.reject(); + }); + } else { + //This is the last link in the chain, if we don't call resolve + //we won't start linking the rest of the chains. + deferred.resolve(isValid); + } + return deferred.promise; + } + + return { + /** + * Deferred validation routine for the ui-router resolve + * functionality. UI-router listens for the promise to be resolved + * before movingn to the next state. + * + * This avoids us having to do awkard redirects while we check + * server for status. + * + * @param {type} args + * @returns {$q@call;defer.promise} + */ + validate: function (args) { + var deferred = $q.defer(); + if (angular.isObject(args.authorities)) { + var keys = Object.keys(args.authorities); + validate(args.authorities, keys, 0, true).then(function (isValid) { + if (!isValid) { + $state.go(args.redirect); + } + deferred.resolve(); + }).catch(function () { + $state.go(args.redirect); + }); + } else { + deferred.resolve(); + } + return deferred.promise; + } + }; + } //end authorizeService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/map/map.js b/portal_client/app/common/services/map/map.js new file mode 100644 index 0000000..8f08b06 --- /dev/null +++ b/portal_client/app/common/services/map/map.js @@ -0,0 +1,200 @@ +(function () { + 'use strict'; + /** + * DESCRIPTION: + * + * Special map object for easy access to both an object map and an array. + * + * This object mimics maps as defined by the Java map interface. + */ + angular.module('portal.common.services.map', ['ui.router']) + .factory('Map', mapService); + + function mapService() { + + /** + * Defines a map object that allows storage of data as array and object. + * @param {type} accessor The value used on elements for the key, e.g. element.id => "id" + * @returns {map_L35.mapService.Map} + */ + function Map(accessor, groupKeys) { + var map = this; + + /** Array Storage. */ + map.values = []; + /** Object Storage. */ + map.object = {}; + /** Group Storage. */ + map.groups = {}; + /** Group keys. */ + map.groupKeys = []; + if (angular.isArray(groupKeys)) { + map.groupKeys = groupKeys.slice(); + } else if (angular.isDefined(groupKeys)) { + map.groupKeys.push(groupKeys); + } + + //Initialize Groups + for (var i = 0; i < map.groupKeys.length; i++) { + map.groups[map.groupKeys[i]] = {}; + } + + /** + * defines the part of the element that comprises the key. + * May be undefined, not guaranteed. + */ + map.accessor = accessor; + + /** + * @returns {Boolean} True if emtpy. + */ + map.isEmpty = function () { + return map.values.length === 0; + }; + + /** + * Determins if an item is stored inside based on its key. + * @param {type} key + * @returns {Boolean} True if contained. + */ + map.containsKey = function (key) { + return angular.isDefined(map.object[key]); + }; + + /** + * Searches for element, via loop. + * @param {type} value + * @returns {Boolean} True if element found. + */ + map.containsValue = function (value) { + for (var i = 0; i < map.values.length; i++) { + if (map.values[i] === value) { + return true; + } + } + return false; + }; + + /** + * @param {type} key + * @returns {type} element found by key. + */ + map.get = function (key) { + return map.object[key]; + }; + + /** + * Inserts element into both array and object storage. + * @param {type} key + * @param {type} value + */ + map.put = function (key, value) { + //Remove elements. + map.removeByKey(key); + + //Insert. + map.values.push(value); + map.object[key] = value; + + //Group + for (var i = 0; i < map.groupKeys.length; i++) { + var groupKey = map.groupKeys[i]; + var group = value[groupKey]; + if (!angular.isDefined(map.groups[groupKey][group])) { + map.groups[groupKey][group] = new Map(map.accessor); + } + map.groups[groupKey][group].put(key, value); + } + return value; + }; + + /** + * Allows access to a group map. + * Always returns a valid map, just may be empty. + * @param {type} group + * @returns {map_L35.mapService.Map} + */ + map.getGroup = function (groupKey, group) { + if (angular.isDefined(map.groups[groupKey][group])) { + return map.groups[groupKey][group]; + } + return new Map(); + }; + + /** + * Removes an element from both storage interfaces. + * @param {type} key + * @returns {Boolean} T/F if removed or not. + */ + map.removeByKey = function (key) { + if (!map.containsKey(key)) { + return false; + } + var value = map.object[key]; + //Remove from Array. + for (var i = 0; i < map.values.length; i++) { + if (map.values[i] === value) { + map.values.splice(i, 1); + break; + } + } + //Remove from object. + delete map.object[key]; + + //Remove from groups. + for (var j = 0; j < map.groupKeys.length; j++) { + var groupKey = map.groupKeys[j]; + var group = value[groupKey]; + if (angular.isDefined(map.groups[groupKey][group])) { + map.groups[groupKey][group].removeByKey(key); + } + } + return true; + }; + + /** + * Empties the storage objects. + */ + map.clear = function () { + map.object = {}; + map.values = []; + }; + + map.init = function (data, Constructor, accessor) { + map.clear(); + map.accessor = accessor; + if (angular.isArray(data)) { + for (var i = 0; i < data.length; i++) { + var obj = new Constructor(data[i]); + map.put(obj[accessor], obj); + } + } + }; + + Object.defineProperties(map, { + /** + * Returns the keys from the object. + */ + keySet: { + get: function () { + return Object.keys(map.object); + } + }, + length: { + get: function () { + return map.values.length; + } + } + }); + } + + Map.create = function (data, Constructor, accessor, groupKeys) { + var map = new Map(accessor, groupKeys); + map.init(data, Constructor, accessor); + return map; + }; + + return Map; + + } //end mapService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/box/box.js b/portal_client/app/common/services/model/cache/box/box.js new file mode 100644 index 0000000..9bc7e02 --- /dev/null +++ b/portal_client/app/common/services/model/cache/box/box.js @@ -0,0 +1,133 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Box contains data and status for the store and cache. + */ + angular.module('portal.common.services.model.cache') + .factory('Box', boxService); + + function boxService(portalUtils, Listener, Triggers, $q) { + + /** + * Box obejct to enacupsulate the data so it can be watched. + * @returns {undefined} + */ + var Box = portalUtils.inherit(function (args) { + var box = this; + Listener.call(box); + /** + * Container for data. + */ + box.data = undefined; + /** + * Container for error. + */ + box.errorData = undefined; + /** + * Status of the data request. + */ + box.status = Box.Status.NEW; + /** Function to handle client side data processing. */ + box.processGui = args.processGui; + /** Function to handle parallel processing. */ + box.processParallel = args.processParallel; + /** Function for deferred processing, expects $q promise. */ + box.processDeferred = args.processDeferred; + + if (angular.isDefined(args.triggers)) { + Triggers.listen(args.triggers, function(trigger, payload) { + portalUtils.call(args.processTrigger, trigger, payload, box); + }); + } + + box.init = function() { + if (box.status === Box.Status.NEW) { + box.status = Box.Status.PENDING; + if (angular.isFunction(args.source)) { + args.source(args.params).then(function (payload) { + box.update(payload); + }).catch(function(payload){ + box.onError(payload); + }); + } else { + console.error('Store source function was not a function.'); + } + } + return box.listen(); + }; + + box.refresh = function () { + var deferred = $q.defer(); + if (box.status !== Box.Status.PENDING && + angular.isFunction(args.source)) { + box.status = Box.Status.PENDING; + args.source(args.params).then(function (payload) { + if (angular.isFunction(box.processDeferred)) { + box.update(payload).then(deferred.resolve); + } else { + box.update(payload); + deferred.resolve(); + } + }); + } else { + deferred.resolve(); + } + return deferred.promise; + }; + + box.update = function (_data) { + box.status = Box.Status.DONE; + var data = _data; + if (angular.isFunction(box.processParallel)) { + data = box.processParallel(data); + } + if (angular.isFunction(box.processGui)) { + data = box.processGui(data); + } + if (angular.isFunction(box.processDeferred)) { + return box.processDeferred(data).then(function(_data){ + return update(_data); + }); + } else { + return update(data); + } + }; + + box.onError = function(_data) { + box.status = Box.Status.ERROR; + box.errorData = _data; + box.notifyError(); + }; + + function update(data) { + box.data = data; + box.notify(); + } + + box.clear = function () { + box.data = undefined; + box.status = Box.Status.new; + box.notify(); + }; + + box.isDone = function () { + return box.status === Box.Status.DONE; + }; + + }, Listener); + + /** + * Status enum + */ + Box.Status = { + NEW: 0, + PENDING: 1, + DONE: 2, + ERROR: 3 + }; + + + return Box; + } //end boxService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/cache.js b/portal_client/app/common/services/model/cache/cache.js new file mode 100644 index 0000000..d4ead1f --- /dev/null +++ b/portal_client/app/common/services/model/cache/cache.js @@ -0,0 +1,192 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Provides entry to the cache service. + */ + angular.module('portal.common.services.model.cache', ['ui.router']) + .service('cache', cacheService); + + function cacheService($cacheFactory, portalUtils, Store, Request, $rootScope, Listener) { + /** + * $cacheFactory instances for storing all cache data. Shared between tabs. + * + * This may be better placed in app.js + * + * Why are we collecting rootscopes? because when data changes on the + * shared cache we need to notify everyone via $digest. This is + * done in the Listener.digest function. + * + * @type @call;$cacheFactory + */ + var caches = {}; + var scopes = []; + if (angular.isObject(window.opener) && angular.isObject(window.opener.cacheService)) { + caches = window.opener.cacheService.caches; + scopes = window.opener.cacheService.rootScopes; + scopes.push($rootScope); + } else { + caches.data = $cacheFactory('dataCache'); + caches.http = $cacheFactory('httpCache'); + scopes = [$rootScope]; + window.cacheService = {caches: caches, rootScopes: scopes}; + } + + Store.registerCache(caches.data); + Request.registerCache(caches.http); + Listener.registerScopes(scopes); + + /** + * Mini Cache Object. + * Provides an isolated cache with only data points + * that were requested. + * @param _stores + * @returns {undefined} + */ + function MiniCache(_stores) { + var miniCache = this; + /** Stores in this cache. */ + var stores = _stores; + /** Callbacks registered on then(...) */ + var dataCallBack = null; + var errorCallback = null; + /** Parent cache in chained caches. */ + miniCache.parent = null; + /** Allows for a do once and unsubscribe. */ + var doOnce = false; + /** + * Allows tracking of first round of notifications + * to avoid multiple notify calls when all data sets are + * ready. + */ + var storesReady = {}; + + //Activate (Listen) to the stores in this minicache. + for (var i = 0; i < stores.length; i++) { + var store = stores[i]; + if (store instanceof Store) { + storesReady[store.id] = false; + store.init().then(notify(store)).catch(error(store)); + miniCache[store.id] = store.getBox(); + } + } + + /** + * Promise Accessor + * @param {type} callback + * @returns {cache_L35.cacheService.MiniCache} + */ + miniCache.then = function (callback, errorCallback) { + dataCallBack = callback; + miniCache.catch(errorCallback); + return this; + }; + + miniCache.catch = function (callback) { + errorCallback = callback; + return this; + }; + + /** + * Promise Accessor + * @param {type} callback + * @returns {undefined} + */ + miniCache.once = function (callback) { + miniCache.then(callback); + doOnce = true; + return this; + }; + + /** + * Notifies callback in .then, only called when all are ready. + * @returns {undefined} + */ + function notify(store) { + var _store = store; + //returned function is what is called by the store, + //the wrapping allows us to keep track of which stores have + //triggered once. + return function () { + storesReady[_store.id] = true; + //Verify all stores are ready before notifiying listeners. + for (var j = 0; j < stores.length; j++) { + var store = stores[j]; + if (!storesReady[store.id] || stores[j].isReady() === false) { + return; + } + } + if (doOnce) { + miniCache.unlisten(); + } + miniCache.merge(portalUtils.call(dataCallBack)); + }; + } + + function error(store) { + var _store = store; + return function () { + portalUtils.call(errorCallback, _store); + }; + } + + miniCache.merge = function (mCache) { + if (mCache instanceof MiniCache) { + mCache.parent = miniCache; + if (miniCache.parent === null) { + //Copy boxes. + var mCacheStores = mCache.getStores(); + for (var i = 0; i < mCacheStores.length; i++) { + miniCache[mCacheStores[i].id] = mCache[mCacheStores[i].id]; + } + } else { + miniCache.parent.merge(mCache); + } + } + }; + + miniCache.getStores = function () { + return stores; + }; + + /** + * Allows you to purge listeners. + * @returns {undefined} + */ + miniCache.unlisten = function () { + for (var i = 0; i < stores.length; i++) { + stores[i].unlisten(); + } + }; + } + + /* + * Public Methods + */ + return { + /** + * Allows data to be requested. + * @param {type} stores + * @returns {MiniCache} Mini cache with requested data + */ + request: function (stores) { + var storesArray = []; + //Arrayify the data points. + if (angular.isArray(stores)) { + storesArray = stores.slice(); + } else { + storesArray.push(stores); + } + return new MiniCache(storesArray); + }, + /** + * Purges the cache. + */ + flush: function () { + caches.data.removeAll(); + caches.http.removeAll(); + } + }; + + } //end cacheService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/listener/listener.js b/portal_client/app/common/services/model/cache/listener/listener.js new file mode 100644 index 0000000..202840c --- /dev/null +++ b/portal_client/app/common/services/model/cache/listener/listener.js @@ -0,0 +1,145 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: + */ + angular.module('portal.common.services.model.cache') + .service('Listener', listenerService); + + function listenerService(Map, $q, debounce, $rootScope) { + + var Listener = function () { + var li = this; + + var successMap = new Map(); + var errorMap = new Map(); + var subscriberId = 0; + var notified = false; + var errored = false; + var errorArgs = []; + var successArgs = []; + + li.Listen = function () { + var pr = this; + + pr.successIds = []; + pr.errorIds = []; + + pr.then = function (success, error) { + if (angular.isFunction(success)) { + pr.successIds.push(subscriberId); + successMap.put(subscriberId, success); + subscriberId++; + } + pr.catch(error); + if (notified) { + call(success, successArgs); + } + return pr; + }; + + pr.catch = function (error) { + if (angular.isFunction(error)) { + pr.errorIds.push(subscriberId); + errorMap.put(subscriberId, error); + subscriberId++; + } + if (errored) { + call(error, errorArgs); + } + return pr; + }; + + pr.unlisten = function () { + var i = 0; + for (i = 0; i < pr.successIds.length; i++) { + successMap.removeByKey(pr.successIds[i]); + } + for (i = 0; i < pr.errorIds.length; i++) { + errorMap.removeByKey(pr.errorIds[i]); + } + }; + }; + + li.listen = function () { + return new li.Listen(); + }; + + li.notify = function (timeout) { + notified = true; + successArgs = arguments; + return callMap(successMap, successArgs, timeout); + }; + + li.notifyError = function () { + errored = true; + errorArgs = arguments; + return callMap(errorMap, arguments); + }; + + function callMap(map, args, timeout) { + var promises = []; + for (var i = 0; i < map.length; i++) { + promises.push(call(map.values[i], args, timeout)); + } + return $q.all(promises); + } + + function call(callee, args, timeout) { + var deferred = $q.defer(); + if (angular.isFunction(callee)) { + window.setTimeout(function () { + callee.apply(li, args); + Listener.digest(); + deferred.resolve(); + }, timeout); + } + return deferred.promise; + } + + }; + + var scopes; + Listener.registerScopes = function(_scopes) { + scopes = _scopes; + }; + + /** + * This allows us to only digest once we settle down. + * This debounce waits 100ms if it doesn't get another + * request to digest it will digest. Every request + * resets the timer to 100ms. This prevents us from + * hammering the CPU on load. + * + * Note: I have read the many many things on the internet that say + * we shouldn't do this. That we are breaking $angular convention, and I + * am ok with that. portal is not a public facing website. + * + * We are doing this because $timeout takes about 500ms to a 1s to make + * an async call. setTimeout is significantly faster. But that takes us + * outside of a digest cycle which means angular watches won't be re-evaluated. + * We must call $digest to get them to update. + * + * Conventionally you should call $scope.$apply, but that results in the same + * delays as $setTimout. + * + * Alternatively we could make this synchronous but doing so prohibits UI + * drawing/interaction which is not conducive to a good UI. + * + */ + Listener.digest = debounce(function () { + console.log('DIGESTING'); + for (var i = 0; i < scopes.length; i++) { + try { + scopes[i].$digest(); + } catch (err){ + console.log('WARN: Digest already in progerss'); + } + } + }, 100); + + return Listener; + + } //end listenerService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/account/account.js b/portal_client/app/common/services/model/cache/objects/account/account.js new file mode 100644 index 0000000..cbac5cb --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/account/account.js @@ -0,0 +1,91 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines an Account object, which defines the person who has + * authenticated with the server. + */ + angular.module('portal.common.services.model.cache') + .service('Account', accountService); + + function accountService(Map, Constants) { + + function Account(data) { + var account = this; + account.id = null; + account.authoritiesMap = new Map('id'); + account.username = ''; + account.password = ''; + account.lastName = ''; + account.firstName = ''; + account.auId = null; + account.email = ''; + account.role = ''; + account.expirationDate = ''; + account.effectiveDate = ''; + account.stompTopics = []; + account.permissions = {}; + account.enabled = false; + account.fromData = function (data) { + account.id = data.id; + account.authoritiesMap = new Map('id'); + account.username = data.username; + account.password = null; + account.lastName = data.lastName; + account.firstName = data.firstName; + account.auId = data.auId; + account.email = data.email; + account.role = data.role; + account.expirationDate = data.expirationDate; + account.effectiveDate = data.effectiveDate; + account.stompTopics = data.stompTopics; + account.permissions = data.permissions; + account.enabled = data.enabled; + if (angular.isArray(data.authorities)) { + for (var i = 0; i < data.authorities.length; i++) { + var authority = Account.Authorities.getTypeFromString(data.authorities[i]); + account.authoritiesMap.put(authority.id, authority); + } + } + }; + + if (angular.isDefined(data)) { + account.fromData(data); + } + + account.hasPermission = function(key) { + return account.authoritiesMap.containsKey(key); + }; + } + + /** + * Object defining a account authority + * @param {type} id + * @param {type} name + * @returns {Account.Authority} + */ + Account.Authority = function (id, name) { + var userType = this; + userType.id = id; + userType.name = name; + }; + Account.Authority.prototype.toString = function () { + return this.id; + }; + + /** + * Defines the statically typed account Authority as an enum (of sorts) + */ + Account.Authorities = { + getTypeFromString: function (typeAsString) { + return Account.Authorities[typeAsString]; + } + }; + + $.each(Constants.Permissions, function(key, value) { + Account.Authorities[key] = new Account.Authority(key, value.replace(/_/g, ' ')); + }); + + return Account; + } //end accountService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/application-log/application-log.js b/portal_client/app/common/services/model/cache/objects/application-log/application-log.js new file mode 100644 index 0000000..0526a2b --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/application-log/application-log.js @@ -0,0 +1,40 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines an Account object, which defines the person who has + * authenticated with the server. + */ + angular.module('portal.common.services.model.cache') + .service('ApplicationLog', appLogService); + + function appLogService(Map) { + + function ApplicationLog(data) { + var appLog = this; + appLog.id = ''; + appLog.eventName = ''; + appLog.event = ''; + appLog.severity = ''; + appLog.ipAddress = ''; + appLog.timestamp = ''; + appLog.loggedBy = ''; + + appLog.fromData = function (data) { + appLog.id = data.id; + appLog.loggedBy = data.loggedBy; + appLog.eventName = data.eventName; + appLog.event = data.event; + appLog.severity = data.severity; + appLog.ipAddress = data.ipAddress; + appLog.timestamp = moment(data.timestamp); + }; + + if (angular.isDefined(data)) { + appLog.fromData(data); + } + } + + return ApplicationLog; + } //end appLogService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/config/config.js b/portal_client/app/common/services/model/cache/objects/config/config.js new file mode 100644 index 0000000..cb036fb --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/config/config.js @@ -0,0 +1,44 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines an Account object, which defines the person who has + * authenticated with the server. + */ + angular.module('portal.common.services.model.cache') + .service('Config', configService); + + function configService(Map) { + + function Config(data) { + var config = this; + config.filepath = ''; + config.dbUsername = ''; + config.dbPassword = ''; + config.dbRootUser = ''; + config.dbRootPassword = ''; + config.dbHost = ''; + config.dbPort = 0; + config.userExpirationDays = 0; + config.logLevel = ''; + + config.fromData = function (data) { + config.filepath = data.filepath; + config.dbUsername = data.dbUsername; + config.dbPassword = data.dbPassword; + config.dbRootUser = data.dbRootUser; + config.dbRootPassword = data.dbRootPassword; + config.dbHost = data.dbHost; + config.dbPort = parseInt(data.dbPort); + config.userExpirationDays = parseInt(data.userExpirationDays); + config.logLevel = data.logLevel; + }; + + if (angular.isDefined(data)) { + config.fromData(data); + } + } + + return Config; + } //end configService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/credentials/credentials.js b/portal_client/app/common/services/model/cache/objects/credentials/credentials.js new file mode 100644 index 0000000..3651d70 --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/credentials/credentials.js @@ -0,0 +1,39 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines an creds object + */ + angular.module('portal.common.services.model.cache') + .service('Credentials', credentialsService); + + function credentialsService(Map) { + + function Credentials(data) { + var creds = this; + creds.id = null; + creds.uid = ''; + creds.username = ''; + creds.password = ''; + creds.name = ''; + creds.credentialType = ''; + creds.key = ''; + creds.passphrase = ''; + + creds.fromData = function (data) { + creds.id = data.id; + creds.uid = data.uid; + creds.username = data.username; + creds.name = data.name; + creds.credentialType = data.credentialType; + creds.key = ''; + creds.passphrase = ''; + }; + + if (angular.isDefined(data)) { + creds.fromData(data); + } + } + return Credentials; + } //end credentialsService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/drop-down-item/drop-down-item.js b/portal_client/app/common/services/model/cache/objects/drop-down-item/drop-down-item.js new file mode 100644 index 0000000..f766a80 --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/drop-down-item/drop-down-item.js @@ -0,0 +1,30 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines an creds object + */ + angular.module('portal.common.services.model.cache') + .service('DropDownItem', dropDownService); + + function dropDownService(Map) { + + function DropDownItem(data) { + var creds = this; + creds.id = null; + creds.name = ''; + creds.value = ''; + + creds.fromData = function (data) { + creds.id = data.id; + creds.name = data.name; + creds.value = data.value; + }; + + if (angular.isDefined(data)) { + creds.fromData(data); + } + } + return DropDownItem; + } //end dropDownService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/email-tmpl/email-tmpl.js b/portal_client/app/common/services/model/cache/objects/email-tmpl/email-tmpl.js new file mode 100644 index 0000000..9109c94 --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/email-tmpl/email-tmpl.js @@ -0,0 +1,40 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines an Account object, which defines the person who has + * authenticated with the server. + */ + angular.module('portal.common.services.model.cache') + .service('EmailTemplate', emailTmplService); + + function emailTmplService(Map, Account) { + + function EmailTemplate(data) { + var emailTmpl = this; + emailTmpl.id = null; + emailTmpl.emailName = ''; + emailTmpl.emailSubject = ''; + emailTmpl.emailMessage = ''; + emailTmpl.scriptId = ''; + emailTmpl.recipients = []; + emailTmpl.fromData = function (data) { + emailTmpl.id = data.id; + emailTmpl.emailName = data.emailName; + emailTmpl.emailSubject = data.emailSubject; + emailTmpl.emailMessage = data.emailMessage; + emailTmpl.scriptId = data.scriptId; + emailTmpl.recipients = []; + $.each(data.recipients, function(key, value){ + emailTmpl.recipients.push(new Account(value)); + }); + }; + + if (angular.isDefined(data)) { + emailTmpl.fromData(data); + } + } + + return EmailTemplate; + } //end emailTmplService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/git-info/git-info.js b/portal_client/app/common/services/model/cache/objects/git-info/git-info.js new file mode 100644 index 0000000..83521b6 --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/git-info/git-info.js @@ -0,0 +1,42 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines an creds object + */ + angular.module('portal.common.services.model.cache') + .service('GitInfo', gitInfoService); + + function gitInfoService(Map, Credentials) { + + function GitInfo(data) { + var gitInfo = this; + gitInfo.id = null; + gitInfo.name = ''; + gitInfo.scm = ''; + gitInfo.branch = ''; + gitInfo.directory = ''; + gitInfo.lastUpdated = null; + gitInfo.createDate = null; + gitInfo.createdBy = ''; + gitInfo.credentials = null; + + gitInfo.fromData = function (data) { + gitInfo.id = data.id; + gitInfo.name = data.name; + gitInfo.scm = data.scm; + gitInfo.branch = data.branch; + gitInfo.directory = data.directory; + gitInfo.credentials = new Credentials(data.credentials); + gitInfo.lastUpdated = data.lastUpdated; + gitInfo.createDate = data.createDate; + gitInfo.createdBy = data.createdBy; + }; + + if (angular.isDefined(data)) { + gitInfo.fromData(data); + } + } + return GitInfo; + } //end gitInfoService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/hash-type/hash-type.js b/portal_client/app/common/services/model/cache/objects/hash-type/hash-type.js new file mode 100644 index 0000000..eacfbff --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/hash-type/hash-type.js @@ -0,0 +1,28 @@ +(function () { + 'use strict'; + angular.module('portal.common.services.model.cache') + .service('HashType', hashTypeService); + + + function hashTypeService(Map) { + + function HashType(data) { + var hash = this; + hash.id = null; + hash.name = ''; + hash.displayName = ''; + hash.valid = false; + hash.length = null; + + if (angular.isDefined(data)) { + hash.id = data.id; + hash.name = data.name; + hash.displayName = data.displayName; + hash.valid = data.valid === 'true' || data.valid === true; + hash.length = parseInt(data.length); + } + } + + return HashType; + } +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/logger-info/logger-info.js b/portal_client/app/common/services/model/cache/objects/logger-info/logger-info.js new file mode 100644 index 0000000..8890542 --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/logger-info/logger-info.js @@ -0,0 +1,30 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines an Account object, which defines the person who has + * authenticated with the server. + */ + angular.module('portal.common.services.model.cache') + .service('LoggerInfo', loggerService); + + function loggerService(Map) { + + function LoggerInfo(data) { + var info = this; + info.logger = ''; + info.level = ''; + + info.fromData = function (data) { + info.logger = data.logger; + info.level = data.level; + }; + + if (angular.isDefined(data)) { + info.fromData(data); + } + } + + return LoggerInfo; + } //end configService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/permission-object/permission-object.js b/portal_client/app/common/services/model/cache/objects/permission-object/permission-object.js new file mode 100644 index 0000000..3fd12a9 --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/permission-object/permission-object.js @@ -0,0 +1,85 @@ +(function () { + 'use strict'; + + angular.module('portal.common.services.model.cache') + .service('permissionObjects', permissionService); + + function permissionService(Map) { + /* jshint validthis: true */ + var permissionObjects = this; + + this.PermissionGroup = function(data) { + this.id = null; + this.name = ''; + this.displayName = ''; + this.enabled = false; + this.permissions = []; + if (data) { + this.id = data.id; + this.name = data.name; + this.displayName = data.displayName; + this.enabled = data.enabled; + this.permissions = []; + if(data.permissions) { + for (var i = 0; i < data.permissions.length; i++) { + this.permissions.push(new permissionObjects.PermissionGroupPermission(data.permissions[i])); + } + } + } + }; + + this.PermissionGroupPermission = function(data) { + this.type = 'PermissionGroupPermission'; + this.id = null; + this.permissionId = null; + this.permissionGroupId = null; + this.name = ''; + this.displayName = ''; + this.permissionCategory = ''; + this.allowed = false; + if (data) { + this.type = 'PermissionGroupPermission'; + this.id = data.id; + this.permissionId = data.permissionId; + this.permissionGroupId = data.permissionGroupId; + this.name = data.name; + this.displayName = data.displayName; + this.permissionCategory = data.permissionCategory; + this.allowed = data.allowed; + } + }; + + this.PermissionCategory = function(data) { + this.id = null; + this.name = ''; + this.displayName = ''; + this.permissions = []; + if (data) { + this.id = data.id; + this.name = data.name; + this.displayName = data.displayName; + this.permissions = []; + if (data.permissions) { + for (var i = 0; i < data.permissions.length; i++) { + this.permissions.push(new permissionObjects.Permission(data.permissions[i])); + } + } + } + }; + + this.Permission = function(data) { + this.type = 'Permission'; + this.id = null; + this.name = ''; + this.displayName = ''; + this.permissionCategory = ''; + if (data) { + this.id = data.id; + this.name = data.name; + this.displayName = data.displayName; + this.permissionCategory = data.permissionCategory; + } + }; + + } //end permissionService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/resource-file/resource-file.js b/portal_client/app/common/services/model/cache/objects/resource-file/resource-file.js new file mode 100644 index 0000000..e6def85 --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/resource-file/resource-file.js @@ -0,0 +1,33 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines an resource file object, + */ + angular.module('portal.common.services.model.cache') + .service('ResourceFile', resourceFileService); + + function resourceFileService(Map) { + + function ResourceFile(data) { + var resourceFile = this; + resourceFile.id = ''; + resourceFile.fileName = ''; + resourceFile.fileLocation = ''; + resourceFile.fileType = ''; + resourceFile.dateCreated = ''; + resourceFile.valid = false; + + if (angular.isDefined(data)) { + resourceFile.id = data.id; + resourceFile.fileName = data.fileName; + resourceFile.fileLocation = data.fileLocation; + resourceFile.fileType = data.fileType; + resourceFile.dateCreated = data.dateCreated; + resourceFile.valid = data.valid; + } + } + + return ResourceFile; + } //end patchFileService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/script/script.js b/portal_client/app/common/services/model/cache/objects/script/script.js new file mode 100644 index 0000000..af86388 --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/script/script.js @@ -0,0 +1,207 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines an script object + */ + angular.module('portal.common.services.model.cache') + .service('scriptObjects', scriptService); + + function scriptService(Map, Credentials, ResourceFile, EmailTemplate) { + /* jshint validthis: true */ + var scriptObjects = this; + + scriptObjects.Script = function (data) { + var script = this; + script.id = null; + script.name = ''; + script.command = ''; + script.baseDirectory = ''; + script.outputDirectory = ''; + script.scriptType = ''; + script.parameters = []; + script.schedule = []; + script.scriptVariables = []; + script.scriptChain = []; + script.scriptEmails = []; + script.remote = null; + script.scp = null; + if (data) { + script.id = data.id; + script.name = data.name; + script.command = data.command; + script.baseDirectory = data.baseDirectory; + script.outputDirectory = data.outputDirectory; + script.scriptType = data.scriptType; + script.remote = new scriptObjects.ScriptRemote(data.remote); + script.scp = new scriptObjects.ScriptScp(data.scp); + script.parameters = []; + script.schedule = []; + script.scriptVariables = []; + script.scriptEmails = []; + + $.each(data.parameters, function(key, value) { + script.parameters.push(new scriptObjects.ScriptParameter(value)); + }); + + $.each(data.schedule, function(key, value) { + script.schedule.push(new scriptObjects.ScriptSchedule(value)); + }); + + $.each(data.scriptVariables, function(key, value) { + script.scriptVariables.push(new scriptObjects.ScriptEnvironmentVariable(value)); + }); + + $.each(data.scriptChain, function(key, value) { + script.scriptChain.push(new scriptObjects.ScriptChain(value)); + }); + + $.each(data.scriptEmails, function(key, value) { + script.scriptEmails.push(new EmailTemplate(value)); + }); + } + }; + + scriptObjects.ScriptSchedule = function(data) { + var schedule = this; + schedule.id = null; + schedule.jobId = ''; + schedule.startDatetime = moment().toDate(); + schedule.endDatetime = null; + schedule.frequency = null; + schedule.maxExecutions = null; + schedule.currentExecutionCount = null; + schedule.lastRun = null; + schedule.script = null; + schedule.executionStatus = null; + if (data) { + schedule.id = data.id; + schedule.jobId = data.jobId; + schedule.startDatetime = data.startDatetime; + schedule.endDatetime = data.endDatetime; + schedule.frequency = data.frequency; + schedule.maxExecutions = data.maxExecutions; + schedule.currentExecutionCount = data.currentExecutionCount; + schedule.lastRun = data.lastRun; + schedule.script = data.script; + schedule.executionStatus = data.executionStatus; + } + }; + + scriptObjects.ScriptParameter = function(data) { + var param = this; + param.id = null; + param.parameter = ''; + param.script = ''; + if (data) { + param.id = data.id; + param.parameter = data.parameter; + param.script = data.script; + } + }; + + scriptObjects.ScriptChain = function(data) { + var chain = this; + chain.id = null; + chain.parentScript = undefined; + chain.childScript = undefined; + chain.executeAfterMin = undefined; + chain.exitStatus = undefined; + if (data) { + chain.id = data.id; + chain.parentScript = data.parentScript; + chain.childScript = data.childScript; + chain.executeAfterMin = data.executeAfterMin; + chain.exitStatus = data.exitStatus; + } + }; + + scriptObjects.ScriptEnvironmentVariable = function(data) { + var envVar = this; + envVar.id = null; + envVar.name = ''; + envVar.value = ''; + envVar.createdBy = null; + envVar.script = null; + envVar.lastUpdated = ''; + envVar.dateCreated = ''; + envVar.isNew = true; + if (data) { + envVar.id = data.id; + envVar.name = data.name; + envVar.value = data.value; + envVar.createdBy = data.createdBy; + envVar.script = data.script; + envVar.lastUpdated = data.lastUpdated; + envVar.dateCreated = data.dateCreated; + envVar.isNew = false; + } + }; + + scriptObjects.ScriptStatus = function(data) { + var status = this; + status.id = undefined; + status.exitStatus = undefined; + status.results = ''; + status.startTime = undefined; + status.endTime = undefined; + status.resourceFiles = []; + if (data) { + status.id = data.id; + status.exitStatus = data.exitStatus; + status.results = data.results; + status.startTime = data.startTime; + status.endTime = data.endTime; + if (data.resourceFiles) { + $.each(data.resourceFiles, function(key, value) { + status.resourceFiles.push(new ResourceFile(value)); + }); + } + } + }; + + scriptObjects.ScriptRemote = function(data) { + var remote = this; + remote.scriptId = undefined; + remote.scriptCredentials = null; + remote.url = undefined; + remote.port = undefined; + if (data) { + remote.scriptId = data.scriptId; + remote.url = data.url; + remote.port = data.port; + if (data.scriptCredentials) { + remote.scriptCredentials = new Credentials(data.scriptCredentials); + } + } + }; + + scriptObjects.ScriptScp = function(data) { + var scp = this; + scp.id = undefined; + scp.scriptId = undefined; + scp.fromHost = ''; + scp.fromPort = 0; + scp.toHost = ''; + scp.toPort = 0; + scp.dataRegex = ''; + scp.toCredentials = undefined; + scp.fromCredentials = undefined; + if (data) { + scp.id = data.id; + scp.scriptId = data.scriptId; + scp.fromHost = data.fromHost; + scp.fromPort = data.fromPort; + scp.toHost = data.toHost; + scp.toPort = data.toPort; + scp.dataRegex = data.dataRegex; + if (data.toCredentials) { + scp.toCredentials = new Credentials(data.toCredentials); + } + if (data.fromCredentials) { + scp.fromCredentials = new Credentials(data.fromCredentials); + } + } + }; + } //end scriptService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/objects/upload-file/upload-file.js b/portal_client/app/common/services/model/cache/objects/upload-file/upload-file.js new file mode 100644 index 0000000..2765226 --- /dev/null +++ b/portal_client/app/common/services/model/cache/objects/upload-file/upload-file.js @@ -0,0 +1,75 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: + */ + angular.module('portal.common.services.model.cache') + .service('UploadFile', uploadFileService); + + function uploadFileService() { + + function UploadFile() { + var file = this; + file.name = ''; + file.fileSize = null; + file.guid = ''; + file.url = ''; + file.rawFile = null; + file.uploadProgress = 0; + file.percentComplete = 0; + file.uploaded = null; + file.reason = ''; + file.trackUploadProcessing = false; + file.uploadMax = 0; + + file.init = function (rawFile, trackUploadProcessing) { + file.trackUploadProcessing = trackUploadProcessing; + file.rawFile = rawFile; + file.name = rawFile.name; + file.fileSize = rawFile.size; + // The file.uploadMax represents when the file upload is complete. If tracking upload + // processing, the server sends percent complete values as triggers periodically so the max is 100 (%). + // Otherwise, just track the uploaded byte progress so the max is the file size. + file.uploadMax = trackUploadProcessing ? 100 : file.fileSize; + file.type = rawFile.type; + return file; + }; + file.uploaded = function (data, baseURI) { + file.uploaded = true; + baseURI = baseURI === undefined ? '' : baseURI; + if (data !== undefined) { + file.name = data.original_name; //jshint ignore:line + file.fileSize = data.file_size; //jshint ignore:line + file.guid = data.guid; + file.url = baseURI + data.guid; + file.uploadProgress = file.fileSize; + } + return file; + }; + file.setUploadProgress = function (uploadProgress) { + file.uploadProgress = uploadProgress; + file.percentComplete = Math.min(Math.floor((file.uploadProgress / file.fileSize) * 100), 100); + }; + file.uploadFailed = function (reason) { + file.uploaded = false; + file.reason = reason; + if (reason === undefined) { + file.reason = 'unknown failure'; + } + }; + file.finished = function () { + return file.uploaded === true ? true : false; + }; + file.inProgress = function () { + return file.upload === null; + }; + file.failed = function () { + return file.uploaded === false ? true : false; + }; + } + + return UploadFile; + + } //end uploadFileService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/store/store.js b/portal_client/app/common/services/model/cache/store/store.js new file mode 100644 index 0000000..21625c3 --- /dev/null +++ b/portal_client/app/common/services/model/cache/store/store.js @@ -0,0 +1,137 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines a store, or access point to get data out of the cache and from an external source. + */ + angular.module('portal.common.services.model.cache') + .service('Store', storeService); + + function storeService($timeout, Box) { + + var cache = null; + + /** + * Store defines the information required to access data from the cache (id) and + * pull it from an external source if it hasn't been pulled. + * @param {type} args {id: '', source: function} + * @returns {store_L35.storeService.Store} + */ + function Store(args) { + var store = this; + + /**Attach the args and a few for convenience directly to the Store (below). */ + store.args = args; + /** + * Unique ID for this store, this will be the . accessor on the mini cache. + */ + store.id = args.id; + /** + * A function with registerable callback that provides the data. + */ + store.source = args.source; + /** + * Empty params object, used in conjunction with Id to generate unique key for store. + * Passed in to the source as the args. + * @type Number|Number + */ + store.params = args.params; + /** + * Key used for accessing data from the cache. + */ + store.key = getKey(); + /** + * Reference to the box accessed by the key. + */ + store.box = cache.get(store.key); + /** + * Listeners for this store to unsubscribe. + */ + store.boxListener = null; + /** + * Registers a callback to be notified of data changes. If the data has not been initialized it is + * initialized, once and only once. Subsequent data changes + * @param {type} callback + * @returns {unresolved} + */ + store.init = function () { + store.boxListener = store.box.init(); + return store.boxListener; + }; + + /** + * Forces a refresh from source. + * @returns {undefined} + */ + store.refresh = function () { + var box = store.getBox(); + return box.refresh(); + }; + + /** + * Updates store on the cache, and notifies listeners. + * Could be called to update data on trigger. + * @param {type} data + * @returns {undefined} + */ + store.update = function (data) { + store.getBox().update(data); + }; + + /** + * @returns {Boolean} True if ready, false if pending or not initialized. + */ + store.isReady = function () { + return store.getBox().isDone(); + }; + + store.getBox = function () { + return store.box; + }; + + store.unlisten = function () { + if (store.boxListener !== null) { + store.boxListener.unlisten(); + } + }; + + store.getParams = function () { + return store.params; + }; + + function getKey() { + var key = store.id; + for (var i in store.params) { + key += '_' + i + '_' + store.params[i]; + } + /** + * Init box if doesn't already exist. + * Box persists between instnaces. + */ + if (!angular.isDefined(cache.get(key))) { + cache.put(key, new Box(args)); + } + return key; + } + + } + + /** + * Static function for registering the cache. + * @param {type} _cache + * @returns {undefined} + */ + Store.registerCache = function (_cache) { + cache = _cache; + }; + + Store.notifyStores = function(stores) { + for (var i in stores) { + stores[i].getBox().notify(); + } + }; + + return Store; + + } //end storeService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/stores/account-stores/account-stores.js b/portal_client/app/common/services/model/cache/stores/account-stores/account-stores.js new file mode 100644 index 0000000..fae8129 --- /dev/null +++ b/portal_client/app/common/services/model/cache/stores/account-stores/account-stores.js @@ -0,0 +1,60 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Stores for account. + */ + angular.module('portal.common.services.model.cache') + .service('accountStores', accountStoresService); + + function accountStoresService(stores, Store, Account, accountProvider, triggerManager, Triggers, Map) { + + var accountStores = { + Account: stores.inherit('account', function () { + Store.call(this, { + id: 'account', + source: accountProvider.isLoggedIn, + processGui: function (data) { + var act = new Account(data); + triggerManager.subscribe(act.stompTopics); + return act; + }}); + }), + UserPool: stores.inherit('userPool', function(){ + Store.call(this, { + id: 'userPool', + source: accountProvider.getAllUsers, + processGui: function(data) { + return Map.create(data, Account, 'id'); + } + }); + }), + }; + + Triggers.listen([Triggers.USER_ADDED], function(trigger, payload){ + var data = payload.resources; + var users = new accountStores.UserPool(); + if (users.isReady()) { + for (var i = 0; i < data.length; i++) { + var user = new Account(data[i]); + users.getBox().data.put(user.id, user); + } + users.getBox().notify(); + } + }); + + Triggers.listen([Triggers.USER_DELETED], function(trigger, payload){ + var data = payload.resources; + var users = new accountStores.UserPool(); + if (users.isReady()) { + for (var i = 0; i < data.length; i++) { + var user = new Account(data[i]); + users.getBox().data.removeByKey(user.id, user); + } + users.getBox().notify(); + } + }); + + return accountStores; + } //end accountStoresService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/stores/application-log-stores/application-log-stores.js b/portal_client/app/common/services/model/cache/stores/application-log-stores/application-log-stores.js new file mode 100644 index 0000000..b9d4b2f --- /dev/null +++ b/portal_client/app/common/services/model/cache/stores/application-log-stores/application-log-stores.js @@ -0,0 +1,33 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Stores for account. + */ + angular.module('portal.common.services.model.cache') + .service('appLogStores', appLogStoresService); + + function appLogStoresService(stores, Store, ApplicationLog, appLogProvider, Map, Triggers) { + + var appLogStores = { + Logs: stores.inherit('logs', function(){ + Store.call(this, { + id: 'logs', + source: appLogProvider.getAllLogs, + processGui: function(data) { + return Map.create(data, ApplicationLog, 'id'); + } + }); + }), + }; + Triggers.listen([Triggers.APP_LOGS_UPDATED, Triggers.APP_LOGS_PURGED], function(trigger, payload){ + var data = payload.resources; + var logs = new appLogStores.Logs(); + if (logs.isReady()) { + logs.getBox().refresh(); + } + }); + + return appLogStores; + } //end accountStoresService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/stores/config-stores/config-stores.js b/portal_client/app/common/services/model/cache/stores/config-stores/config-stores.js new file mode 100644 index 0000000..999b9d4 --- /dev/null +++ b/portal_client/app/common/services/model/cache/stores/config-stores/config-stores.js @@ -0,0 +1,35 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Stores for account. + */ + angular.module('portal.common.services.model.cache') + .service('configStores', configStoresService); + + function configStoresService(stores, Store, Config, configProvider, Map) { + + var copnfigStores = { + Configs: stores.inherit('configs', function () { + Store.call(this, { + id: 'configs', + source: configProvider.getConfiguration, + processGui: function (data) { + var act = new Config(data); + return act; + }}); + }), + Config: stores.inherit('config', function (configKey) { + Store.call(this, { + id: 'config', + source: configProvider.getConfigurationValue, + params: configKey, + processGui: function (data) { + var conf = new Config(data); + return conf; + }}); + }), + }; + return copnfigStores; + } //end configStoresService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/stores/credential-stores/credential-stores.js b/portal_client/app/common/services/model/cache/stores/credential-stores/credential-stores.js new file mode 100644 index 0000000..bfed746 --- /dev/null +++ b/portal_client/app/common/services/model/cache/stores/credential-stores/credential-stores.js @@ -0,0 +1,34 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Stores for account. + */ + angular.module('portal.common.services.model.cache') + .service('credentialStores', credentialStoresService); + + function credentialStoresService(stores, Store, Credentials, credentialProvider, Map, DropDownItem) { + + var credentialsStores = { + Credentials: stores.inherit('credentials', function () { + Store.call(this, { + id: 'credentials', + source: credentialProvider.getCredentials, + processGui: function (data) { + var map = Map.create(data, Credentials, 'uid'); + return map; + }}); + }), + CredentialTypes: stores.inherit('credentialTypes', function(){ + Store.call(this, { + id: 'credentialTypes', + source: credentialProvider.getCredentialTypes, + processGui: function(data) { + return Map.create(data, DropDownItem, 'id'); + } + }); + }), + }; + return credentialsStores; + } //end credentialsStoresService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/stores/git-stores/git-stores.js b/portal_client/app/common/services/model/cache/stores/git-stores/git-stores.js new file mode 100644 index 0000000..f0f7d7e --- /dev/null +++ b/portal_client/app/common/services/model/cache/stores/git-stores/git-stores.js @@ -0,0 +1,25 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Stores for account. + */ + angular.module('portal.common.services.model.cache') + .service('gitStores', gitStoresService); + + function gitStoresService(stores, Store, GitInfo, gitProvider, Map) { + + var gitStores = { + Repos: stores.inherit('repos', function () { + Store.call(this, { + id: 'repos', + source: gitProvider.getAllRepos, + processGui: function (data) { + var map = Map.create(data, GitInfo, 'id'); + return map; + }}); + }), + }; + return gitStores; + } //end gitStoresService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/stores/logger-stores/logger-stores.js b/portal_client/app/common/services/model/cache/stores/logger-stores/logger-stores.js new file mode 100644 index 0000000..56f1a1d --- /dev/null +++ b/portal_client/app/common/services/model/cache/stores/logger-stores/logger-stores.js @@ -0,0 +1,26 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Stores for account. + */ + angular.module('portal.common.services.model.cache') + .service('loggerStores', loggerStoreService); + + function loggerStoreService(stores, Store, LoggerInfo, loggerProvider, Map) { + + var loggerStores = { + Loggers: stores.inherit('loggers', function(){ + Store.call(this, { + id: 'loggers', + source: loggerProvider.getLoggers, + processGui: function(data) { + return Map.create(data, LoggerInfo, 'logger'); + } + }); + }) + }; + + return loggerStores; + } //end loggerStopresService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/stores/permission-stores/permission-stores.js b/portal_client/app/common/services/model/cache/stores/permission-stores/permission-stores.js new file mode 100644 index 0000000..f1cbfab --- /dev/null +++ b/portal_client/app/common/services/model/cache/stores/permission-stores/permission-stores.js @@ -0,0 +1,65 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Stores for account. + */ + angular.module('portal.common.services.model.cache') + .service('permissionStores', permissionStoresService); + + function permissionStoresService(stores, Store, permissionObjects, permissionProvider, triggerManager, Triggers, Map) { + + var permissionStores = { + PermissionGroups: stores.inherit('permissionGroups', function () { + Store.call(this, { + id: 'permissionGroups', + source: permissionProvider.getPermissionGroups, + processGui: function (data) { + return Map.create(data, permissionObjects.PermissionGroup, 'id'); + }}); + }), + AllPermissionGroups: stores.inherit('allPermissionGroups', function () { + Store.call(this, { + id: 'allPermissionGroups', + source: permissionProvider.getAllPermissionGroups, + processGui: function (data) { + return Map.create(data, permissionObjects.PermissionGroup, 'id'); + }}); + }), + PermissionCategories: stores.inherit('permissionCategories', function () { + Store.call(this, { + id: 'permissionCategories', + source: permissionProvider.getPermissionCategories, + processGui: function (data) { + return Map.create(data, permissionObjects.PermissionCategory, 'id'); + }}); + }) + }; + + Triggers.listen([Triggers.PERMISSIONS_CHANGED, Triggers.PERMISSIONS_ADDED, Triggers.PERMISSIONS_UPDATED], function(trigger, payload){ + var data = payload.resources; + var permGroups = new permissionStores.PermissionGroups(); + if (permGroups.isReady()) { + for (var i = 0; i < data.length; i++) { + var permGroup = new permissionObjects.PermissionGroup(data[i]); + permGroups.getBox().data.put(permGroup.id, permGroup); + } + permGroups.getBox().notify(); + } + }); + + Triggers.listen([Triggers.PERMISSIONS_DELETED], function(trigger, payload){ + var data = payload.resources; + var permGroups = new permissionStores.PermissionGroups(); + if (permGroups.isReady()) { + for (var i = 0; i < data.length; i++) { + var permGroup = new permissionObjects.PermissionGroup(data[i]); + permGroups.getBox().data.removeByKey(permGroup.id, permGroup); + } + permGroups.getBox().notify(); + } + }); + + return permissionStores; + } //end permissionStoresService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/stores/resource-file-stores/resource-file-stores.js b/portal_client/app/common/services/model/cache/stores/resource-file-stores/resource-file-stores.js new file mode 100644 index 0000000..8dbc7a5 --- /dev/null +++ b/portal_client/app/common/services/model/cache/stores/resource-file-stores/resource-file-stores.js @@ -0,0 +1,33 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Stores for resource file. + */ + angular.module('portal.common.services.model.cache') + .service('resourceFileStores', resourceFileStoresService); + + function resourceFileStoresService(stores, Store, ResourceFile, fileProvider, Map) { + + var resourceFileStores = { + PatchFiles: stores.inherit('patchFiles', function (args) { + Store.call(this, { + id: 'patchFiles', + source: fileProvider.getAllFiles, + processGui: function (data) { + return Map.create(data, ResourceFile, 'id'); + }}); + }), + Directories: stores.inherit('patchDirectories', function () { + Store.call(this, { + id: 'patchDirectories', + source: fileProvider.getDirectories, + processGui: function (data) { + return data; + }}); + }), + }; + + return resourceFileStores; + } //end patchFileStoresService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/stores/script-stores/script-stores.js b/portal_client/app/common/services/model/cache/stores/script-stores/script-stores.js new file mode 100644 index 0000000..54ea351 --- /dev/null +++ b/portal_client/app/common/services/model/cache/stores/script-stores/script-stores.js @@ -0,0 +1,130 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Stores for account. + */ + angular.module('portal.common.services.model.cache') + .service('scriptStores', scriptStoresService); + + function scriptStoresService(stores, Store, scriptObjects, scriptProvider, DropDownItem, Map, Triggers, portalUtils) { + + var scriptStores = { + Scripts: stores.inherit('scripts', function(){ + Store.call(this, { + id: 'scripts', + source: scriptProvider.getAllScripts, + processGui: function(data) { + return Map.create(data, scriptObjects.Script, 'id'); + } + }); + }), + ScriptTypes: stores.inherit('scriptTypes', function(){ + Store.call(this, { + id: 'scriptTypes', + source: scriptProvider.getScriptType, + processGui: function(data) { + return Map.create(data, DropDownItem, 'id'); + } + }); + }), + ScheduleFrequencies: stores.inherit('scheduleFrequencies', function(){ + Store.call(this, { + id: 'scheduleFrequencies', + source: scriptProvider.getScheduleFrequencies, + processGui: function(data) { + return Map.create(data, DropDownItem, 'id'); + } + }); + }), + ScriptStatus: stores.inherit('scriptStatus', function(jobId){ + Store.call(this, { + id: 'scriptStatus', + source: scriptProvider.getScriptStatus, + params: jobId, + processGui: function(data) { + return Map.create(data, scriptObjects.ScriptStatus, 'id'); + } + }); + }) + }; + + Triggers.listen([Triggers.SCRIPT_ADDED, Triggers.SCRIPT_UPDATED], function(trigger, payload){ + var data = payload.resources; + var scripts = new scriptStores.Scripts(); + if (scripts.isReady()) { + for (var i = 0; i < data.length; i++) { + var script = new scriptObjects.Script(data[i]); + scripts.getBox().data.put(script.id, script); + } + scripts.getBox().notify(); + } + }); + + Triggers.listen([Triggers.SCRIPT_DELETED], function(trigger, payload){ + var data = payload.resources; + var scripts = new scriptStores.Scripts(); + if (scripts.isReady()) { + for (var i = 0; i < data.length; i++) { + var script = new scriptObjects.Script(data[i]); + scripts.getBox().data.removeByKey(script.id, script); + } + scripts.getBox().notify(); + } + }); + + Triggers.listen([Triggers.SCRIPT_SCHEDULE_DELETED], function(trigger, payload){ + var data = payload.resources; + var scripts = new scriptStores.Scripts(); + var schedule = new scriptObjects.ScriptSchedule(data[0]); + if (scripts.isReady()) { + var scriptBox = scripts.getBox().data.values; + for (var i = 0; i < scriptBox.length; i++) { + if(schedule.script === scriptBox[i].id) { + for (var j = 0; j < scriptBox[i].schedule.length; j++) { + if (schedule.jobId === scriptBox[i].schedule[j].jobId) { + scriptBox[i].schedule.splice(j, 1); + break; + } + } + break; + } + } + scripts.getBox().notify(); + } + }); + + Triggers.listen([Triggers.SCRIPT_COMPLETED], function(trigger, payload) { + if (!payload.resources) { + return; + } + + var data = payload.resources; + var scripts = new scriptStores.Scripts(); + var script = new scriptObjects.Script(data[1]); + if (scripts.isReady()) { + var scriptsBox = scripts.getBox().data.values; + for (var x = 0; x < scriptsBox.length; x++) { + if (scriptsBox[x].id === script.id) { + scriptsBox[x].schedule = script.schedule; + } + } + scripts.getBox().notify(); + } + + var scriptName = script.name; + var jobId = data[0]; + var statusStr = 'Failed'; + for (var j = 0; j < script.schedule.length; j++) { + var sched = script.schedule[j]; + if (sched.jobId === jobId && sched.executionStatus === 0) { + statusStr = 'Success'; + break; + } + } + portalUtils.toast('Script ' + scriptName +' completed with status of ' + statusStr, ['top', 'left'], 2000); + }); + + return scriptStores; + } //end scriptStoresService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/stores/stores.js b/portal_client/app/common/services/model/cache/stores/stores.js new file mode 100644 index 0000000..8a89430 --- /dev/null +++ b/portal_client/app/common/services/model/cache/stores/stores.js @@ -0,0 +1,27 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Provides accessors to the cache(cacheService) getting data if + * it doesn't exist, providing promises to the data, and notification when + * the data changes. + * + */ + angular.module('portal.common.services.model.cache') + .service('stores', storesService); + + function storesService(Store, $log, portalUtils) { + var stores = {}; + return { + inherit: function (id, ChildStore) { + if (angular.isDefined(stores[id])) { + $log.error('Duplicate store with id - ' + id); + } else { + stores[id] = portalUtils.inherit(ChildStore, Store); + return stores[id]; + } + } + }; + + } //end storesService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/cache/stores/system-stores/system-stores.js b/portal_client/app/common/services/model/cache/stores/system-stores/system-stores.js new file mode 100644 index 0000000..0aafc68 --- /dev/null +++ b/portal_client/app/common/services/model/cache/stores/system-stores/system-stores.js @@ -0,0 +1,92 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Stores for account. + */ + angular.module('portal.common.services.model.cache') + .service('systemStores', systemStoresService); + + function systemStoresService(stores, Store, scriptObjects, systemProvider, Map, EmailTemplate, Triggers) { + + var systemStores = { + EnvironmentVariables: stores.inherit('envvars', function(){ + Store.call(this, { + id: 'envvars', + source: systemProvider.getAllEnvVars, + processGui: function(data) { + return Map.create(data, scriptObjects.ScriptEnvironmentVariable, 'id'); + } + }); + }), + SMTPConfig: stores.inherit('smtpConfig', function(){ + Store.call(this, { + id: 'smtpConfig', + source: systemProvider.getSmtpConfig, + processGui: function(data) { + return Map.create(data, scriptObjects.ScriptEnvironmentVariable, 'id'); + } + }); + }), + EmailTemplates: stores.inherit('emailTmpls', function(){ + Store.call(this, { + id: 'emailTmpls', + source: systemProvider.getEmailTemplates, + processGui: function(data) { + return Map.create(data, EmailTemplate, 'id'); + } + }); + }), + }; + + Triggers.listen([Triggers.EMAIL_TEMPLATE_UPDATED], function(trigger, payload){ + var data = payload.resources; + var tmpls = new systemStores.EmailTemplates(); + if (tmpls.isReady()) { + for (var i = 0; i < data.length; i++) { + var tmpl = new EmailTemplate(data[i]); + tmpls.getBox().data.put(tmpl.id, tmpl); + } + tmpls.getBox().notify(); + } + }); + + Triggers.listen([Triggers.EMAIL_TEMPLATE_DELETED], function(trigger, payload){ + var data = payload.resources; + var tmpls = new systemStores.EmailTemplates(); + if (tmpls.isReady()) { + for (var i = 0; i < data.length; i++) { + var tmpl = new EmailTemplate(data[i]); + tmpls.getBox().data.removeByKey(tmpl.id, tmpl); + } + tmpls.getBox().notify(); + } + }); + + Triggers.listen([Triggers.ENV_VARS_ADDED], function(trigger, payload){ + var data = payload.resources; + var envvars = new systemStores.EnvironmentVariables(); + if (envvars.isReady()) { + for (var i = 0; i < data.length; i++) { + var envvar = new scriptObjects.ScriptEnvironmentVariable(data[i]); + envvars.getBox().data.put(envvar.id, envvar); + } + envvars.getBox().notify(); + } + }); + + Triggers.listen([Triggers.ENV_VARS_DELETED], function(trigger, payload){ + var data = payload.resources; + var envvars = new systemStores.EnvironmentVariables(); + if (envvars.isReady()) { + for (var i = 0; i < data.length; i++) { + var envvar = new scriptObjects.ScriptEnvironmentVariable(data[i]); + envvars.getBox().data.removeByKey(envvar.id, envvar); + } + envvars.getBox().notify(); + } + }); + + return systemStores; + } //end systemStoresService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/account-provider/account-provider.js b/portal_client/app/common/services/model/rest/account-provider/account-provider.js new file mode 100644 index 0000000..72fe0db --- /dev/null +++ b/portal_client/app/common/services/model/rest/account-provider/account-provider.js @@ -0,0 +1,76 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines the restful calls for the account server module. + */ + angular.module('portal.common.services.model.rest') + .factory('accountProvider', accountProviderService); + + function accountProviderService(rest, Account) { + var baseUrl = '/account'; + return { + isLoggedIn: function () { + return rest.get({ + id: baseUrl + '/isLoggedIn.do', + convert: function (data) { + return new Account(data); + } + }); + }, + getAllUsers: function() { + return rest.get({ + id: baseUrl + '/getAll.do', + }); + }, + /** + * Login. + * @param {type} args password, username + * @returns {undefined} + */ + login: function (username, password) { + return rest.put({ + id: baseUrl + '/login.do', + block: true, + data: {username: username, password: password} + }); + }, + /** + * Logout. + * @param {type} + * @returns {undefined} + */ + logout: function () { + return rest.put({ + id: baseUrl + '/logout.do' + }); + }, + addUser: function(user) { + return rest.put({ + id: baseUrl + '/addUser.do', + data: user + }); + }, + deleteUser: function(user) { + return rest.put({ + id: baseUrl + '/deleteUser.do', + data: user + }); + }, + getRoleData: function(data) { + return rest.put({ + id: baseUrl + '/getRoleData.do', + data: {type: data} + }); + }, + getUserInfo: function(args) { + return rest.get({ + id: baseUrl + '/getUserInfo.do', + data: args, + cache: false + }); + } + }; + + } //end accountProviderService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/application-log-provider/application-log-provider.js b/portal_client/app/common/services/model/rest/application-log-provider/application-log-provider.js new file mode 100644 index 0000000..ef6cb9e --- /dev/null +++ b/portal_client/app/common/services/model/rest/application-log-provider/application-log-provider.js @@ -0,0 +1,26 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines the restful calls for the account server module. + */ + angular.module('portal.common.services.model.rest') + .factory('appLogProvider', appLogProviderService); + + function appLogProviderService(rest) { + var baseUrl = '/logs'; + return { + getAllLogs: function () { + return rest.get({ + id: baseUrl + '/getAll.do', + }); + }, + purgeLogs: function(date) { + return rest.post({ + id: baseUrl + '/purge.do', + data: date + }); + } + }; + } //end configProviderService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/config-provider/config-provider.js b/portal_client/app/common/services/model/rest/config-provider/config-provider.js new file mode 100644 index 0000000..b33fa09 --- /dev/null +++ b/portal_client/app/common/services/model/rest/config-provider/config-provider.js @@ -0,0 +1,33 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines the restful calls for the account server module. + */ + angular.module('portal.common.services.model.rest') + .factory('configProvider', configProviderService); + + function configProviderService(rest, Config) { + var baseUrl = '/config'; + return { + getConfiguration: function () { + return rest.get({ + id: baseUrl + '/getConfiguration.do', + }); + }, + getConfigurationValue: function (value) { + return rest.get({ + id: baseUrl + '/getConfigurationValue.do', + data: value + }); + }, + saveConfiguration: function(config) { + return rest.put({ + id: baseUrl + '/saveConfiguration.do', + data:config + }); + } + }; + + } //end configProviderService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/credential-provider/credential-provider.js b/portal_client/app/common/services/model/rest/credential-provider/credential-provider.js new file mode 100644 index 0000000..39e4b43 --- /dev/null +++ b/portal_client/app/common/services/model/rest/credential-provider/credential-provider.js @@ -0,0 +1,38 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines the restful calls for the account server module. + */ + angular.module('portal.common.services.model.rest') + .factory('credentialProvider', credentialProviderService); + + function credentialProviderService(rest, Credentials) { + var baseUrl = '/creds'; + return { + addCredentials: function (args) { + return rest.put({ + id: baseUrl + '/add.do', + data: args, + }); + }, + getCredentials : function() { + return rest.get({ + id: baseUrl + '/get.do' + }); + }, + deleteCredentials: function(entity) { + return rest.put({ + id: baseUrl + '/delete.do', + data: entity + }); + }, + getCredentialTypes: function() { + return rest.get({ + id: baseUrl + '/getCredentialTypes.do' + }); + } + }; + + } //end accountProviderService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/file-provider/file-provider.js b/portal_client/app/common/services/model/rest/file-provider/file-provider.js new file mode 100644 index 0000000..fdedeea --- /dev/null +++ b/portal_client/app/common/services/model/rest/file-provider/file-provider.js @@ -0,0 +1,22 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines the restful calls for the file server module. + */ + angular.module('portal.common.services.model.rest') + .factory('fileProvider', fileProviderService); + + function fileProviderService(rest) { + var baseUrl = '/file'; + return { + deleteFile: function(resourceFile) { + return rest.put({ + id: baseUrl + '/delete.do', + data: resourceFile + }); + } + }; + + } //end fileProviderService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/git-provider/git-provider.js b/portal_client/app/common/services/model/rest/git-provider/git-provider.js new file mode 100644 index 0000000..ff360bf --- /dev/null +++ b/portal_client/app/common/services/model/rest/git-provider/git-provider.js @@ -0,0 +1,39 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines the restful calls for the git server module. + */ + angular.module('portal.common.services.model.rest') + .factory('gitProvider', gitProviderService); + + function gitProviderService(rest, GitInfo) { + var baseUrl = '/git'; + return { + addGitRepo: function(gitInfo) { + return rest.put({ + id: baseUrl + '/add.do', + data: gitInfo + }); + }, + getAllRepos : function() { + return rest.get({ + id: baseUrl + '/getAll.do' + }); + }, + deleteRepo: function(entity) { + return rest.put({ + id: baseUrl + '/delete.do', + data: entity + }); + }, + pullRepo: function(entity) { + return rest.put({ + id: baseUrl + '/pull.do', + data: entity + }); + } + }; + + } //end gitProviderService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/logger-provider/logger-provider.js b/portal_client/app/common/services/model/rest/logger-provider/logger-provider.js new file mode 100644 index 0000000..b4e5a6d --- /dev/null +++ b/portal_client/app/common/services/model/rest/logger-provider/logger-provider.js @@ -0,0 +1,27 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines the restful calls for the logger provider server module. + */ + angular.module('portal.common.services.model.rest') + .factory('loggerProvider', loggerProviderSerice); + + function loggerProviderSerice(rest, Config) { + var baseUrl = '/logger'; + return { + getLoggers: function () { + return rest.get({ + id: baseUrl + '/getLoggers.do', + }); + }, + setLoggers: function(loggers) { + return rest.put({ + id: baseUrl + '/setLoggers.do', + data: loggers + }); + } + }; + + } //end loggerProviderSerice function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/permission-provider/permission-provider.js b/portal_client/app/common/services/model/rest/permission-provider/permission-provider.js new file mode 100644 index 0000000..395377d --- /dev/null +++ b/portal_client/app/common/services/model/rest/permission-provider/permission-provider.js @@ -0,0 +1,42 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines the restful calls for the permissions server module. + */ + angular.module('portal.common.services.model.rest') + .factory('permissionProvider', permissionProviderService); + + function permissionProviderService(rest) { + var baseUrl = '/permissions'; + return { + getPermissionGroups: function () { + return rest.get({ + id: baseUrl + '/getPermissionGroups.do', + }); + }, + getAllPermissionGroups: function () { + return rest.get({ + id: baseUrl + '/getAll.do', + }); + }, + getPermissionCategories: function () { + return rest.get({ + id: baseUrl + '/getPermissionCategories.do', + }); + }, + updatePermissions: function (isNew, args) { + return rest.put({ + id: baseUrl + (isNew ? '/addPermssionCategory.do' : '/updatePermissionCategory.do'), + data: args + }); + }, + deletePermissionGroup: function (args) { + return rest.put({ + id: baseUrl + '/deletePermissionGroup.do', + data: args + }); + } + }; + } //end permissionProviderService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/request/request.js b/portal_client/app/common/services/model/rest/request/request.js new file mode 100644 index 0000000..6ff2eea --- /dev/null +++ b/portal_client/app/common/services/model/rest/request/request.js @@ -0,0 +1,177 @@ +(function () { + 'use strict'; + + + /** + * DESCRIPTION: Describes a server rest request. + * Defines the promise where data is returned. + */ + angular.module('portal.common.services.model.rest') + .factory('Request', requestService); + + function requestService($http, $q, $rootScope, $window) { + /** + * Request Object and promise. + * @param {type} requestData {id, params, data} + */ + function Request(args) { + var request = this; + var deferred = $q.defer(); + request.id = args.id; + request.params = args.params; + request.data = args.data; + request.process = args.process; + request.cache = args.cache ? true : false; + request.payload = null; + request.block = args.block; + + function apply() { + if (request.block) { + $rootScope.loadingBlock = true; + } + //angular.toJson(request.data); + $http({ + method: 'POST', + url: Request.baseURI + request.id, + params: request.params, + headers: {'Content-Type': Request.jsonContentType, 'dataType': Request.jsonDataType}, + data: request.data, + cache: request.cache + }).then(onSuccess, onError).catch(onError); + return deferred.promise; + } + + /** + * Handler for the $http request. + * @param {type} returnData + * @returns {undefined} + */ + function onSuccess(returnData, status, header, config) { + if (returnData.data) { + if (returnData.data.hasOwnProperty('body')) { + doSuccessSequence(returnData.data.body); + } else { + doSuccessSequence(returnData.data); + } + } else { + doErrorSequence(returnData.data, returnData.status); + } + $rootScope.loadingBlock = false; + } + /** + * Handles the sequence of callbacks for the success path. + * @param {type} data + * @returns {undefined} + */ + function doSuccessSequence(data) { + request.payload = data; + if (request.process) { + request.payload = request.process(data); + } + deferred.resolve(request.payload); + } + + /** + * Handler for $http request. + * @param {type} data + * @returns {undefined} + */ + function onError(data, status, header, config) { + doErrorSequence(data.data, data.status); + $rootScope.loadingBlock = false; + } + + /** + * Handles the sequence of callbacks for the error path. + * @param {type} data + * @returns {undefined} + */ + function doErrorSequence(data, status) { + request.payload = data; + // if we've made a rest call and go to the login page, redirect + if (!data.hasOwnProperty('success') && status !== 400) { + $window.location.reload(); + } + deferred.reject(request.payload); + } + + /** + * Puts the data on the server. + * @returns {request_L35.requestService.Request} + */ + request.put = function () { + return apply(); + }; + + /** + * Gets data from the server. (could retry in the future). + * @returns {request_L35.requestService.Request} + */ + request.get = function () { + return apply(); + }; + + request.upload = function () { + var promises = []; + var files = request.data; + var params = request.params; + //Angular doesn't support progress, so we use JQUERY instead. + function sendUpload(file) { + var deferred = $q.defer(); + var formData = new FormData(); + formData.append('file', file.rawFile); + if (params !== undefined) { + $.each(params, function(key, value) { + formData.append(key, value); + }); + } + + $http({ + method: 'POST', + url: Request.baseURI + request.id, + data: formData, + cache: false, + transformRequest: angular.identity, + headers: {'Content-Type': undefined, 'dataType': undefined} + }).then(onUploadSuccess, onUploadError).catch(onUploadError); + + function onUploadError(data, status, header, config) { + file.uploadFailed(data.data.message); + deferred.reject(angular.fromJson(data.data)); + } + + function onUploadSuccess(data, status, header, config) { + var returnData = data.data; + if (returnData.success) { + if (returnData.body.length === 0) { + return; + } + file.uploaded(returnData.body[0], Request.baseURI + '/'); + file.response = returnData.body; + deferred.resolve(file); + } else { + file.uploadFailed(returnData.message); + deferred.reject(); + } + } + return deferred.promise; + } + //Server only supports a single file. + for (var i = 0; i < files.length; i++) { + promises.push(sendUpload(files[i])); + } + return $q.all(promises); + }; + } + + Request.registerCache = function (cache) { + $http.defaults.cache = cache; + }; + // TEMPORARY: should be unneeded when this file is being served from baseURI + Request.baseURI = '/portal'; + Request.jsonContentType = 'application/json; charset=utf-8'; + Request.jsonDataType = 'json'; + return Request; + + } //end requestService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/rest.js b/portal_client/app/common/services/model/rest/rest.js new file mode 100644 index 0000000..4811d80 --- /dev/null +++ b/portal_client/app/common/services/model/rest/rest.js @@ -0,0 +1,38 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Contains wrappers for the request obejct to simplify/force + * the different calls to pick either get/put and facilitate different behavior for each of them. + */ + angular.module('portal.common.services.model.rest', ['ui.router']) + .service('rest', restService); + + function restService(Request) { + return { + /** + * Wraps/creates a request that gets data from the server. + * @param {type} request + * @returns {unresolved} + */ + get: function (args) { + return new Request(args).get(); + }, + /** + * Wraps/creates a request that commits data to the server. + * @param {type} request + * @returns {unresolved} + */ + put: function (args) { + return new Request(args).put(); + }, + post: function(args) { + return new Request(args).put(); + }, + upload: function (args) { + return new Request(args).upload(); + } + }; + + } //end restService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/script-provider/script-provider.js b/portal_client/app/common/services/model/rest/script-provider/script-provider.js new file mode 100644 index 0000000..8cee308 --- /dev/null +++ b/portal_client/app/common/services/model/rest/script-provider/script-provider.js @@ -0,0 +1,64 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines the restful calls for the account server module. + */ + angular.module('portal.common.services.model.rest') + .factory('scriptProvider', scriptProviderService); + + function scriptProviderService(rest, scriptObjects) { + var baseUrl = '/scripts'; + return { + runScript: function(script) { + return rest.put({ + id: baseUrl + '/runScript.do', + data: script + }); + }, + addScript: function(script) { + var endPoint = '/addScript.do'; + if (script.id !== undefined && script.id !== null) { + endPoint = '/updateScript.do'; + } + return rest.put({ + id: baseUrl + endPoint, + data: script + }); + }, + getScriptStatus: function(jobId) { + return rest.put({ + id: baseUrl + '/getScriptScheduleStatus.do', + data: jobId + }); + }, + deleteScript: function(script) { + return rest.put({ + id: baseUrl + '/deleteScript.do', + data: script + }); + }, + deleteScriptSchedule: function(scriptSched) { + return rest.put({ + id: baseUrl + '/deleteScriptSchedule.do', + data: scriptSched + }); + }, + getAllScripts: function() { + return rest.get({ + id: baseUrl + '/getAll.do' + }); + }, + getScheduleFrequencies: function() { + return rest.get({ + id: baseUrl + '/getScheduleFrequencies.do' + }); + }, + getScriptType: function() { + return rest.get({ + id: baseUrl + '/getScriptType.do' + }); + } + }; + } //end scriptProviderService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/model/rest/system-provider/system-provider.js b/portal_client/app/common/services/model/rest/system-provider/system-provider.js new file mode 100644 index 0000000..d672071 --- /dev/null +++ b/portal_client/app/common/services/model/rest/system-provider/system-provider.js @@ -0,0 +1,90 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: Defines the restful calls for the account server module. + */ + angular.module('portal.common.services.model.rest') + .factory('systemProvider', scriptProviderService); + + function scriptProviderService(rest, scriptObjects) { + var baseUrl = '/system'; + return { + addEnvVars: function(envVars) { + var endPoint = '/addEnvVars.do'; + var arrayItems = []; + if (!angular.isArray(envVars)) { + arrayItems.push(envVars); + } else { + arrayItems = envVars; + } + + return rest.put({ + id: baseUrl + endPoint, + data: arrayItems + }); + }, + updateSeed: function(seed) { + var endPoint = '/updateSeed.do'; + return rest.put({ + id: baseUrl + endPoint, + data: seed + }); + }, + updateSmtpConfig: function(smtpConf) { + var endPoint = '/updateSmtpConfig.do'; + var arrayItems = []; + if (!angular.isArray(smtpConf)) { + arrayItems.push(smtpConf); + } else { + arrayItems = smtpConf; + } + return rest.put({ + id: baseUrl + endPoint, + data: arrayItems + }); + }, + deleteEnvVar: function(envVars) { + var endPoint = '/deleteEnvVars.do'; + var arrayItems = []; + if (!angular.isArray(envVars)) { + arrayItems.push(envVars); + } else { + arrayItems = envVars; + } + + return rest.put({ + id: baseUrl + endPoint, + data: arrayItems + }); + }, + getAllEnvVars: function() { + return rest.get({ + id: baseUrl + '/getAll.do' + }); + }, + getSmtpConfig: function() { + return rest.get({ + id: baseUrl + '/getSmtpConfig.do' + }); + }, + addEmailTemplate: function(tmpl) { + return rest.get({ + id: baseUrl + '/addEmailTemplate.do', + data: tmpl + }); + }, + getEmailTemplates: function() { + return rest.get({ + id: baseUrl + '/getEmailTemplates.do' + }); + }, + deleteEmailTemplates: function(tmpl) { + return rest.get({ + id: baseUrl + '/deleteEmailTemplate.do', + data: tmpl + }); + }, + }; + } //end scriptProviderService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/permissions/permissions.js b/portal_client/app/common/services/permissions/permissions.js new file mode 100644 index 0000000..4ab749b --- /dev/null +++ b/portal_client/app/common/services/permissions/permissions.js @@ -0,0 +1,40 @@ +angular.module('portal').factory('permissions', permissionFactory); + +function permissionFactory($rootScope, $state, portalUtils, $q, cache, accountStores, Triggers) { + + Triggers.listen([Triggers.PERMISSIONS_CHANGED], function(trigger, payload){ + new accountStores.Account().refresh(); + $rootScope.$broadcast('permissionsChanged'); + }); + + var permissions = { + canView: function (attrs) { + var deferred = $q.defer(); + //Nothing defined (always true) + if (angular.isDefined(attrs) === false) { + deferred.resolve(true); + } else { + var mCache = cache.request([new accountStores.Account()]).then(function () { + var validRole = verifyRole(mCache, attrs); + deferred.resolve(validRole); + }); + } + return deferred.promise; + } + }; + + /** + * Check that the user is one of the passed in roles + * @param {type} attrs attributes to check against + * @returns {Boolean} + */ + function verifyRole(mCache, attrs) { + var show = true; + if (angular.isDefined(attrs.permission)) { + show = mCache.account.data.hasPermission(attrs.permission); + } + return show; + } + + return permissions; +} \ No newline at end of file diff --git a/portal_client/app/common/services/triggers/trigger-manager.js b/portal_client/app/common/services/triggers/trigger-manager.js new file mode 100644 index 0000000..52fe26a --- /dev/null +++ b/portal_client/app/common/services/triggers/trigger-manager.js @@ -0,0 +1,160 @@ +(function () { + 'use strict'; + /** + * Manages the STOMP connections. + * @type String + */ + + angular.module('portal.common.services.triggers').factory('triggerManager', function ($log, Triggers, $rootScope, Map, Request, portalUtils, $mdDialog, $state, cache, $window) { + + var subscriptions = new Map(); + var stompClient = null; + var allTopics = []; + var showTimeout = true; + var retryCounter = 0; + var useStomp = true; + + function reconnect(onSuccess) { + if (stompClient !== null && stompClient.connected) { + stompClient.disconnect(); + } + console.log('Reconnecting....'); + subscriptions = new Map(); + stompClient = null; + stompClient = null; + connect(onSuccess); + } + + function connect(onSuccess) { + if (stompClient === null) { + var socket = new SockJS(Request.baseURI + '/ws/stomp'); + stompClient = Stomp.over(socket); + // turns off printing out the contents of all messages + stompClient.debug = null; + } + if (!stompClient.connected) { + stompClient.connect({}, function (frame) { + $log.log('Stomp connection success'); + portalUtils.call(onSuccess); + retryCounter = 0; + }, function (error) { + // this is called if the connection closes or times out. + // Should we display a notice to the user? auto-reconnect? + $log.log('Stomp connection error: ' + error); + + if (retryCounter < 6) { + setTimeout(function() { reconnect(onSuccess); }, 5000); + retryCounter++; + } + + else if (showTimeout) { + var modal = $mdDialog.show({ + templateUrl: 'common/partials/session-end/session-end.html', + controller: 'SessionEndCtrl', + clickOutsideToClose: false, + escapeToClose: false, + locals: { + modalInstance: { + message: 'Session lost. Please click below to connect', + btnText: 'Connect', + onOkay: function() { + stompClient = null; + retryCounter = 0; + reconnect(onSuccess); + $mdDialog.cancel(); + } + } + + } + }); + } + showTimeout = true; + }); + } else { + portalUtils.call(onSuccess); + } + } + + function disconnect(callback) { + subscriptions = new Map(); + if (stompClient !== null) { + showTimeout = false; + stompClient = null; + } else { + portalUtils.call(callback); + } + } + + function stompMsgReceived(message) { + var trigger = angular.fromJson(message.body); + //$log.log('Received Trigger: ' + trigger.type + '\n' + message.body); + processTrigger(trigger); + } + + function processTrigger(trigger) { + //This function comes from stomp, which comes in on a non-angular javascript turn. + //That means we need to call apply ourselves. If we are ever in a digest + //cycle at this point in the code, something is wrong + $rootScope.$apply(function () { + Triggers.notify(Triggers[trigger.type], trigger); + }); + } + + function subscribe() { + if (stompClient === null || stompClient === undefined || !stompClient.connected) { + connect(function () { + subscribe(); + }); + } else { + for (var x = 0; x < allTopics.length; x++) { + var topic = allTopics[x]; + if (!subscriptions.containsKey(topic)) { + var subscription = stompClient.subscribe(topic, stompMsgReceived); + subscription.topic = topic; + subscriptions.put(topic, subscription); + } + } + } + } + + function unsubscribe(topic) { + var subscription = subscriptions.get(topic); + if (subscription !== undefined) { + subscription.unsubscribe(); + } + subscriptions.removeByKey(topic); + + var index = allTopics.indexOf(topic); + if (index > -1) { + allTopics.splice(index, 1); + } + } + + function unsubscribeAll() { + for (var i = 0; i < subscriptions.length; i++) { + var sub = subscriptions.values[i]; + sub.unsubscribe(); + subscriptions.removeByKey(sub.topic); + } + allTopics.length = 0; + } + + return { + subscribe: function (topics) { + //unsubscribeAll(); //For now unsubscribe all, eventually want to support listening for more than one. + for (var x = 0; x < topics.length; x++) { + var index = allTopics.indexOf(topics[x]); + if (index < 0) { + allTopics.push(topics[x]); + } + } + connect(function () { + subscribe(); + }); + }, + unsubscribe: unsubscribe, + disconnect: disconnect, + unsubscribeAll: unsubscribeAll + }; + }); +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/triggers/triggers.js b/portal_client/app/common/services/triggers/triggers.js new file mode 100644 index 0000000..a53d814 --- /dev/null +++ b/portal_client/app/common/services/triggers/triggers.js @@ -0,0 +1,168 @@ +(function () { + 'use strict'; + + /** + * DESCRIPTION: + */ + angular.module('portal.common.services.triggers', ['ui.router']) + .factory('Triggers', triggersService); + + function triggersService($log, portalUtils, Map) { + + function Trigger(id, validatorFn, preCall) { + var trigger = this; + trigger.id = id; + trigger.validate = validatorFn; + if (!angular.isFunction(trigger.validate)) { + trigger.validate = function () { + return true; + }; + } + Object.defineProperties(trigger, { + isValid: { + get: trigger.validate + } + }); + trigger.preCall = true; + if (angular.isDefined(preCall)) { + trigger.preCall = preCall; + } + } + + Trigger.prototype.toString = function () { + return this.id; + }; + + var triggers = { + APP_LOGS_UPDATED: new Trigger('APP_LOGS_UPDATED', undefined, false), + APP_LOGS_PURGED: new Trigger('APP_LOGS_PURGED', undefined, false), + SCRIPT_COMPLETED: new Trigger('SCRIPT_COMPLETED', undefined, false), + SCRIPT_DELETED: new Trigger('SCRIPT_DELETED', undefined, false), + SCRIPT_ADDED: new Trigger('SCRIPT_ADDED', undefined, false), + SCRIPT_UPDATED: new Trigger('SCRIPT_UPDATED', undefined, false), + SCRIPT_SCHEDULE_DELETED: new Trigger('SCRIPT_SCHEDULE_DELETED', undefined, false), + USER_ADDED: new Trigger('USER_ADDED', undefined, false), + USER_DELETED: new Trigger('USER_DELETED', undefined, false), + HASH_TYPES_CHANGED: new Trigger('HASH_TYPES_CHANGED', undefined, false), + PERMISSIONS_DELETED: new Trigger('PERMISSIONS_DELETED', undefined, false), + PERMISSIONS_CHANGED: new Trigger('PERMISSIONS_CHANGED', undefined, false), + PERMISSIONS_UPDATED: new Trigger('PERMISSIONS_UPDATED', undefined, false), + PERMISSIONS_ADDED: new Trigger('PERMISSIONS_ADDED', undefined, false), + FILE_DELETED: new Trigger('FILE_DELETED', undefined, false), + ENV_VARS_ADDED: new Trigger('ENV_VARS_ADDED', undefined, false), + ENV_VARS_DELETED: new Trigger('ENV_VARS_DELETED', undefined, false), + EMAIL_TEMPLATE_UPDATED: new Trigger('EMAIL_TEMPLATE_UPDATED', undefined, false), + EMAIL_TEMPLATE_DELETED: new Trigger('EMAIL_TEMPLATE_DELETED', undefined, false), + SMTP_CONFIG_UPDATED: new Trigger('SMTP_CONFIG_UPDATED', undefined, false), + SEED_UPDATED: new Trigger('SEED_UPDATED', undefined, false), + }; + + triggers.Trigger = Trigger; + + + /** + * Registers to be notified when the passed in triggers are fired. + * @param {type} params {required:array[Trigger],optional:array[Trigger]} + * @param {type} callback + * @returns {cache_L40.listen.cacheAnonym$4|undefined} + */ + triggers.listen = function (params, callback) { + var optional = []; + var ids = []; + + if (angular.isArray(params)) { + optional = params.slice(); + } else if (params instanceof Trigger) { + optional.push(params); + } + + //Warn if data is incorrect. + if (optional.length === 0) { + $log.error('Attempting to listen for triggers without correct parameters'); + return; + } + for (var j = 0; j < optional.length; j++) { + ids.push(subscribe(optional[j], callback)); + } + + return {unlisten: function () { + unsubscribe(ids); + }}; + }; + + /** + * Starting subscriber id for new listeners + * @type Number + */ + var subscriberId = 0; + /** + * + * @type cache_L40.MapMap of listeners. + */ + var subscribersMap = new Map('id', 'trigger'); + /** + * Defines a subscriber object. + * @param {type} trigger + * @param {type} callback + * @param {type} id + * @returns {undefined} + */ + function Subscriber(trigger, callback, id) { + var subscriber = this; + subscriber.trigger = trigger; + subscriber.callback = callback; + subscriber.id = id; + } + /** + * Function for managing subscription map. + * @param {type} trigger + * @param {type} callback + * @param {type} now + * @returns {Number} + */ + function subscribe(trigger, callback) { + if (trigger instanceof Trigger) { + subscribersMap.put(subscriberId, new Subscriber(trigger, callback, subscriberId)); + return subscriberId++; + } else { + $log.error('Failed to listen on invalid trigger.'); + } + } + /** + * Function for itterating over ids and unsubscribing. + * @param {type} ids + * @returns {undefined} + */ + function unsubscribe(ids) { + for (var i = 0; i < ids.length; i++) { + subscribersMap.removeByKey(ids[i]); + } + } + + /** + * Notifies cache listeners of an event, propagates the data passed in. + * @param {type} trigger + * @param {type} data + * @returns {undefined} + */ + triggers.notify = function (trigger, data) { + if (trigger instanceof Trigger) { + var subsMap = subscribersMap.getGroup('trigger', trigger); + for (var i = 0; i < subsMap.length; i++) { + var sub = subsMap.values[i]; + call(sub.callback, trigger, data); + } + } else { + $log.error('Failed to notify on invalid trigger.'); + } + }; + + function call(callback, trigger, data) { + window.setTimeout(function () { + portalUtils.call(callback, trigger, data); + }, 0); + } + + return triggers; + } //end triggersService function +})(); \ No newline at end of file diff --git a/portal_client/app/common/services/utils/portalUtils.js b/portal_client/app/common/services/utils/portalUtils.js new file mode 100644 index 0000000..1bdad0b --- /dev/null +++ b/portal_client/app/common/services/utils/portalUtils.js @@ -0,0 +1,276 @@ +angular.module('portal.common.services.portalUtils', ['ui.router']) + .service('portalUtils', function ($sce, $window, $mdToast, $mdDialog) { + + this.trustAsHtml = function (string){ + return $sce.trustAsHtml(string); + }; + + this.setScopeValue = function(scope, key, value, recurse) { + if (scope === undefined || scope === null) { + return; + } + if (key in scope) { + scope[key] = value; + return; + } + if (recurse) { + this.setScopeValue(scope.$parent, key, value, recurse); + } + }; + + this.hashSize = function(hash) { + var size = 0, key; + for (key in hash) { + if (hash.hasOwnProperty(key)) { + size++; + } + } + return size; + }; + + this.stripHtml = function(htmlStr) { + var newStr = htmlStr.replace(/<(?:.|\n)*?>/gm, ''); + return newStr; + }; + + function inArray(array, comparer) { + for (var i = 0; i < array.length; i++) { + if (comparer(array[i])) { + return true; + } + } + return false; + } + + /** + * Push a value onto an array if it doesnt exist + */ + this.arrayPushIfNotExist = function(array, element, comparer) { + if (!inArray(array, comparer)) { + array.push(element); + } + }; + + this.isIE = function() { + var sAgent = $window.navigator.userAgent; + var idx = sAgent.indexOf('MSIE'); + + // If IE, return version number. + if (idx > 0) { + return true; + } + // If IE 11 then look for Updated user agent string. + else if (sAgent.match(/Trident\/7\./)) { + return true; + } + else { + return false; //It is not IE + } + }; + + this.confirmBox = function(id, title, msg, showConfirm, returnFunction, newTemplate) { + var template, size; + + if(newTemplate !== null && newTemplate !== undefined) { + template = newTemplate; + } else { + template = 'common/partials/templates/confirm-window.tmpl.html'; + } + + $mdDialog.show({ + templateUrl: template, + skipHide: true, + multiple: true, + locals: { + modalInstance: { + btnId: id, + confirmTitle: title, + confirmMessage: msg, + showConfirmMessage: showConfirm + } + }, + controller: ['$scope', 'modalInstance' , '$mdDialog', function($scope, modalInstance, $mdDialog) { + + $scope.confirmTitle = modalInstance.confirmTitle; + $scope.confirmMessage = []; + if (angular.isArray(modalInstance.confirmMessage)) { + $scope.confirmMessage = modalInstance.confirmMessage; + } else { + $scope.confirmMessage.push(modalInstance.confirmMessage); + } + $scope.showConfirmMessage = modalInstance.showConfirmMessage; + $scope.btnId = modalInstance.btnId; + + $scope.data = { + checkbox: false, + confirm: false, + choice: 'merge' + }; + + $scope.confirm = function() { + $scope.data.confirm = true; + $mdDialog.hide($scope.data); + }; + + $scope.cancel = function() { + $scope.data.confirm = false; + $mdDialog.hide($scope.data); + }; + + }] + }).then(function(disabledConfirm) { + returnFunction(disabledConfirm); + }); + }; + + var toastPos = { + bottom: false, + top: false, + left: false, + right: false + }; + + function validPosition(position) { + return toastPos[position] !== undefined; + } + + /** + * Allows for a toast message to be displayed on screen + * @param message: The message to be displayed + * @param position: The Position of the toast box (valid values are ['top'|'bottom'|'left'|'right']) + * @param duration: How long the toast message should be displayed + */ + this.toast = function(message, position, duration) { + toastPos.bottom = false; + toastPos.top = false; + toastPos.left = false; + toastPos.right = false; + + if (Array.isArray(position)) { + for (var i = 0; i < position.length; i++) { + if (validPosition(position[i])) { + toastPos[position[i]] = true; + } + } + } else if (validPosition(position)) { + toastPos[position] = true; + } + + var toastPosition = angular.extend({}, toastPos); + + var pinTo = Object.keys(toastPosition) + .filter(function(pos) { return toastPosition[pos]; }) + .join(' '); + + $mdToast.show( + $mdToast.simple() + .textContent(message) + .position(pinTo) + .hideDelay(duration) + ); + }; + + /** + * Converts space separated string to title case. + * + * See: http://stackoverflow.com/a/196991/1516011 + * + * @param {type} str Any 'space separated string' + * @returns {undefined} returns 'space separated string' as 'Space Separated String' + */ + this.toTitleCase = function (str) { + if (str === null) { + return; + } + return str.replace(/\w\S*/g, function (txt) { + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + }; + + /** + * Helper function to make calls to possibly undefined functions. + * First argument is the function to call. + * Rest of the arguements get passed to the callee + * @returns {undefined} + */ + this.call = function (callee) { + if (angular.isFunction(callee)) { + var calleeArgs = Array.prototype.slice.call(arguments, 1); + return callee.apply(this, calleeArgs); + } + }; + + this.createHash = function(array, Constructor, keyField) { + var hash = {}; + for (var i in array) { + var obj = new Constructor(array[i]); + hash[obj[keyField]] = obj; + } + return hash; + } ; + + /** + * Create an array + * + * @param {type} array An array of server objects; + * @param {type} Constructor A constructor to wrap the given data. + * @returns {Array} A wrapped array of the given type. + */ + this.createArray = function (array, Constructor) { + var objectArray = []; + for (var i in array) { + objectArray.push(new Constructor(array[i])); + } + return objectArray; + }; + + this.validEmail = function(text) { + var EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + var test = EMAIL_REGEX.test(text); + return test; + }; + + /** + * Allows for quick and easy inheritance of a parent class. + * @param {type} sub + * @param {type} parent + * @returns {unresolved} + */ + this.inherit = function (sub, parent) { + sub.prototype = Object.create(parent.prototype); //Available in Chrome (5), FF (4), IE (9), Safari (5). + sub.prototype.constructor = sub; + return sub; + }; + + this.arrayLength = function(array) { + return Object.keys(array).length; + }; + + this.createMap = function(data, Constructor, accessor) { + var map = {}; + if (angular.isArray(data)) { + for (var i = 0; i < data.length; i++) { + var obj = new Constructor(data[i]); + map[obj[accessor]] = obj; + } + } + return map; + }; + + /* jshint ignore:start */ + this.sortHashTable = function(hash) { + var tmpArray = []; + $.each(hash, function(key, value){ + tmpArray[key] = value; + }); + + tmpArray = tmpArray.sort(); + var tmpHash = {}; + for (var index in tmpArray) { + tmpHash[index] = tmpArray[index]; + } + return tmpHash; + }; + /* jshint ignore:end */ + + }); \ No newline at end of file diff --git a/portal_client/app/custom/portal-material-override.css b/portal_client/app/custom/portal-material-override.css new file mode 100644 index 0000000..5405d4b --- /dev/null +++ b/portal_client/app/custom/portal-material-override.css @@ -0,0 +1,208 @@ +.md-portal { background-color: #FF6600 !important; } +.md-portal-fore { color: rgb(70, 138, 172) !important; } + +.md-portal-dark { background-color: rgba(67, 136, 221, 0.7) !important; } +.md-portal-grey { background-color: rgb(67, 136, 221) !important;} +.md-portal-light-grey { background-color: #FF6600 !important;} +.md-portal-grey-opaque { + /* The top is the fallback color */ + background-color: rgb(126, 140, 154) !important; + background-color: rgba(126, 140, 154, 0.8) !important; +} +.md-portal-light { + background-color: #b7d3e1 !important; /* Fall back color in case rgba isnt compatible */ + background-color: rgba(70, 138, 172, 0.5) !important; +} + +.md-portal-disabled { + color: #969696; +} + +.play-green { color: rgb(51, 204, 51); } +.play-green-inactive { color: rgba(51, 204, 51, 0.26); } + +.stop-red { color: rgb(204, 41, 0); } +.stop-red-inactive { color: rgba(204, 41, 0, 0.26);} + +/* Google's meterial-design-icons */ +/* Rules for sizing the icon. */ +.material-icons.md-10 { font-size: 10px; } +.material-icons.md-12 { font-size: 12px; } +.material-icons.md-14 { font-size: 14px; } +.material-icons.md-16 { font-size: 16px; } +.material-icons.md-18 { font-size: 18px; } +.material-icons.md-20 { font-size: 20px; } +.material-icons.md-22 { font-size: 22px; } +.material-icons.md-24 { font-size: 24px; } +.material-icons.md-36 { font-size: 36px; } +.material-icons.md-48 { font-size: 48px; } +.material-icons.md-auto { + height: auto !important; + width: auto !important; +} + +.md-button.md-sm { + height: 26px; + line-height: 26px; + min-height: 26px; + min-width: 1%; + font-size: 16px; + vertical-align: bottom; +} + +.md-button.md-xs { + height: 20px; + line-height: 20px; + min-height: 20px; + min-width: 1%; + font-size: 10px; + vertical-align: bottom; +} + +.md-button.md-lg { + height: 58px; + line-height: 58px; + min-height: 58px; + min-width: 1%; + font-size: 48px; + vertical-align: center; +} + +/* Rules for using icons as black on a light background. */ +.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); } +.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); } + +/* Rules for using icons as white on a dark background. */ +.material-icons.md-light { color: rgba(255, 255, 255, 1); } +.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); } + +/* Rules for icon coloring */ +.material-icons.play-green { color: rgb(51, 204, 51); } +.material-icons.play-green-inactive { color: rgba(51, 204, 51, 0.26); } + +.material-icons.stop-red { color: rgb(204, 41, 0); } +.material-icons.stop-red-inactive { color: rgba(204, 41, 0, 0.26);} + +.material-icons.portal-blue { color: rgb(70, 138, 172); } +.material-icons.portal-blue-inactive { color: rgb(70, 138, 172, 0.26); } + +md-icon.portal-green { color: #4CAF50} + +md-toast { + z-index: 99999 !important; +} + +md-tabs.stretch-height md-tab-content>div, +md-tabs.stretch-height md-tab-body { + margin: 0; + width: 100%; + min-height: 100%; + height: 100%; +} + +md-menu-content.max-height { + height:30vh; +} + +.vertical-divide { + border-top-width: 0; + border-right-width: 1px; + border-right-style: solid; + margin-left: 5px; + margin-right: 5px; +} + +.flex-min-width-85 { + min-width: 85% !important; +} + +.flex-min-height-85 { + min-height: 85% !important; +} + +.flex-min-width-75 { + min-width: 75% !important; +} + +.flex-min-height-75 { + min-height: 75% !important; +} + +.flex-min-width-50 { + min-width: 50% !important; +} + +.flex-min-width-40 { + min-width: 40% !important; +} + +md-input-container.md-input-invalid .md-auto-hide .md-input-message-animation:not(.ng-animate) { + opacity: 1 !important; + margin-top: 0 !important; +} + +md-input-container.md-input-invalid [ng-messages] { + max-height: 19px !important; +} + +md-input-container .md-input-message-animation:not(.ng-animate) { + opacity: 1 !important; + margin-top: 0px !important; +} +md-input-container.md-block { + display: block !important; + width: 100% !important; +} + +md-card .md-media-sm { + height: 80px !important; + width: 80px !important; +} + +md-badge{ + margin: 0 0 0 8px; + background-color: red; + color: white; + padding: 2px 4px; + border-radius: 4px; + position: relative; + left: 0px; + top: -35px; +} + +md-toolbar.md-toolbar-small { + height:44px; + min-height: 44px; + max-height: 44px; +} +md-toolbar.md-toolbar-small .md-toolbar-tools { + vertical-align: middle; + font-size: 16px; +} + +input.md-disabled-input { + color: rgba(0,0,0,1) !important; + background: none !important; +} + +.dtp table.dtp-picker-days tr > td > a.selected, +.dtp table.dtp-picker-days tr > td > a.selected.hilite, +.dtp div.dtp-date, .dtp div.dtp-time, .dtp .dtp-hand.on, +.dtp .dtp-actual-meridien a.selected, +.dtp .dtp-picker-time > a.dtp-select-hour.selected { + background: #FF6600; +} + +.dtp table.dtp-picker-days tr > td > a.hilite:not(.selected), +.dtp div.dtp-actual-time.p60 span.selected { + color: #FF6600; +} + +.dtp div.dtp-year-btn, .dtp div.dtp-actual-year, .dtp div.dtp-actual-maxtime { + color: #b7d3e1; +} + +.dtp > .dtp-content > .dtp-date-view > header.dtp-header { + background: rgb(67, 136, 221); +} + diff --git a/portal_client/app/favicon.ico b/portal_client/app/favicon.ico new file mode 100644 index 0000000..9823ef1 Binary files /dev/null and b/portal_client/app/favicon.ico differ diff --git a/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.css b/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.css new file mode 100644 index 0000000..80e5910 --- /dev/null +++ b/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.css @@ -0,0 +1,36 @@ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url('MaterialIcons-Regular.eot') + url('fonts/google-md-icons/MaterialIcons-Regular.eot'); + src: url('MaterialIcons-Regular.woff2') format('woff2'), + url('MaterialIcons-Regular.woff') format('woff'), + url('fonts/google-md-icons/MaterialIcons-Regular.woff2') format('woff2'), + url('fonts/google-md-icons/MaterialIcons-Regular.woff') format('woff'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: 'liga'; +} \ No newline at end of file diff --git a/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.eot b/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.eot new file mode 100644 index 0000000..70508eb Binary files /dev/null and b/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.eot differ diff --git a/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.woff b/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.woff new file mode 100644 index 0000000..b648a3e Binary files /dev/null and b/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.woff differ diff --git a/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.woff2 b/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.woff2 new file mode 100644 index 0000000..9fa2112 Binary files /dev/null and b/portal_client/app/fonts/google-md-icons/MaterialIcons-Regular.woff2 differ diff --git a/portal_client/app/images/NGLogo.png b/portal_client/app/images/NGLogo.png new file mode 100644 index 0000000..17fafa6 Binary files /dev/null and b/portal_client/app/images/NGLogo.png differ diff --git a/portal_client/app/images/background.png b/portal_client/app/images/background.png new file mode 100644 index 0000000..f720993 Binary files /dev/null and b/portal_client/app/images/background.png differ diff --git a/portal_client/app/images/portal.png b/portal_client/app/images/portal.png new file mode 100644 index 0000000..657ae49 Binary files /dev/null and b/portal_client/app/images/portal.png differ diff --git a/portal_client/app/login/login.css b/portal_client/app/login/login.css new file mode 100644 index 0000000..87d07e3 --- /dev/null +++ b/portal_client/app/login/login.css @@ -0,0 +1,46 @@ +#moreinfo-view { + text-align: center; +} + +#ngLogo-content { + width: 25%; +} + +.loginTextbox { + color: white !important; +} + +input[placeholder], [placeholder], *[placeholder].loginTextbox { + color: white !important; +} + +.loginBox { + max-width: 90%; + width: 40%; +} + +#ngLogo-img { + width: 40%; + height: auto; + display: inline-block; +} + +#logo-img { + width: 35%; + height: auto; +} + +#forgotPass { + font-style: italic; + font-size: smaller; +} + + +#errorField { + margin-top: 3%; +} + +.center { + display:inline-block; + margin: 0 auto; +} \ No newline at end of file diff --git a/portal_client/app/login/login.html b/portal_client/app/login/login.html new file mode 100644 index 0000000..9fe3c88 --- /dev/null +++ b/portal_client/app/login/login.html @@ -0,0 +1,24 @@ +
+ portal +
+ + account_circle + + +
+
+ + lock + + + + remove_red_eye + View password + +
+
+
+ + Login +
+
\ No newline at end of file diff --git a/portal_client/app/login/login.js b/portal_client/app/login/login.js new file mode 100644 index 0000000..5c59a08 --- /dev/null +++ b/portal_client/app/login/login.js @@ -0,0 +1,80 @@ +angular.module('portal.login', + ['ui.router']).controller('LoginCtrl', function ($scope, $state, $window, cache, accountStores, + accountProvider, PortalPubSub, triggerManager) { + + $scope.mCache = cache.request([new accountStores.Account()]); + $scope.errorMsg = ''; + $scope.errorColor = 'text-danger'; + $scope.processing = false; + $scope.passwordType = 'password'; + + $scope.login = function () { + if ($scope.stompSuccess !== true) { + return; + } + + $scope.processing = true; + accountProvider.login($scope.username, $scope.password).then(function(data){ + if (data && data.message) { + $scope.errorMsg = data.message; + $scope.errorColor = 'text-danger'; + $scope.processing = false; + return; + } + $scope.mCache.account.update(data); + var act = $scope.mCache.account.data; + triggerManager.subscribe(act.stompTopics); + $state.go(data.page,{}); + $scope.processing = false; + }).catch(function(data) { + $scope.errorMsg = 'Unable to login ' + data; + $scope.errorColor = 'text-danger'; + $scope.processing = false; + }); + }; + + $scope.stompSuccess = false; + $scope.subscription = PortalPubSub.subscribe('StompTestStatus', function (topic, status) { + + var success = ''; + var failed = 'Communications to the Portal server cannot be established. '; + var degraded = 'Communication with the Portal server has been established in a fall-back mode. ' + + 'Portal will function properly but performance will be degraded. '; + var support = 'isaac.parenteau@ngc.com'; + var body; + + if (topic.status=== 'successful') { + + $scope.stompSuccess = true; + $scope.errorMsg = success; + $scope.errorColor = 'text-success'; + + } else if (topic.status === 'degraded') { + + $scope.stompSuccess = true; + body = encodeURIComponent('Please help me with this warning: \n\n\"' + degraded + '\"' + + '\n\nInfo to help resolve this: ' + status.stats); + $scope.errorMsg = degraded + 'Please contact ' + + support + ' for further assistance.'; + $scope.errorColor = 'text-warning'; + + } else if (topic.status === 'failure') { + + $scope.stompSuccess = false; + body = encodeURIComponent('Please help me with this error: \n\n\"' + failed + '\"' + + '\n\nInfo to help resolve this: ' + status.stats); + + $scope.errorMsg = failed + 'Please contact ' + + support + ' for further assistance.'; + $scope.errorColor = 'text-danger'; + } + }); + + $scope.$on('$destroy', function () { + PortalPubSub.unsubscribe($scope.subscription); + }); +}); \ No newline at end of file diff --git a/portal_client/app/main/cred-bank/cell-templates/cred-actions.html b/portal_client/app/main/cred-bank/cell-templates/cred-actions.html new file mode 100644 index 0000000..377d21a --- /dev/null +++ b/portal_client/app/main/cred-bank/cell-templates/cred-actions.html @@ -0,0 +1,12 @@ +
+ + edit + Edit Credential + + + + delete + Delete Credential + + +
\ No newline at end of file diff --git a/portal_client/app/main/cred-bank/cred-bank.html b/portal_client/app/main/cred-bank/cred-bank.html new file mode 100644 index 0000000..00f70f1 --- /dev/null +++ b/portal_client/app/main/cred-bank/cred-bank.html @@ -0,0 +1,21 @@ +
+ + + +
+ Credentials + + + filter + Filter Credentials + + + add + Add Credentials + +
+
+
+
+
+
\ No newline at end of file diff --git a/portal_client/app/main/cred-bank/cred-bank.js b/portal_client/app/main/cred-bank/cred-bank.js new file mode 100644 index 0000000..2705d55 --- /dev/null +++ b/portal_client/app/main/cred-bank/cred-bank.js @@ -0,0 +1,115 @@ +(function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.dashboard.credBank', ['ui.router']) + .controller('CredBankCtrl', credBankCtrlController); + + function credBankCtrlController($scope, cache, credentialProvider, credentialStores, $mdDialog, uiGridConstants, portalUtils, Constants) { + + $scope.Permissions = Constants.Permissions; + $scope.confirmQuestionBox = true; + + $scope.mCache = cache.request([new credentialStores.Credentials()]).then(function(){ + $scope.credentials = $scope.mCache.credentials.data.values; + }); + + $scope.credsGrid = { + data: 'credentials', + paginationPageSizes: [28, 56, 84, 102], + paginationPageSize: 28, + enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER, + toggleModifierKeysToMultiSelect : true, + enableRowHeaderSelection: true, + multiSelect: true, + noUnselect: false, + onRegisterApi: function (gridApi) { + gridApi.selection.setModifierKeysToMultiSelect(true); + $scope.credsGridApi = gridApi; + }, + columnDefs: [ + {field: 'name', displayName: 'Credential Name', width: '*'}, + {field: 'useSSH', displayName: 'Credential Type', width: '*', + cellTemplate: '
{{row.entity.credentialType === "SSH" ? "SSH Key" : "Username/Password"}}
'}, + {field: 'uid', displayName: 'Actions', width: '100', enableColumnMenu: false, enableFiltering: false, enableHiding: false, + cellTemplate: 'main/cred-bank/cell-templates/cred-actions.html'} + ], + }; + + $scope.$on('$destroy', function () { + $scope.mCache.unlisten(); + }); + + $scope.toggleFiltering = function() { + $scope.credsGridApi.enableFiltering = !$scope.credsGridApi.enableFiltering; + $scope.credsGridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN ); + }; + + var originatorEv; + $scope.openMenu = function($mdOpenMenu, ev) { + originatorEv = ev; + $mdOpenMenu(ev); + }; + + $scope.delete = function(entity) { + if ($scope.confirmQuestionBox) { + portalUtils.confirmBox('deleteCreds', 'Delete Credentials', ['Are you sure you want to delete the credential', entity.name], + true, function(value) { + if (value.confirm) { + $scope.confirmQuestionBox = !value.checkbox; + $scope.deleteCreds(entity); + } + }); + } else { + $scope.deleteCreds(entity); + } + }; + + $scope.deleteCreds = function(entity) { + credentialProvider.deleteCredentials(entity).then(function() { + portalUtils.toast('Credentials deleted successfully', ['top', 'left'], 2000); + new credentialStores.Credentials().refresh(); + }).catch(function(err){ + portalUtils.toast('Unable to delete credentials: ' + err.message, ['top', 'left'], 4000); + new credentialStores.Credentials().refresh(); + }); + }; + + $scope.edit = function(entity) { + $mdDialog.show({ + templateUrl: 'common/partials/add-creds/add-creds.html', + controller: 'AddCredsCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Edit Credentials', + credentials: entity + } + } + }).then(function(){ + new credentialStores.Credentials().refresh(); + }); + }; + + $scope.addCredentials = function() { + $mdDialog.show({ + templateUrl: 'common/partials/add-creds/add-creds.html', + controller: 'AddCredsCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Add Credentials', + } + } + }).then(function(){ + new credentialStores.Credentials().refresh(); + }); + }; + + } //end UserBankCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/main/dashboard.css b/portal_client/app/main/dashboard.css new file mode 100644 index 0000000..15478fa --- /dev/null +++ b/portal_client/app/main/dashboard.css @@ -0,0 +1,21 @@ +#portal-logo { + width: 75px; + height: auto; +} + +.account-md-menu{ + text-align: center; +} + +#accountMenu { + margin-top: 7px; +} +/* Centers icon in ripple container */ +#iconAccountMenu { + margin-right: 11px; + margin-bottom: 13px; +} + +.username-banner { + margin-top: 10px; +} \ No newline at end of file diff --git a/portal_client/app/main/dashboard.html b/portal_client/app/main/dashboard.html new file mode 100644 index 0000000..bc9aaa2 --- /dev/null +++ b/portal_client/app/main/dashboard.html @@ -0,0 +1,60 @@ +
+ + +
+ + +
Welcome {{ mCache.account.data.firstName }} {{mCache.account.data.lastName}}
+ + + + +
+
+ +
+
\ No newline at end of file diff --git a/portal_client/app/main/dashboard.js b/portal_client/app/main/dashboard.js new file mode 100644 index 0000000..31a271b --- /dev/null +++ b/portal_client/app/main/dashboard.js @@ -0,0 +1,215 @@ +(function () { + 'use strict'; + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.dashboard', ['ui.router', 'portal.common.constants']).config(DashboardConfig) + .controller('DashboardCtrl', DashboardCtrlController); + + function DashboardConfig($stateProvider, Constants) { + $stateProvider.state('dashboard', { + url: '/dashboard', + pageTitle: 'Dashboard', + templateUrl: 'main/dashboard.html', + resolve: { + authorize: ['authorize', + function (authorize) { + return authorize.validate({ + authorities: { + permission: [ + Constants.Permissions.VIEW_SCRIPT_TAB, + Constants.Permissions.VIEW_GIT_TAB, + Constants.Permissions.VIEW_USER_TAB, + Constants.Permissions.VIEW_CREDS_TAB, + Constants.Permissions.VIEW_LOG_TAB, + ] + }, + redirect: 'login' + }); + } + ] + } + }).state('dashboard.scripts', { + url: '/scripts', + pageTitle: 'Scripts', + templateUrl: 'main/script-bank/script-bank.html' + }).state('dashboard.repos', { + url: '/repos', + pageTitle: 'Git Repositories', + templateUrl: 'main/git-bank/git-bank.html' + }).state('dashboard.credentials', { + url: '/credentials', + pageTitle: 'Credentials', + templateUrl: 'main/cred-bank/cred-bank.html' + }).state('dashboard.emails', { + url: '/emails', + pageTitle: 'Emails', + templateUrl: 'main/email-bank/email-bank.html' + }).state('dashboard.users', { + url: '/users', + pageTitle: 'Users', + templateUrl: 'main/user-bank/user-bank.html' + }).state('dashboard.permissions', { + url: '/permissions', + pageTitle: 'Permissions', + templateUrl: 'main/permission-bank/permission-bank.html' + }).state('dashboard.envars', { + url: '/envars', + pageTitle: 'Environment Variables', + templateUrl: 'main/env-bank/env-bank.html' + }).state('dashboard.syslogs', { + url: '/syslogs', + pageTitle: 'System Logs', + templateUrl: 'main/log-bank/log-bank.html' + }); + } + + function DashboardCtrlController($state, $scope, cache, accountStores, accountProvider, + $window, $mdDialog, Constants) { + + var originatorEv; + $scope.openMenu = function($mdMenu, ev) { + originatorEv = ev; + $mdMenu.open(ev); + }; + + $scope.Constants = Constants; + + $scope.loadingBlock = false; + + $scope.userRole = ''; + $scope.tabs = [{ + title: 'Scripts', + source: 'main/script-bank/script-bank.html', + state: 'dashboard.scripts', + leftItem: 'description', + active: true, + default: false, + canView: Constants.Permissions.VIEW_SCRIPT_TAB + },{ + title: 'Git Repositories', + source: 'main/git-bank/git-bank.html', + state: 'dashboard.repos', + leftItem: 'computer', + active: true, + default: true, + canView: Constants.Permissions.VIEW_GIT_TAB + },{ + title: 'Credentials', + source: 'main/cred-bank/cred-bank.html', + state: 'dashboard.credentials', + leftItem: 'vpn_key', + active: false, + default: false, + canView: Constants.Permissions.VIEW_CREDS_TAB + },{ + title: 'Email Templates', + source: 'main/email-bank/email-bank.html', + state: 'dashboard.emails', + leftItem: 'email', + active: false, + default: false, + canView: Constants.Permissions.VIEW_EMAIL_TAB + },{ + title: 'Users', + source: 'main/user-bank/user-bank.html', + state: 'dashboard.users', + leftItem: 'people', + active: false, + default: false, + canView: Constants.Permissions.VIEW_USER_TAB + },{ + title: 'Permissions', + source: 'main/permission-bank/permission-bank.html', + state: 'dashboard.permissions', + leftItem: 'lock_open', + active: false, + default: false, + canView: Constants.Permissions.VIEW_PERMISSION_TAB + },{ + title: 'Environment Variables', + source: 'main/env-bank/env-bank.html', + state: 'dashboard.envars', + leftItem: 'storage', + active: false, + default: false, + canView: Constants.Permissions.VIEW_ENV_VARS_TAB + },{ + title: 'System Logs', + source: 'main/log-bank/log-bank.html', + state: 'dashboard.syslogs', + leftItem: 'description', + active: false, + default: false, + canView: Constants.Permissions.VIEW_LOG_TAB + }]; + + $scope.mCache = cache.request([new accountStores.Account()]).then(function(){ + $scope.allowedTabs = []; + $.each($scope.tabs, function(i, tab){ + if ($scope.mCache.account.data.hasPermission(tab.canView)) { + $scope.allowedTabs.push(tab); + } + }); + $scope.$broadcast('tabsetDidChange', $scope.allowedTabs); + }).catch(function(err){ }); + + $scope.$on('$destroy', function () { + $scope.mCache.unlisten(); + }); + + $scope.logout = function() { + accountProvider.logout().then(function (data) { + cache.flush(); + $state.go('login'); + $window.location.href = '#/login'; + $window.location.reload(); + }).catch(function (data) { + $state.go('login'); + }); + }; + + $scope.openConfiguration = function() { + $mdDialog.show({ + controller: 'ConfigurationCtrl', + templateUrl: 'common/partials/configuration/configuration.html', + clickOutsideToClose: false, + escapeToClose: false, + locals: { + account: $scope.mCache.account + } + }); + }; + + $scope.openSeed = function() { + $mdDialog.show({ + controller: 'SeedInfoCtrl', + templateUrl: 'common/partials/seed-info/seed-info.html', + clickOutsideToClose: false, + escapeToClose: false, + }); + }; + + $scope.openLogLevel = function() { + $mdDialog.show({ + controller: 'LoggerInfoCtrl', + templateUrl: 'common/partials/logger-info/logger-info.html', + clickOutsideToClose: false, + escapeToClose: false, + }); + }; + + $scope.openSMTPConfig = function() { + $mdDialog.show({ + controller: 'SMTPInfoCtrl', + templateUrl: 'common/partials/smtp-info/smtp-info.html', + clickOutsideToClose: false, + escapeToClose: false, + }); + }; + + } //end DashboardCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/main/email-bank/cell-templates/email-actions.html b/portal_client/app/main/email-bank/cell-templates/email-actions.html new file mode 100644 index 0000000..5599207 --- /dev/null +++ b/portal_client/app/main/email-bank/cell-templates/email-actions.html @@ -0,0 +1,12 @@ +
+ + edit + Edit Email Template + + + + delete + Delete Email Template + + +
\ No newline at end of file diff --git a/portal_client/app/main/email-bank/email-bank.html b/portal_client/app/main/email-bank/email-bank.html new file mode 100644 index 0000000..849ac29 --- /dev/null +++ b/portal_client/app/main/email-bank/email-bank.html @@ -0,0 +1,21 @@ +
+ + + +
+ Available Email Templates + + + filter + Filter + + + add + Add New Email Template + +
+
+
+
+
+
\ No newline at end of file diff --git a/portal_client/app/main/email-bank/email-bank.js b/portal_client/app/main/email-bank/email-bank.js new file mode 100644 index 0000000..16e8bfe --- /dev/null +++ b/portal_client/app/main/email-bank/email-bank.js @@ -0,0 +1,111 @@ +(function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.dashboard.emailBank', ['ui.router']) + .controller('EmailBankCtrl', EmailBankCtrlController); + + function EmailBankCtrlController($scope, cache, systemProvider, systemStores, $mdDialog, + uiGridConstants, portalUtils, Constants) { + + $scope.confirmQuestionBox = true; + + $scope.scripts = []; + + $scope.userRole = ''; + + $scope.Constants = Constants; + + $scope.mCache = cache.request([ + new systemStores.EmailTemplates() + ]).then(function(){ + $scope.emailTemplates = $scope.mCache.emailTmpls.data.values; + }); + + $scope.emailGrid = { + data: 'emailTemplates', + paginationPageSizes: [25, 50, 75, 100, 125, 150, 175, 200], + paginationPageSize: 25, + enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER, + toggleModifierKeysToMultiSelect : true, + enableRowHeaderSelection: true, + multiSelect: false, + noUnselect: false, + onRegisterApi: function (gridApi) { + $scope.emailTmplGridApi = gridApi; + }, + columnDefs: [ + {field: 'emailName', displayName: 'Name', width: '*'}, + {field: 'emailSubject', displayName: 'Subject', width: '**' }, + {field: 'id', displayName: 'Actions', width: '125', enableColumnMenu: false, enableFiltering: false, enableHiding: false, + cellTemplate: 'main/email-bank/cell-templates/email-actions.html' + } + ], + }; + + + $scope.edit = function(entity) { + $mdDialog.show({ + templateUrl: 'common/partials/add-email-tmpl/add-email-tmpl.html', + controller: 'AddEmailTmplCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Edit Email Template', + template: entity + } + } + }); + }; + + $scope.addEmail = function() { + $mdDialog.show({ + templateUrl: 'common/partials/add-email-tmpl/add-email-tmpl.html', + controller: 'AddEmailTmplCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Add New Email Template', + } + } + }); + }; + + $scope.delete = function(entity) { + if ($scope.confirmQuestionBox) { + portalUtils.confirmBox('deleteTmpl', 'Delete Email Template', ['Are you sure you want to delete the template', entity.emailName], + true, function(value) { + if (value.confirm) { + $scope.confirmQuestionBox = !value.checkbox; + $scope.deleteTmpl(entity); + } + }); + } else { + $scope.deleteTmpl(entity); + } + }; + + $scope.deleteTmpl = function(entity) { + systemProvider.deleteEmailTemplates(entity).then(function() { + portalUtils.toast('Email Template deleted successfully', ['top', 'left'], 2000); + },function(err){ + portalUtils.toast('Unable to delete email Template: ' + err.message, ['top', 'left'], 4000); + }); + }; + + $scope.toggleFiltering = function() { + $scope.scriptGrid.enableFiltering = !$scope.scriptGrid.enableFiltering; + $scope.emailTmplGridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN ); + }; + + /* $scope.$on('$destroy', function () { + $scope.mCache.unlisten(); + });*/ + + } //end EmailBankCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/main/env-bank/env-bank.html b/portal_client/app/main/env-bank/env-bank.html new file mode 100644 index 0000000..8cbd9df --- /dev/null +++ b/portal_client/app/main/env-bank/env-bank.html @@ -0,0 +1,29 @@ +
+ + + +
+ Available Environment Variables + + + filter + Filter + + + add + Add New Environment Variable + + + edit + Edit Selected Environment Variable + + + delete + Delete Selected Environment Variable + +
+
+
+
+
+
\ No newline at end of file diff --git a/portal_client/app/main/env-bank/env-bank.js b/portal_client/app/main/env-bank/env-bank.js new file mode 100644 index 0000000..a419fbc --- /dev/null +++ b/portal_client/app/main/env-bank/env-bank.js @@ -0,0 +1,117 @@ +(function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.dashboard.envBank', ['ui.router']) + .controller('EnvBankCtrl', EnvBankCtrlController); + + function EnvBankCtrlController($scope, cache, systemStores, systemProvider, uiGridConstants, $timeout, $mdDialog, portalUtils, Constants) { + + $scope.envars = []; + $scope.Constants = Constants; + $scope.mCache = cache.request([new systemStores.EnvironmentVariables()]).then(function(){ + $scope.envars = $scope.mCache.envvars.data.values; + }); + + $scope.envarGrid = { + data: 'envars', + paginationPageSizes: [25, 50, 75, 100, 125, 150, 175, 200], + paginationPageSize: 25, + enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER, + toggleModifierKeysToMultiSelect : true, + enableRowHeaderSelection: true, + enableColumnMenus: true, + enableGridMenu: true, + multiSelect: true, + noUnselect: false, + onRegisterApi: function (gridApi) { + gridApi.selection.setModifierKeysToMultiSelect(true); + $scope.envGridApi = gridApi; + }, + columnDefs: [ + {field: 'name', displayName: 'Environment Name'}, + {field: 'value', displayName: 'Environment Value'}, + ], + }; + + $scope.$on('$destroy', function () { + $scope.mCache.unlisten(); + }); + + $scope.toggleFiltering = function() { + $scope.envGridApi.enableFiltering = !$scope.envGridApi.enableFiltering; + $scope.envGridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN ); + }; + + var originatorEv; + $scope.openMenu = function($mdOpenMenu, ev) { + originatorEv = ev; + $mdOpenMenu(ev); + }; + + $scope.delete = function() { + var messages = ['Are you sure you want to delete the following environment variables?']; + + var selectedEnvVars = $scope.envGridApi.selection.getSelectedRows(); + for (var i = 0; i < selectedEnvVars.length; i++) { + messages.push(selectedEnvVars[i].name); + } + + portalUtils.confirmBox('deleteEnVars', 'Delete Environment Variables', messages, false, function(value) { + if (value.confirm) { + $scope.deleteEnvVars(selectedEnvVars); + } + }); + }; + + $scope.deleteEnvVars = function(selectedEnvVars) { + systemProvider.deleteEnvVar(selectedEnvVars).then(function() { + portalUtils.toast('Environment Variables deleted successfully', ['top', 'left'], 2000); + }).catch(function(err){ + portalUtils.toast('Unable to delete environment variables: ' + err.message, ['top', 'left'], 4000); + }); + }; + + $scope.edit = function() { + $mdDialog.show({ + templateUrl: 'common/partials/add-env/add-env.html', + controller: 'AddEnvVarCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Edit Environment Variables', + envVars: $scope.envGridApi.selection.getSelectedRows() + } + } + }); + }; + + $scope.addEnvVar = function() { + $mdDialog.show({ + templateUrl: 'common/partials/add-env/add-env.html', + controller: 'AddEnvVarCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Add New Environment Variables', + envVars: [] + } + } + }); + }; + + $scope.selectedCount = function() { + try { + return $scope.envGridApi.selection.getSelectedRows().length; + } catch(err) { + return 0; + } + }; + + } //end EnvBankCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/main/git-bank/cell-templates/git-actions.html b/portal_client/app/main/git-bank/cell-templates/git-actions.html new file mode 100644 index 0000000..6b51f4e --- /dev/null +++ b/portal_client/app/main/git-bank/cell-templates/git-actions.html @@ -0,0 +1,17 @@ +
+ + edit + Edit + + + + play_arrow + Issue pull request + + + + delete + Delete Repository + + +
\ No newline at end of file diff --git a/portal_client/app/main/git-bank/git-bank.html b/portal_client/app/main/git-bank/git-bank.html new file mode 100644 index 0000000..dfc52df --- /dev/null +++ b/portal_client/app/main/git-bank/git-bank.html @@ -0,0 +1,21 @@ +
+ + + +
+ Available Git Repositories + + + filter + Filter + + + add + Add Git Repository + +
+
+
+
+
+
\ No newline at end of file diff --git a/portal_client/app/main/git-bank/git-bank.js b/portal_client/app/main/git-bank/git-bank.js new file mode 100644 index 0000000..9111f97 --- /dev/null +++ b/portal_client/app/main/git-bank/git-bank.js @@ -0,0 +1,151 @@ +(function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.dashboard.gitBank', ['ui.router']) + .controller('GitBankCtrl', GitBankCtrlController); + + function GitBankCtrlController($scope, cache, gitProvider, gitStores, Constants, + accountStores, $mdDialog, uiGridConstants, portalUtils) { + + $scope.confirmQuestionBox = true; + $scope.Permissions = Constants.Permissions; + $scope.repos = []; + + $scope.userRole = ''; + + $scope.dirs = {}; + + $scope.mCache = cache.request([ + new accountStores.Account(), + new gitStores.Repos() + ]).then(function(){ + $scope.userRole = $scope.mCache.account.data.role; + $scope.repos = $scope.mCache.repos.data.values; + }); + + $scope.repoGrid = { + data: 'repos', + paginationPageSizes: [25, 50, 75, 100, 125, 150, 175, 200], + paginationPageSize: 25, + enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER, + toggleModifierKeysToMultiSelect : true, + enableRowHeaderSelection: true, + enableColumnMenus: true, + enableGridMenu: true, + exporterMenuCsv: true, + multiSelect: false, + noUnselect: false, + onRegisterApi: function (gridApi) { + $scope.repoGridApi = gridApi; + }, + columnDefs: [ + {field: 'name', displayName: 'Name', width: '*'}, + {field: 'scm', displayName: 'SCM', width: '**' }, + {field: 'branch', displayName: 'Branch', width: '**' }, + {field: 'directory', displayName: 'Directory', width: '**' }, + {field: 'lastUpdated', displayName: 'Last Updated', width: '**', + cellTemplate: '
{{grid.appScope.formatTimeLong(row.entity.lastUpdated)}}
'}, + {field: 'createDate', displayName: 'Create Date', width: '*', + cellTemplate: '
{{grid.appScope.formatTimeShort(row.entity.createDate)}}
'}, + {field: 'id', displayName: 'Actions', width: '125', enableColumnMenu: false, enableFiltering: false, enableHiding: false, + cellTemplate: 'main/git-bank/cell-templates/git-actions.html' + } + ], + }; + + $scope.formatTimeLong = function(value) { + return moment(value).format('MM/DD/Y HH:mm:ss'); + }; + + $scope.formatTimeShort = function(value) { + return moment(value).format('MM/DD/Y'); + }; + + $scope.$on('$destroy', function () { + $scope.mCache.unlisten(); + }); + + $scope.toggleFiltering = function() { + $scope.repoGrid.enableFiltering = !$scope.repoGrid.enableFiltering; + $scope.repoGridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN ); + }; + + $scope.pull = function(entity) { + portalUtils.confirmBox('updateRepo', 'Update Repository', ['Issue a pull request for', entity.name], + true, function(value) { + if (value.confirm) { + gitProvider.pullRepo(entity).then(function() { + portalUtils.toast('Repository updated successfully', ['top', 'left'], 2000); + new gitStores.Repos().refresh(); + }).catch(function(err){ + portalUtils.toast('Unable to update repository: ' + err.message, ['top', 'left'], 4000); + new gitStores.Repos().refresh(); + }); + } + }); + }; + + $scope.delete = function(entity) { + if ($scope.confirmQuestionBox) { + portalUtils.confirmBox('deleteRepo', 'Delete Repository', ['Are you sure you want to delete the repository', entity.name], + true, function(value) { + if (value.confirm) { + $scope.confirmQuestionBox = !value.checkbox; + $scope.deleteRepo(entity); + } + }); + } else { + $scope.deleteRepo(entity); + } + }; + + $scope.deleteRepo = function(entity) { + gitProvider.deleteRepo(entity).then(function() { + portalUtils.toast('Repository deleted successfully', ['top', 'left'], 2000); + new gitStores.Repos().refresh(); + }).catch(function(err){ + portalUtils.toast('Unable to delete repository: ' + err.message, ['top', 'left'], 4000); + new gitStores.Repos().refresh(); + }); + }; + + $scope.edit = function(entity) { + $mdDialog.show({ + templateUrl: 'common/partials/add-git/add-git.html', + controller: 'AddGitCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Edit Git Repository', + gitInfo: entity + } + } + }).then(function() { + new gitStores.Repos().refresh(); + }); + }; + + $scope.addRepo = function() { + $mdDialog.show({ + templateUrl: 'common/partials/add-git/add-git.html', + controller: 'AddGitCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Add Git Repository', + } + } + }).then(function() { + new gitStores.Repos().refresh(); + }); + }; + + + } //end GitBankCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/main/log-bank/log-bank.html b/portal_client/app/main/log-bank/log-bank.html new file mode 100644 index 0000000..a9c6ea7 --- /dev/null +++ b/portal_client/app/main/log-bank/log-bank.html @@ -0,0 +1,22 @@ +
+ + + +
+ System Logs + + + delete_sweep + Purge Logs + + + filter + Filter Logs + +
+
+
+ +
+
+
\ No newline at end of file diff --git a/portal_client/app/main/log-bank/log-bank.js b/portal_client/app/main/log-bank/log-bank.js new file mode 100644 index 0000000..c668bc3 --- /dev/null +++ b/portal_client/app/main/log-bank/log-bank.js @@ -0,0 +1,126 @@ +(function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.dashboard.logBank', ['ui.router']) + .controller('LogBankCtrl', LogBankCtrlController); + + function LogBankCtrlController($scope, cache, appLogStores, uiGridConstants, $timeout, $mdDialog, Constants) { + + $scope.logs = []; + + $scope.Constants = Constants; + + $scope.mCache = cache.request([new appLogStores.Logs()]).then(function(){ + $scope.logs = $scope.mCache.logs.data.values; + for (var i = 0; i < $scope.logs.length; i++) { + $scope.logs[i].formattedTime = moment($scope.logs[i].timestamp).format('MM/DD/Y HH:mm:ss'); + } + }); + + $scope.logGrid = { + data: 'logs', + paginationPageSizes: [25, 50, 75, 100, 125, 150, 175, 200], + paginationPageSize: 25, + enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER, + toggleModifierKeysToMultiSelect : true, + enableRowHeaderSelection: true, + enableColumnMenus: true, + enableGridMenu: true, + exporterCsvFilename: 'RepoLogs.csv', + exporterMenuCsv: true, + multiSelect: true, + noUnselect: false, + onRegisterApi: function (gridApi) { + gridApi.selection.setModifierKeysToMultiSelect(true); + $scope.logGridApi = gridApi; + }, + columnDefs: [ + {field: 'loggedBy', displayName: 'Logged By', width: '*'}, + {field: 'eventName', displayName: 'Event Type'}, + {field: 'event', displayName: 'Event', width: '*****'}, + {field: 'severity', displayName: 'Severity'}, + {field: 'ipAddress', displayName: 'IP Address', width: '*' }, + {field: 'formattedTime', displayName: 'Timestamp', width: '*' }, + ], + }; + + $scope.$on('$destroy', function () { + $scope.mCache.unlisten(); + }); + + $scope.toggleFiltering = function() { + $scope.logGridApi.enableFiltering = !$scope.logGridApi.enableFiltering; + $scope.logGridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN ); + }; + + var originatorEv; + $scope.openMenu = function($mdOpenMenu, ev) { + originatorEv = ev; + $mdOpenMenu(ev); + }; + + $scope.purge = function() { + $mdDialog.show({ + templateUrl: 'common/partials/templates/purge-logs.tmpl.html', + multipe: true, + skipHide: true, + locals: { + modalInstance: { + 'title': 'Purge Logs', + 'message': 'Select the date to purge logs before' + } + }, + controller: ['$scope', 'modalInstance', '$mdDialog', 'portalUtils', 'appLogProvider', + function($scope, modalInstance, $mdDialog, portalUtils, appLogProvider) { + $scope.title = modalInstance.title; + $scope.message = modalInstance.message; + $scope.loadingBlock = false; + + $scope.model = {}; + + $scope.confirm = function() { + $scope.error = ''; + if ($scope.model.purgeStartDate === undefined) { + $scope.error = 'Purge start date is required'; + return; + } + if ($scope.model.purgeEndDate === undefined) { + $scope.error = 'Purge end date is required'; + return; + } + portalUtils.confirmBox('purgeConfirm', 'Confirm Purge', + ['Are you sure you want to purge files between', + moment($scope.model.purgeStartDate).format('MMMM Do, Y') + + ' and ' + moment($scope.model.purgeEndDate).format('MMMM Do, Y')], false, + function(value) { + if (value.confirm) { + $scope.loadingBlock = true; + appLogProvider.purgeLogs($scope.model).then(function(value) { + $scope.loadingBlock = false; + portalUtils.toast('Successfully purged ' + value + ' logs', ['top', 'left'], 2000); + $mdDialog.hide(); + }).catch(function(err) { + $scope.loadingBlock = false; + portalUtils.toast('Unable to purge logs: ' + err.message, ['top', 'left'], 4000); + }); + } + }); + }; + + $scope.cancel = function() { + $mdDialog.cancel(); + }; + }] + }).then(function() { + new appLogStores.Logs().refresh(); + }); + }; + + } //end LogBankCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/main/permission-bank/cell-templates/permission-actions.html b/portal_client/app/main/permission-bank/cell-templates/permission-actions.html new file mode 100644 index 0000000..39cbcc6 --- /dev/null +++ b/portal_client/app/main/permission-bank/cell-templates/permission-actions.html @@ -0,0 +1,12 @@ +
+ + edit + Edit Permission + + + + delete + Delete Permission + + +
\ No newline at end of file diff --git a/portal_client/app/main/permission-bank/permission-bank.html b/portal_client/app/main/permission-bank/permission-bank.html new file mode 100644 index 0000000..f3e0770 --- /dev/null +++ b/portal_client/app/main/permission-bank/permission-bank.html @@ -0,0 +1,21 @@ +
+ + + +
+ Permissions + + + filter + Filter Permissions + + + add_circle + Add Permissions + +
+
+
+
+
+
\ No newline at end of file diff --git a/portal_client/app/main/permission-bank/permission-bank.js b/portal_client/app/main/permission-bank/permission-bank.js new file mode 100644 index 0000000..680c3e6 --- /dev/null +++ b/portal_client/app/main/permission-bank/permission-bank.js @@ -0,0 +1,132 @@ +(function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.dashboard.permissionBank', ['ui.router']) + .controller('PermissionBankCtrl', PermissionBankCtrlController); + + function PermissionBankCtrlController($scope, cache, permissionStores, permissionProvider, $mdDialog, + uiGridConstants, portalUtils, Constants) { + + $scope.confirmQuestionBox = true; + $scope.permissions = []; + $scope.Constants = Constants; + + $scope.mCache = cache.request([new permissionStores.PermissionGroups()]).then(function(){ + $scope.permissions = $scope.mCache.permissionGroups.data.values; + }); + + portalUtils.setScopeValue($scope, 'loadingBlock', false, true); + + $scope.permGrid = { + data: 'permissions', + paginationPageSizes: [28, 56, 84, 102], + paginationPageSize: 28, + enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER, + toggleModifierKeysToMultiSelect : true, + enableRowHeaderSelection: true, + enableColumnMenus: true, + enableGridMenu: true, + exporterCsvFilename: 'Files.csv', + exporterMenuCsv: true, + multiSelect: true, + noUnselect: false, + onRegisterApi: function (gridApi) { + gridApi.selection.setModifierKeysToMultiSelect(true); + $scope.permGridApi = gridApi; + }, + columnDefs: [ + {field: 'displayName', displayName: 'Permission Group', width: '*'}, + {field: 'enabled', displayName: 'Enabled', width: '*'}, + {field: 'permissions', displayName: 'Allowed Permissions', width: '***', + cellTemplate: '
{{grid.appScope.formatPermissions(row.entity.permissions)}}
'}, + {field: 'id', displayName: 'Actions', width: '100', enableColumnMenu: false, enableFiltering: false, enableHiding: false, + cellTemplate: 'main/permission-bank/cell-templates/permission-actions.html'} + ], + }; + + $scope.formatPermissions = function(perms) { + var p = []; + for (var i = 0; i < perms.length; i++) { + var perm = perms[i]; + if (perm.allowed) { + p.push(perm.displayName); + } + } + return p.join(', '); + }; + + $scope.$on('$destroy', function () { + $scope.mCache.unlisten(); + }); + + $scope.toggleFiltering = function() { + $scope.permGrid.enableFiltering = !$scope.permGrid.enableFiltering; + $scope.permGridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN ); + }; + + var originatorEv; + $scope.openMenu = function($mdOpenMenu, ev) { + originatorEv = ev; + $mdOpenMenu(ev); + }; + + $scope.delete = function(entity) { + if ($scope.confirmQuestionBox) { + portalUtils.confirmBox('deletePermissionGroup', 'Delete Permission Group', ['Are you sure you want to delete the permission group', entity.displayName], + true, function(value) { + if (value.confirm) { + $scope.confirmQuestionBox = !value.checkbox; + $scope.deletePermission(entity); + } + }); + } else { + $scope.deletePermission(entity); + } + }; + + $scope.deletePermission = function(entity) { + portalUtils.setScopeValue($scope, 'loadingBlock', true, true); + permissionProvider.deletePermissionGroup(entity).then(function(data) { + portalUtils.toast('Successfully deleted permission group ' + entity.displayName, ['top', 'left'], 2000); + portalUtils.setScopeValue($scope, 'loadingBlock', false, true); + }).catch(function(err){ + portalUtils.toast('Unable to delete permission group: ' + err.message, ['top', 'left'], 4000); + portalUtils.setScopeValue($scope, 'loadingBlock', false, true); + }); + }; + + $scope.edit = function(entity) { + $mdDialog.show({ + templateUrl: 'common/partials/add-permission/add-permission.html', + controller: 'AddPermissionCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Edit Permission Group', + permission: entity, + } + } + }); + }; + + $scope.addPermissions = function() { + $mdDialog.show({ + templateUrl: 'common/partials/add-permission/add-permission.html', + controller: 'AddPermissionCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Add Permissions', + } + } + }); + }; + + } //end PermissionBankCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/main/script-bank/cell-templates/script-actions.html b/portal_client/app/main/script-bank/cell-templates/script-actions.html new file mode 100644 index 0000000..651f5af --- /dev/null +++ b/portal_client/app/main/script-bank/cell-templates/script-actions.html @@ -0,0 +1,17 @@ +
+ + edit + Edit Script + + + + play_arrow + Manual Run of script + + + + delete + Delete Script + + +
\ No newline at end of file diff --git a/portal_client/app/main/script-bank/cell-templates/script-sched-actions.html b/portal_client/app/main/script-bank/cell-templates/script-sched-actions.html new file mode 100644 index 0000000..4af6901 --- /dev/null +++ b/portal_client/app/main/script-bank/cell-templates/script-sched-actions.html @@ -0,0 +1,10 @@ +
+ + remove_red_eye + View Status Results + + + delete + Delete Schedule + +
\ No newline at end of file diff --git a/portal_client/app/main/script-bank/cell-templates/script-sched-status.html b/portal_client/app/main/script-bank/cell-templates/script-sched-status.html new file mode 100644 index 0000000..5117b6f --- /dev/null +++ b/portal_client/app/main/script-bank/cell-templates/script-sched-status.html @@ -0,0 +1,7 @@ +
+ {{grid.appScope.formatStatus(row.entity)}} +
\ No newline at end of file diff --git a/portal_client/app/main/script-bank/script-bank.html b/portal_client/app/main/script-bank/script-bank.html new file mode 100644 index 0000000..eed240e --- /dev/null +++ b/portal_client/app/main/script-bank/script-bank.html @@ -0,0 +1,21 @@ +
+ + + +
+ Available Scripts + + + filter + Filter + + + add + Add New Script + +
+
+
+
+
+
\ No newline at end of file diff --git a/portal_client/app/main/script-bank/script-bank.js b/portal_client/app/main/script-bank/script-bank.js new file mode 100644 index 0000000..d94c388 --- /dev/null +++ b/portal_client/app/main/script-bank/script-bank.js @@ -0,0 +1,234 @@ +(function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.dashboard.scriptBank', ['ui.router']) + .controller('ScriptBankCtrl', ScriptBankCtrlController); + + function ScriptBankCtrlController($scope, cache, scriptStores, scriptProvider, + accountStores, $mdDialog, uiGridConstants, portalUtils, $log, Triggers, Constants) { + + $scope.confirmQuestionBox = true; + + $scope.scripts = []; + + $scope.userRole = ''; + + $scope.Constants = Constants; + + $scope.mCache = cache.request([ + new accountStores.Account(), + new scriptStores.Scripts() + ]).then(function(){ + $scope.acct = $scope.mCache.account.data; + $scope.scripts = $scope.mCache.scripts.data.values; + for (var i = 0; i < $scope.scripts.length; i++) { + $scope.scripts[i].subGridOptions = { + data: $scope.scripts[i].schedule, + columnDefs: [ + {field: 'jobId', displayName: 'Job Id', width: '**'}, + {field: 'startDatetime', displayName: 'Start Date/Time', width: '*', + cellTemplate: '
{{grid.appScope.formatTimeSub(row.entity.startDatetime)}}
'}, + {field: 'endDatetime', displayName: 'End Date/Time', width: '*', + cellTemplate: '
{{grid.appScope.formatTimeSub(row.entity.endDatetime)}}
'}, + {field: 'frequency', displayName: 'Frequency', width: '*'}, + {field: 'maxExecutions', displayName: 'Max Executions', width: '*'}, + {field: 'currentExecutionCount', displayName: 'Current Count', width: '*'}, + {field: 'lastRun', displayName: 'Last Ran', width: '*', + cellTemplate: '
{{grid.appScope.formatTimeSub(row.entity.lastRun)}}
'}, + {field: 'executionStatus', displayName: 'Status', width: '*', + cellTemplate: 'main/script-bank/cell-templates/script-sched-status.html'}, + {field: 'id', displayName: '', width: '100', enableColumnMenu: false, enableFiltering: false, enableHiding: false, + cellTemplate: 'main/script-bank/cell-templates/script-sched-actions.html'}, + ] + }; + } + }); + + $scope.scriptGrid = { + data: 'scripts', + expandableRowTemplate:'
', + expandableRowScope: { + Constants : $scope.Constants, + formatTimeSub : function(value) { + if (value === null || value === undefined || value === 0) { + return 'None'; + } + return $scope.formatTime(value); + }, + deleteSchedule: function(entity) { + $scope.deleteSchedule(entity); + }, + formatStatus: function(entity) { + return $scope.formatScriptStatus(entity); + }, + viewStatus: function(entity) { + $scope.viewScriptStatus(entity); + } + }, + paginationPageSizes: [25, 50, 75, 100, 125, 150, 175, 200], + paginationPageSize: 25, + enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER, + toggleModifierKeysToMultiSelect : true, + enableRowHeaderSelection: true, + enableColumnMenus: true, + enableGridMenu: true, + exporterMenuCsv: true, + multiSelect: false, + noUnselect: false, + onRegisterApi: function (gridApi) { + $scope.scriptGridApi = gridApi; + }, + columnDefs: [ + {field: 'name', displayName: 'Name', width: '*'}, + {field: 'command', displayName: 'Command', width: '**' }, + {field: 'parameters', displayName: 'Parameters', width: '**', + cellTemplate: '
{{grid.appScope.formatParameters(row.entity.parameters)}}
'}, + {field: 'schedule', displayName: 'Schedule Count', width: '*', + cellTemplate: '
{{row.entity.schedule.length}}
'}, + {field: 'id', displayName: 'Actions', width: '125', enableColumnMenu: false, enableFiltering: false, enableHiding: false, + cellTemplate: 'main/script-bank/cell-templates/script-actions.html' + } + ], + }; + + $scope.formatScriptStatus = function(entity) { + if (entity.currentExecutionCount === null) { + return 'Not Ran'; + } + + if (entity.executionStatus !== 0) { + return 'Failed'; + } + + return 'Success'; + }; + + $scope.formatParameters = function(parameters) { + var params = ''; + + for (var i = 0; i < parameters.length; i++) { + params += parameters[i].parameter + ' '; + } + return params; + }; + + $scope.formatTime = function(value) { + return moment(value).format('MMM D, YYYY hh:mm:ss A'); + }; + + $scope.run = function(entity) { + portalUtils.confirmBox('run', 'Run Script', ['Are you sure you want to manually run the script', entity.name], + true, function(value) { + if (value.confirm) { + $scope.confirmQuestionBox = !value.checkbox; + $scope.runScript(entity); + } + }); + }; + + $scope.runScript = function(entity) { + scriptProvider.runScript(entity).then(function() { + portalUtils.toast('Script scheduled to run', ['top', 'left'], 2000); + }, function(err){ + portalUtils.toast('Unable to run script: ' + err.message, ['top', 'left'], 4000); + new scriptStores.Scripts().refresh(); + }); + }; + + $scope.deleteSchedule = function(entity) { + portalUtils.confirmBox('deleteSched', 'Delete Schedule', ['Are you sure you want to delete the schedule', entity.jobId], + false, function(value) { + if (value.confirm) { + $scope.deleteSched(entity); + } + }); + }; + + $scope.deleteSched = function(entity) { + scriptProvider.deleteScriptSchedule(entity).then(function(){ + portalUtils.toast('Script schedule deleted successfully', ['top', 'left'], 2000); + }, function(err){ + portalUtils.toast('Unable to delete script : ' + err.message, ['top', 'left'], 4000); + }); + }; + + $scope.delete = function(entity) { + if ($scope.confirmQuestionBox) { + portalUtils.confirmBox('deleteScript', 'Delete Script', ['Are you sure you want to delete the script', entity.name], + true, function(value) { + if (value.confirm) { + $scope.confirmQuestionBox = !value.checkbox; + $scope.deleteScript(entity); + } + }); + } else { + $scope.deleteScript(entity); + } + }; + + $scope.deleteScript = function(entity) { + scriptProvider.deleteScript(entity).then(function() { + portalUtils.toast('Script deleted successfully', ['top', 'left'], 2000); + },function(err){ + portalUtils.toast('Unable to delete script: ' + err.message, ['top', 'left'], 4000); + }); + }; + + $scope.edit = function(entity) { + $mdDialog.show({ + templateUrl: 'common/partials/add-script/add-script.html', + controller: 'AddScriptCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Edit Script', + script: entity + } + } + }); + }; + + $scope.addScript = function() { + $mdDialog.show({ + templateUrl: 'common/partials/add-script/add-script.html', + controller: 'AddScriptCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Add New Script', + } + } + }); + }; + + $scope.viewScriptStatus = function(entity) { + $mdDialog.show({ + templateUrl: 'common/partials/script-status/view-script-status.html', + controller: 'ScriptStatusCtrl', + escapeToClose: false, + locals: { + modalInstance: { + jobId: entity.jobId, + header: 'Script Status' + } + } + }); + }; + + $scope.toggleFiltering = function() { + $scope.scriptGrid.enableFiltering = !$scope.scriptGrid.enableFiltering; + $scope.scriptGridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN ); + }; + + $scope.$on('$destroy', function () { + $scope.mCache.unlisten(); + }); + + } //end ScriptBankCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/app/main/user-bank/cell-templates/user-actions.html b/portal_client/app/main/user-bank/cell-templates/user-actions.html new file mode 100644 index 0000000..3384f36 --- /dev/null +++ b/portal_client/app/main/user-bank/cell-templates/user-actions.html @@ -0,0 +1,12 @@ +
+ + edit + Edit User + + + + delete + Delete User + + +
\ No newline at end of file diff --git a/portal_client/app/main/user-bank/user-bank.html b/portal_client/app/main/user-bank/user-bank.html new file mode 100644 index 0000000..6eaa38b --- /dev/null +++ b/portal_client/app/main/user-bank/user-bank.html @@ -0,0 +1,21 @@ +
+ + + +
+ Users + + + filter + Filter Users + + + person_add + Add User + +
+
+
+
+
+
\ No newline at end of file diff --git a/portal_client/app/main/user-bank/user-bank.js b/portal_client/app/main/user-bank/user-bank.js new file mode 100644 index 0000000..0ff1406 --- /dev/null +++ b/portal_client/app/main/user-bank/user-bank.js @@ -0,0 +1,126 @@ +(function () { + 'use strict'; + + /** + * MODULE DESCRIPTION: + * + * TODO: Write description here. + */ + angular.module('portal.dashboard.userBank', ['ui.router']) + .controller('UserBankCtrl', UserBankCtrlController); + + function UserBankCtrlController($scope, cache, accountStores, accountProvider, $mdDialog, uiGridConstants, portalUtils, Constants) { + + $scope.confirmQuestionBox = true; + $scope.Constants = Constants; + $scope.users = []; + + $scope.mCache = cache.request([new accountStores.Account(), new accountStores.UserPool()]).then(function(){ + $scope.userRole = $scope.mCache.account.data.role; + $scope.users = $scope.mCache.userPool.data.values; + }); + + $scope.userGrid = { + data: 'users', + paginationPageSizes: [28, 56, 84, 102], + paginationPageSize: 28, + enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER, + toggleModifierKeysToMultiSelect : true, + enableRowHeaderSelection: true, + enableColumnMenus: true, + enableGridMenu: true, + exporterCsvFilename: 'Files.csv', + exporterMenuCsv: true, + multiSelect: true, + noUnselect: false, + onRegisterApi: function (gridApi) { + gridApi.selection.setModifierKeysToMultiSelect(true); + $scope.userGridApi = gridApi; + }, + columnDefs: [ + {field: 'username', displayName: 'User Name', width: '*'}, + {field: 'firstName', displayName: 'First Name', width: '*', }, + {field: 'lastName', displayName: 'Last Name', width: '**'}, + {field: 'role', displayName: 'Role', width: '**' }, + {field: 'id', displayName: 'Actions', width: '100', enableColumnMenu: false, enableFiltering: false, enableHiding: false, + cellTemplate: 'main/user-bank/cell-templates/user-actions.html'} + ], + }; + + + $scope.$on('$destroy', function () { + $scope.mCache.unlisten(); + }); + + $scope.toggleFiltering = function() { + $scope.userGrid.enableFiltering = !$scope.userGrid.enableFiltering; + $scope.userGridApi.core.notifyDataChange( uiGridConstants.dataChange.COLUMN ); + }; + + var originatorEv; + $scope.openMenu = function($mdOpenMenu, ev) { + originatorEv = ev; + $mdOpenMenu(ev); + }; + + $scope.delete = function(entity) { + if ($scope.confirmQuestionBox) { + portalUtils.confirmBox('deleteUser', 'Delete User', ['Are you sure you want to delete the user', entity.email], + true, function(value) { + if (value.confirm) { + $scope.confirmQuestionBox = !value.checkbox; + $scope.deleteUser(entity); + } + }); + } else { + $scope.deleteUser(entity); + } + + }; + + $scope.deleteUser = function(entity) { + accountProvider.deleteUser(entity).then(function() { + portalUtils.toast('User deleted successfully', ['top', 'left'], 2000); + new accountStores.UserPool().refresh(); + }).catch(function(err){ + portalUtils.toast('Unable to delete user: ' + err.message, ['top', 'left'], 4000); + new accountStores.UserPool().refresh(); + }); + }; + + $scope.edit = function(entity) { + $mdDialog.show({ + templateUrl: 'common/partials/add-user/add-user.html', + controller: 'AddUserCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Edit User', + account: entity, + requirePassword: false, + } + } + }).then(function(){ + new accountStores.UserPool().refresh(); + }); + }; + + $scope.addUser = function() { + $mdDialog.show({ + templateUrl: 'common/partials/add-user/add-user.html', + controller: 'AddUserCtrl', + escapeToClose: false, + locals: { + modalInstance: { + header: 'Add User', + requirePassword: true + } + } + }).then(function(){ + new accountStores.UserPool().refresh(); + }); + }; + + } //end RepoBankCtrlController function + +})(); \ No newline at end of file diff --git a/portal_client/assets.js b/portal_client/assets.js new file mode 100644 index 0000000..dc63e91 --- /dev/null +++ b/portal_client/assets.js @@ -0,0 +1,66 @@ +//File containing all the 3rd party css/js/font/images that need to be packaged with the application +const CSS = [ + 'bootstrap/dist/css/bootstrap.min.css', + 'jquery-ui-dist/jquery-ui.min.css', + 'jquery-ui-dist/jquery-ui.theme.min.css', + 'angular-material/angular-material.min.css', + 'angular-ui-grid/ui-grid.min.css', + 'ng-material-datetimepicker/dist/material-datetimepicker.min.css', + 'tinymce/skins/lightgray/content.min.css', + 'tinymce/skins/lightgray/skin.min.css' +]; + +const JS = [ + 'jquery/dist/jquery.min.js', + 'jquery-ui-dist/jquery-ui.min.js', + 'underscore/underscore-min.js', + 'moment/min/moment.min.js', + 'angular/angular.min.js', + 'angular-animate/angular-animate.min.js', + 'angular-sanitize/angular-sanitize.min.js', + '@uirouter/angularjs/release/angular-ui-router.min.js', + 'angular-resource/angular-resource.min.js', + 'angular-bootstrap/ui-bootstrap-tpls.min.js', + 'angular-aria/angular-aria.min.js', + 'angular-material/angular-material.min.js', + 'angular-messages/angular-messages.min.js', + 'angular-cookies/angular-cookies.min.js', + 'ng-debounce/dist/ng-debounce.js', + 'angular-ui-grid/ui-grid.min.js', + 'PubSub/dist/pubsub.min.js', + 'sockjs-client/dist/sockjs.min.js', + 'stomp-websocket-js/lib/stomp.js', + 'spark-md5/spark-md5.min.js', + 'ng-material-datetimepicker/dist/angular-material-datetimepicker.min.js', + 'tinymce/tinymce.min.js', + 'tinymce/jquery.tinymce.min.js', + 'tinymce/themes/modern/theme.min.js', + 'angular-ui-tinymce/dist/tinymce.min.js' +]; + +const FONTS = [ + 'bootstrap/dist/fonts/glyphicons-halflings-regular.eot', + 'bootstrap/dist/fonts/glyphicons-halflings-regular.svg', + 'bootstrap/dist/fonts/glyphicons-halflings-regular.ttf', + 'bootstrap/dist/fonts/glyphicons-halflings-regular.woff', + 'bootstrap/dist/fonts/glyphicons-halflings-regular.woff2', + 'angular-ui-grid/fonts/ui-grid.eot', + 'angular-ui-grid/fonts/ui-grid.svg', + 'angular-ui-grid/fonts/ui-grid.ttf', + 'angular-ui-grid/fonts/ui-grid.woff', + 'tinymce/skins/lightgray/fonts/tinymce.eot', + 'tinymce/skins/lightgray/fonts/tinymce.svg', + 'tinymce/skins/lightgray/fonts/tinymce.ttf', + 'tinymce/skins/lightgray/fonts/tinymce.woff' +]; + +const IMAGES = [ + 'jquery-ui-dist/images/ui-icons_444444_256x240.png', + 'jquery-ui-dist/images/ui-icons_555555_256x240.png', + 'jquery-ui-dist/images/ui-icons_777620_256x240.png', + 'jquery-ui-dist/images/ui-icons_777777_256x240.png', + 'jquery-ui-dist/images/ui-icons_cc0000_256x240.png', + 'jquery-ui-dist/images/ui-icons_ffffff_256x240.png' +]; + +module.exports =[...JS, ...CSS, ...FONTS, ...IMAGES]; \ No newline at end of file diff --git a/portal_client/index.html b/portal_client/index.html new file mode 100644 index 0000000..97c2bec --- /dev/null +++ b/portal_client/index.html @@ -0,0 +1,125 @@ + + + + NGAS Portal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + \ No newline at end of file diff --git a/portal_client/package.json b/portal_client/package.json new file mode 100644 index 0000000..a040208 --- /dev/null +++ b/portal_client/package.json @@ -0,0 +1,69 @@ +{ + "name": "portal", + "version": "0.0.0", + "private": true, + "dependencies": { + "@uirouter/angularjs": "^1.0.12", + "PubSub": "^3.4.0", + "angular": "^1.6.8", + "angular-animate": "^1.6.8", + "angular-aria": "^1.6.8", + "angular-bootstrap": "^0.12.2", + "angular-cookies": "^1.6.8", + "angular-material": "^1.1.5", + "angular-messages": "^1.6.8", + "angular-mocks": "^1.6.8", + "angular-resource": "^1.6.8", + "angular-route": "^1.6.8", + "angular-sanitize": "^1.6.8", + "angular-ui-grid": "^4.1.3", + "angular-ui-tinymce": "^0.0.19", + "angular-ui-utils": "^0.1.1", + "bootstrap": "^3.3.7", + "jquery": "^3.2.1", + "jquery-ui-dist": "^1.12.1", + "moment": "^2.20.1", + "ng-debounce": "^1.0.1", + "ng-material-datetimepicker": "^1.11.0", + "sockjs-client": "^1.1.4", + "spark-md5": "^3.0.0", + "stomp-websocket-js": "^1.0.0", + "tinymce": "^4.7.6", + "underscore": "^1.8.3" + }, + "devDependencies": { + "copy-webpack-plugin": "^4.3.1", + "css-loader": "^0.28.8", + "file-loader": "^1.1.6", + "grunt": "~1.0.1", + "grunt-angular-templates": "^1.1.0", + "grunt-cli": "^1.2.0", + "grunt-concurrent": "^2.3.1", + "grunt-connect-proxy2": "~2.0.0", + "grunt-contrib-clean": "^1.1.0", + "grunt-contrib-concat": "~1.0.1", + "grunt-contrib-connect": "~1.0.2", + "grunt-contrib-copy": "~1.0.0", + "grunt-contrib-csslint": "^2.0.0", + "grunt-contrib-cssmin": "^2.2.1", + "grunt-contrib-htmlmin": "^2.4.0", + "grunt-contrib-jshint": "^1.1.0", + "grunt-contrib-less": "^1.4.1", + "grunt-contrib-uglify": "^3.1.0", + "grunt-contrib-watch": "~1.0.0", + "grunt-dom-munger2": "~4.0.1", + "grunt-html2js": "^0.5.1", + "grunt-lesslint": "^4.0.0", + "grunt-ng-annotate": "^3.0.0", + "jit-grunt": "^0.10.0", + "serve-index": "^1.9.1", + "serve-static": "^1.13.1", + "style-loader": "^0.19.1", + "time-grunt": "^1.4.0", + "url-loader": "^0.6.2", + "webpack": "^3.10.0" + }, + "scripts": { + "webpack": "webpack" + } +} diff --git a/portal_client/pom.xml b/portal_client/pom.xml new file mode 100644 index 0000000..c3bfe2b --- /dev/null +++ b/portal_client/pom.xml @@ -0,0 +1,178 @@ + + + 4.0.0 + + + net.locusworks + portal + 1.0.0-RELEASE + + + portal_client + Portal Web Client + pom + The Portal client which houses all the css, html and javascript files for the client. It uses AngularJS 1.x with Angular-Material. Data is sent/received to/from the server via rest requests as json. + +Vendor packages are retrieved via yarn and nodejs. They are compiled and injected into the index.html before the whole application is minimized + + + ${project.parent.basedir} + + + + + webapp_distribution + + true + + + + + maven-clean-plugin + 2.6.1 + + + + dist + + **/* + + + + app/yarn_components + + **/* + + + + + + + com.github.eirslett + frontend-maven-plugin + 1.6 + + + + + install node and yarn + initialize + + install-node-and-yarn + + + set strict-ssl false + v8.9.4 + v1.3.2 + + + + + + yarn install + generate-sources + + yarn + + + install + + + + + webpack build + + webpack + + + + generate-sources + + + + -p + + + + + + grunt build + compile + + grunt + + + build + + + + + + grunt test + compile + + grunt + + + unitTests + + + + + + grunt clean + compile + + grunt + + + build + + + + + + + + + + + + + debug + + + + maven-clean-plugin + 2.6.1 + + clean + + + dist + + **/* + + + + + + + delete-dist + clean + + clean + + + + + + + + + + diff --git a/portal_client/webpack.config.js b/portal_client/webpack.config.js new file mode 100644 index 0000000..8f74016 --- /dev/null +++ b/portal_client/webpack.config.js @@ -0,0 +1,43 @@ +const path = require('path'); +const webpack = require('webpack'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +const YarnFileCopy = require('./yarn_file_copy'); + +// assets.js +const Assets = require('./assets'); +module.exports = { + module: { + rules: [ + { + test: /\.(png|woff|woff2|eot|ttf|svg|css|js)$/, + loader: 'file-loader', + options: { + emitFile: false + } + } + ] + }, + entry: Assets, + output: { + path: __dirname + "/app/yarn_components/", + filename: "ignore_me.js" + }, + plugins: [ + new CopyWebpackPlugin( + Assets.map(asset => { + return { + from: path.resolve(__dirname, `./node_modules/${asset}`), + to: path.resolve(__dirname, `./app/yarn_components/${asset}`) + }; + }) + ), + new YarnFileCopy({ + assets: Assets, + indexFile: 'index.html', + outputDir: 'app', + jsDir: 'yarn_components', + cssDir: 'yarn_components', + }) + ] +}; \ No newline at end of file diff --git a/portal_client/yarn_file_copy.js b/portal_client/yarn_file_copy.js new file mode 100644 index 0000000..c733abf --- /dev/null +++ b/portal_client/yarn_file_copy.js @@ -0,0 +1,110 @@ +function YarnFileCopy(options = {}) { + + if (!options.assets === undefined || !Array.isArray(options.assets)) { + throw new Error('No assets defined or assets are not in an array'); + }; + + if (options.indexFile === undefined) { + throw new Error('No index file set. Please specify options.indexFile'); + } + + options.outputDir = options.outputDir || '.'; + options.jsDir = options.jsDir || '.'; + options.cssDir = options.cssDir || '.'; + + //Defaults debug level to 'warning' + options.debug = options.debug || 'warning'; + + // Defaults debugging to info if only true is specified + if (options.debug === true) { + options.debug = 'info'; + } + + const debugLevels = ['warning', 'info', 'debug']; + const debugLevelIndex = debugLevels.indexOf(options.debug); + function log(msg, level) { + if (level === 0) { + msg = `WARNING - ${msg}`; + } else { + level = level || 1; + } + if (level <= debugLevelIndex) { + console.log('[YarnFileCopy] ' + msg); // eslint-disable-line no-console + } + } + + function warning(msg) { + log(msg, 0); + } + + function info(msg) { + log(msg, 1); + } + + function debug(msg) { + log(msg, 2); + } + + const apply = (compiler) => { + compiler.plugin('emit', (compilation, cb) => { + debug('starting emit'); + const callback = () => { + debug('finishing emit'); + cb(); + }; + var fs = require('fs'), + readline = require('readline'), + instream = fs.createReadStream(options.indexFile), + outstream = new(require('stream'))(), + rl = readline.createInterface(instream, outstream); + + var content = ''; + rl.on('line', (line) => { + debug(line); + if (line.indexOf('yarn:css') > -1) { + options.assets + .filter(asset => asset.endsWith('.css')) + .forEach((asset) => { + content += ' \n'; + }); + } else if (line.indexOf('yarn:js') > -1) { + options.assets + .filter(asset => asset.endsWith('.js')) + .forEach((asset) => { + content += ' \n'; + }); + } else { + content += line + '\n'; + } + }); + + rl.on('close', (line) => { + var file = require('fs'); + file.writeFile(options.outputDir + '/index.html', content, (err) =>{ + if (err) { + throw err; + } + debug('new index saved'); + }); + }); + + callback(); + }); + + compiler.plugin('after-emit', (compilation, cb) => { + debug('starting after-emit'); + const callback = () => { + debug('finishing after-emit'); + cb(); + }; + + callback(); + }); + } + + return { + apply + }; +} + +module.exports = YarnFileCopy; \ No newline at end of file diff --git a/portal_common/README.md b/portal_common/README.md new file mode 100644 index 0000000..aa742a6 --- /dev/null +++ b/portal_common/README.md @@ -0,0 +1,4 @@ +Portal Common +== + +The portal_common module contains classes that are shared between the other modules. Any reusable code that is used in multiple places is placed here. \ No newline at end of file diff --git a/portal_common/findbugs-exclude.xml b/portal_common/findbugs-exclude.xml new file mode 100644 index 0000000..1b9aa8a --- /dev/null +++ b/portal_common/findbugs-exclude.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/portal_common/pom.xml b/portal_common/pom.xml new file mode 100644 index 0000000..daf2174 --- /dev/null +++ b/portal_common/pom.xml @@ -0,0 +1,86 @@ + + 4.0.0 + + + net.locusworks + portal + 1.0.0-RELEASE + + + portal_common + Portal Common Files + The common files are used throughout the patch repository project. Code that is used in multiple places are placed in this library for reusability + + + ${project.parent.basedir} + 2.9.4 + 2.5.0 + 2.9.1 + + + + + + org.codehaus.mojo + findbugs-maven-plugin + ${findbugs.version} + + true + target/site/${project.artifactId} + findbugs-exclude.xml + + + + + + + + + + + org.apache.httpcomponents + httpclient + 4.5.5 + + + org.apache.httpcomponents + httpmime + 4.5.5 + + + + + + + + + org.apache.commons + commons-dbcp2 + 2.2.0 + + + org.apache.commons + commons-pool2 + 2.5.0 + + + + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpmime + + + + net.locusworks + portal_json + ${current.version} + + + + + \ No newline at end of file diff --git a/portal_common/src/main/java/net/locusworks/portal/common/PortalDataSource.java b/portal_common/src/main/java/net/locusworks/portal/common/PortalDataSource.java new file mode 100644 index 0000000..de089b8 --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/PortalDataSource.java @@ -0,0 +1,252 @@ +package net.locusworks.portal.common; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.TimeZone; +import java.util.stream.Collectors; + +import javax.sql.DataSource; + +import org.apache.commons.dbcp2.ConnectionFactory; +import org.apache.commons.dbcp2.DriverManagerConnectionFactory; +import org.apache.commons.dbcp2.PoolableConnection; +import org.apache.commons.dbcp2.PoolableConnectionFactory; +import org.apache.commons.dbcp2.PoolingDataSource; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.pool2.impl.AbandonedConfig; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +import net.locusworks.common.logger.ApplicationLogger; +import net.locusworks.common.logger.ApplicationLoggerFactory; +import net.locusworks.portal.common.services.ConfigurationService; + +/*** + * Defines the Portals's database connections + * @author Isaac Parenteau + * + */ +@Primary +@Component +public class PortalDataSource { + + private static ApplicationLogger logger = ApplicationLoggerFactory.getLogger(PortalDataSource.class); + + private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; + private static final String JNDI_STRING = "jdbc:mysql://%s:%d%s"; + + private static Map mysqlProperties; + static { + mysqlProperties = new LinkedHashMap<>(); + mysqlProperties.put("rewriteBatchedStatements", "true"); + mysqlProperties.put("zeroDateTimeBehavior", "CONVERT_TO_NULL"); + mysqlProperties.put("useSSL", "false"); + mysqlProperties.put("serverTimezone", TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT)); + } + + private Map dataSources = new HashMap<>(); + + protected static enum Jndi { + PORTAL_USER, + PORTAL_ROOT + } + + @Autowired + private ConfigurationService configurationService; + + /** + * Default constructor + */ + public PortalDataSource() { + dataSources = new HashMap<>(); + } + + /** + * Create the data source bean + * @return the data source bean + */ + @Bean + public DataSource dataSource() { + return getPortalDataSource(); + } + + /*** + * Creates a database connection + * This will create a connection and store it in the data pool + * which will allow reuse of connections instead of creating new connections every time + * @param url URL to the database + * @param userName The username for the database + * @param password the password for the database + * @return A Datasource representing the connection + * @throws Exception + */ + private static DataSource createDataSource(String url, String userName, String password) throws Exception { + Class.forName(DRIVER).newInstance(); + + Properties props = new Properties(); + props.setProperty("user", userName); + props.setProperty("password", password); + + List params = mysqlProperties + .entrySet() + .stream() + .map(prop -> String.format("%s=%s", prop.getKey(), prop.getValue())) + .collect(Collectors.toList()); + + url = String.format("%s?%s", url, StringUtils.join(params, "&")); + + //Create a connection factory that the pool will use to create connections + ConnectionFactory cf = new DriverManagerConnectionFactory(url, props); + //Create the poolable connection factory + PoolableConnectionFactory pcf = new PoolableConnectionFactory(cf, null); + pcf.setValidationQuery("SELECT 1"); + pcf.setDefaultAutoCommit(true); + + GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); + poolConfig.setMinIdle(10); + poolConfig.setMaxTotal(100); + + AbandonedConfig abandonConfig = new AbandonedConfig(); + abandonConfig.setRemoveAbandonedTimeout(60); + abandonConfig.setLogAbandoned(false); + abandonConfig.setRemoveAbandonedOnBorrow(true); + abandonConfig.setRemoveAbandonedOnMaintenance(true); + + //Create the pool of connections + GenericObjectPool connectionPool = new GenericObjectPool<>(pcf, poolConfig); + connectionPool.setTestOnBorrow(true); + connectionPool.setTestWhileIdle(true); + connectionPool.setTimeBetweenEvictionRunsMillis(10000); + connectionPool.setMinEvictableIdleTimeMillis(1000); + connectionPool.setAbandonedConfig(abandonConfig); + pcf.setPool(connectionPool); + + //Pooling data source itself + PoolingDataSource dataSource = new PoolingDataSource<>(connectionPool); + return dataSource; + } + + /** + * Creates a datasouce from the JNDI type + * @param jndi Root or User JNDI + * @return + * @throws Exception + */ + private DataSource createDataSource(Jndi jndi) throws Exception { + + if (dataSources.containsKey(jndi)) { + return dataSources.get(jndi); + } + + String tail = null; + boolean isRoot = false; + switch (jndi) { + case PORTAL_ROOT: + tail = ""; + isRoot = true; + break; + case PORTAL_USER: + tail = "/portal"; + break; + default: + throw new Exception("Unknown data source specifier: " + jndi); + } + + String userName = isRoot ? configurationService.getDatabaseRootUsername() : configurationService.getDatabaseUsername(); + String passwd = isRoot? configurationService.getDatabaseRootPassword() : configurationService.getDatabasePassword(); + + String url = String.format(JNDI_STRING, configurationService.getDatabaseHost(), configurationService.getDatabasePort(), tail); + + dataSources.put(jndi, createDataSource(url, userName, passwd)); + + return dataSources.get(jndi); + } + + /** + * Get the datasource + * @param jndiName name of the data source to retrieve + * @return + */ + private DataSource getDataSource(Jndi jndiName) { + // locate the DataSource + DataSource ds = null; + try { + ds = dataSources.get(jndiName); + if (ds == null) { + logger.info("JNDI %s not found. Creating it", jndiName.toString()); + // Get the data source from initial context or by building our own pooling data source + ds = createDataSource(jndiName); + dataSources.put(jndiName, ds); + } else { + logger.info("Loading JNDI %s", jndiName.toString()); + } + } catch (Exception e) { + logger.error("Unable to get datasource", e); + } + return ds; + } + + /** + * Get the normal data source that is used for the portal application + * @return the user data source + */ + public DataSource getPortalDataSource() { + return getDataSource(PortalDataSource.Jndi.PORTAL_USER); + } + + /** + * Get the database root data source. + * This data source is only used to do the migrations then closed + * @return the root data source + */ + public DataSource getPortalRootDataSource() { + return getDataSource(PortalDataSource.Jndi.PORTAL_ROOT); + } + + /** + * Close the root data source once it is no longer necessary. + */ + public void closeRootDataSource() { + DataSource ds = dataSources.get(PortalDataSource.Jndi.PORTAL_ROOT); + try { + if (ds != null) { + if (ds instanceof PoolingDataSource) { + ((PoolingDataSource)ds).close(); + } else { + ds.getConnection().close(); + } + dataSources.remove(PortalDataSource.Jndi.PORTAL_ROOT); + } + } catch (Exception e) { } + } + + /** + * Close the data source connections when the server shots down + */ + public void closeDataSources() { + for (Iterator> it = dataSources.entrySet().iterator(); it.hasNext();) { + Map.Entry ds = it.next(); + try { + logger.info("Closing datasource " + ds.getKey()); + DataSource dataSource = ds.getValue(); + if (dataSource instanceof PoolingDataSource) { + ((PoolingDataSource)dataSource).close(); + } else { + dataSource.getConnection().close(); + } + } catch (Exception ex) { + logger.error("Unable to close datasource %s -> %s", ds.getKey(), ex.getMessage()); + } finally { + it.remove(); + } + } + } +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/enums/Configuration.java b/portal_common/src/main/java/net/locusworks/portal/common/enums/Configuration.java new file mode 100644 index 0000000..860040e --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/enums/Configuration.java @@ -0,0 +1,108 @@ +package net.locusworks.portal.common.enums; + +/** + * Enumeration specifying the allowed configuration values + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + */ +public enum Configuration { + DB_ROOT_USER("dbRootUser"), + DB_ROOT_PASSWORD("dbRootPassword"), + DB_USER("dbUsername"), + DB_PASSWORD("dbPassword"), + DB_HOST("dbHost"), + DB_PORT("dbPort"), + USER_EXPIRATION_DAYS("userExpirationDays"), + CA_CERT_FILE("caCertFile"), + CA_CERT_PASSWORD("caCertPassword"), + AES_SEED_FILE("aesSeedFile"), + LOG_LEVEL("logLevel"); + + private String value; + + private Configuration(String value) { + this.value = value; + } + + /** + * Get the current value of the enumeration + * @return value + */ + public String getValue() { + return this.value; + } + + @Override + public String toString() { + return this.value; + } + + /** + * Checks to see if the configuration is of a certain value + * @param confTypes types to check against + * @return true if the current configuration is of a certain enumeration, false otherwise + */ + public boolean is(Configuration... confTypes) { + for (Configuration confType : confTypes) { + if (this == confType) { + return true; + } + } + return false; + } + + /** + * Checks to see if the configuration is not of a certain value + * @param confTypes Types to check against + * @return true if the current configuration is not of a certain enuermation, false otherwise + */ + public boolean isNot(Configuration... confTypes) { + return !is(confTypes); + } + + /** + * Finds a specific enumeration + * @param value Value to find + * @return the found enumeration or null if nothing is found + */ + public static Configuration findEnum(String value) { + for (Configuration conf: values()) { + if (conf.getValue().equalsIgnoreCase(value)) { + return conf; + } + } + return null; + } + + /** + * Checks to see if a specific value is in a set of configuration types + * @param value Value to look for + * @param confTypes types to compare against + * @return true if the string value is in a certain set of configuration enumerations, false otherwise + */ + public static boolean in(String value, Configuration... confTypes) { + Configuration ft = findEnum(value); + if (ft == null) { + return false; + } + + for (Configuration confType : confTypes) { + if (ft == confType) { + return true; + } + } + + return false; + } + + /** + * Checks to see if a specific value is not in a set of configuration types + * @param value Value to look for + * @param confTypes types to compare against + * @return true if the string value is not in a certain set of configuration enumerations, false otherwise + */ + public static boolean notIn(String value, Configuration... confTypes) { + return !in(value, confTypes); + } +} \ No newline at end of file diff --git a/portal_common/src/main/java/net/locusworks/portal/common/enums/CredentialType.java b/portal_common/src/main/java/net/locusworks/portal/common/enums/CredentialType.java new file mode 100644 index 0000000..46621c1 --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/enums/CredentialType.java @@ -0,0 +1,71 @@ +package net.locusworks.portal.common.enums; + +/** + * Enumeration specifying the allowed credential types + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + */ +public enum CredentialType { + SSH, + PASSWD; + + /** + * Checks to see if the credential type is of a certain value + * @param credentialTypes types to check against + * @return true if the current credential type is of a certain enumeration, false otherwise + */ + public boolean is(CredentialType... credentialTypes) { + for (CredentialType credType : credentialTypes) { + if (this == credType) { + return true; + } + } + return false; + } + + /** + * Get the credential type based off the string value + * @param value value to check + * @return the credential type if one is found or null + */ + public static CredentialType getEnum(String value) { + for (CredentialType type : values()) { + if (type.toString().equalsIgnoreCase(value)) { + return type; + } + } + return null; + } + + /** + * Checks to see if a specific value is in a set of credential types + * @param value Value to look for + * @param credentialTypes types to compare against + * @return true if the string value is in a certain set of credential types enumerations, false otherwise + */ + public static boolean in(String value, CredentialType... credentialTypes) { + CredentialType ft = getEnum(value); + if (ft == null) { + return false; + } + + for (CredentialType freqType : credentialTypes) { + if (ft == freqType) { + return true; + } + } + + return false; + } + + /** + * Checks to see if a specific value is not in a set of credential types + * @param value Value to look for + * @param credentialTypes types to compare against + * @return true if the string value is not in a certain set of credential types enumerations, false otherwise + */ + public static boolean notIn(String value, CredentialType... credentialTypes) { + return !in(value, credentialTypes); + } +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/enums/EventType.java b/portal_common/src/main/java/net/locusworks/portal/common/enums/EventType.java new file mode 100644 index 0000000..34ed3f3 --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/enums/EventType.java @@ -0,0 +1,65 @@ +package net.locusworks.portal.common.enums; + +/** + * Enumeration specifying the different types of allowed events + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + */ +public enum EventType { + LOGIN, + LOGOUT, + USER_ADDED, + USER_EDITED, + USER_DELETED, + CREDENTIALS_ADDED, + CREDENTIALS_REMOVED, + REPOSITORY_ADDED, + REPOSITORY_PULL, + REPOSITORY_DELETED, + SCRIPT_ADDED, + SCRIPT_EDITED, + SCRIPT_DELETED, + SCRIPT_SCHEDULE_DELETED, + ENVVAR_ADDED, + ENVVAR_EDITED, + ENVVAR_DELETED, + SCRIPT_RUN, + SCRIPT_END, + SCRIPT_ERROR, + SSH_RUN, + SSH_END, + SSH_ERROR, + SCP_RUN, + SCP_END, + SCP_ERROR, + LOGS_PURGED, + HASH_TYPE_UPDATED, + CONFIGURATION_UPDATED, + PERMISSIONS_CHANGED, + PERMISSIONS_DELETED, + DIRECTORY_PERMISSIONS_CHANGED, + DIRECTORY_PERMISSIONS_REMOVED, + FILE_DOWNLOADED, + FILE_DELETED, + EMAIL_TEMPLATED_ADDED, + EMAIL_TEMPLATED_EDITED, + EMAIL_TEMPLATED_DELETED, + SMTP_CONFIG_CHANGED, + MESSAGE, + EXCEPTION; + + public String getValue() { + //Returns the enum type with the underscores replaced with spaces + return this.toString().replace("_", " ").trim().toUpperCase(); + } + + public static EventType getEnum(String value) { + for (EventType type : values()) { + if (type.getValue().equalsIgnoreCase(value)) { + return type; + } + } + throw new IllegalArgumentException("No EventType found for value of: " + value); + } +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/enums/FrequencyType.java b/portal_common/src/main/java/net/locusworks/portal/common/enums/FrequencyType.java new file mode 100644 index 0000000..9a73c2d --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/enums/FrequencyType.java @@ -0,0 +1,63 @@ +package net.locusworks.portal.common.enums; + +/** + * Enumeration containing the frequency types allowed for scheduling events + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + */ +public enum FrequencyType { + NOW, + HOURLY, + DAILY, + WEEKLY, + MONTHLY, + YEARLY, + SPECIFIC, + TRIGGERED; + + /** + * Get a frequency type by string value + * @param value frequency type to check for + * @return frequencyType if found; null otherwise + */ + public static FrequencyType getEnum(String value) { + for (FrequencyType type : values()) { + if (type.toString().equalsIgnoreCase(value)) { + return type; + } + } + return null; + } + + /** + * checks to see if a string value is in a certain set of frequency types + * @param value Value to check + * @param frequencyTypes frequency types to check against + * @return true if found within the set, false otherwise + */ + public static boolean in(String value, FrequencyType... frequencyTypes) { + FrequencyType ft = getEnum(value); + if (ft == null) { + return false; + } + + for (FrequencyType freqType : frequencyTypes) { + if (ft == freqType) { + return true; + } + } + + return false; + } + + /** + * checks to see if a string value is not in a certain set of frequency types + * @param value Value to check + * @param frequencyTypes frequency types to check against + * @return false if found within the set, true otherwise + */ + public static boolean notIn(String value, FrequencyType... frequencyTypes) { + return !in(value, frequencyTypes); + } +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/enums/PermissionType.java b/portal_common/src/main/java/net/locusworks/portal/common/enums/PermissionType.java new file mode 100644 index 0000000..51009e0 --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/enums/PermissionType.java @@ -0,0 +1,49 @@ +package net.locusworks.portal.common.enums; + +/** + * Enumeration specifying the allowed permissions + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + * + */ +public enum PermissionType { + VIEW_SCRIPT_TAB, + VIEW_GIT_TAB, + VIEW_USER_TAB, + VIEW_CREDS_TAB, + VIEW_LOG_TAB, + VIEW_PERMISSION_TAB, + VIEW_EMAIL_TAB, + VIEW_ENV_VARS_TAB, + VIEW_CONFIG, + CHANGE_CONFIG, + VIEW_SMTP_CONFIG, + EDIT_SMTP_CONFIG, + VIEW_HASH_TYPES, + CHANGE_HASH_TYPES, + ADD_USERS, + DELETE_USERS, + PURGE_LOGS, + ADD_PERMS, + DELETE_PERMS, + ADD_CREDENTIALS, + DELETE_CREDENTIALS, + ADD_GIT_REPO, + PULL_GIT_REPO, + DELETE_GIT_REPO, + ADD_EMAIL, + DELETE_EMAIL, + RUN_SCRIPT, + ADD_SCRIPT, + DELETE_SCRIPT, + VIEW_SCRIPT_SCHED_STATUS, + DELETE_SCRIPT_SCHED_STATUS, + ADD_ENV_VARS, + DELETE_ENV_VARS, + VIEW_ENV_VARS, + VIEW_LOG_LEVELS, + CHANGE_LOG_LEVELS, + DELETE_FILE, + UPDATE_SEED_VALUE +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/enums/ScriptType.java b/portal_common/src/main/java/net/locusworks/portal/common/enums/ScriptType.java new file mode 100644 index 0000000..5a9b92b --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/enums/ScriptType.java @@ -0,0 +1,28 @@ +package net.locusworks.portal.common.enums; + +/** + * Enumeration for the allowed script types when creating a task + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + */ +public enum ScriptType { + LOCAL, + REMOTE, + SCP, + NULL; + + /** + * Get a script type based off from string value + * @param value value to check + * @return script type if the value is found. False otherwise + */ + public static ScriptType getEnum(String value) { + for (ScriptType type : values()) { + if (type.toString().equalsIgnoreCase(value)) { + return type; + } + } + return ScriptType.NULL; + } +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/enums/TriggerType.java b/portal_common/src/main/java/net/locusworks/portal/common/enums/TriggerType.java new file mode 100644 index 0000000..34d8c6b --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/enums/TriggerType.java @@ -0,0 +1,32 @@ +package net.locusworks.portal.common.enums; + +/** + * Enumeration specifying the allowed trigger types + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + * + */ +public enum TriggerType { + APP_LOGS_UPDATED, + APP_LOGS_PURGED, + SCRIPT_COMPLETED, + SCRIPT_DELETED, + SCRIPT_ADDED, + SCRIPT_UPDATED, + SCRIPT_SCHEDULE_DELETED, + USER_ADDED, + USER_DELETED, + HASH_TYPES_CHANGED, + PERMISSIONS_DELETED, + PERMISSIONS_CHANGED, + PERMISSIONS_UPDATED, + PERMISSIONS_ADDED, + FILE_DELETED, + ENV_VARS_ADDED, + ENV_VARS_DELETED, + EMAIL_TEMPLATE_UPDATED, + EMAIL_TEMPLATE_DELETED, + SMTP_CONFIG_UPDATED, + SEED_UPDATED +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/exceptions/PortalException.java b/portal_common/src/main/java/net/locusworks/portal/common/exceptions/PortalException.java new file mode 100644 index 0000000..8d24fd3 --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/exceptions/PortalException.java @@ -0,0 +1,49 @@ +package net.locusworks.portal.common.exceptions; + +import net.locusworks.common.exceptions.ApplicationException; + +/*** + * Custom exception class for the portal program + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + */ +public class PortalException extends ApplicationException { + private static final long serialVersionUID = 1L; + + /** + * Exception is thrown when there is no file found with a given file name + * @param fileName The file name of the file trying to be located + * @return no such file error message + */ + public static PortalException noSuchFile(String fileName) { + return new PortalException(9010, String.format("No such file found for %s", fileName)); + } + + /** + * Exception thrown when a file is unable to be deleted + * @param filename Name of the file that couldn't be deleted + * @return unable to delete file error message + */ + public static PortalException unableToDeleteFile(String filename) { + return new PortalException(9010, String.format("Unable to delete file %s. Please check permissions", filename)); + } + + /** + * Constructor for the PortalException + * @param code error code + * @param message error message + */ + private PortalException(int code, Exception ex) { + super(code, ex); + } + + /** + * Constructor for the PortalException + * @param code error code + * @param message error message + */ + private PortalException(int code, String message) { + super(code, message); + } +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/http/HttpClientHelper.java b/portal_common/src/main/java/net/locusworks/portal/common/http/HttpClientHelper.java new file mode 100644 index 0000000..0cf97f5 --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/http/HttpClientHelper.java @@ -0,0 +1,131 @@ +package net.locusworks.portal.common.http; + +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; + +import net.locusworks.common.logger.ApplicationLogger; +import net.locusworks.common.logger.ApplicationLoggerFactory; + +/** +* Class to handle http(s) connections +* @author Isaac Parenteau +* @since 1.0.0-RELEASE +* @version 1.0 +*/ +public class HttpClientHelper { + + private static ApplicationLogger logger = ApplicationLoggerFactory.getLogger(HttpClientHelper.class); + + public enum HttpSchema { + HTTP, + HTTPS; + + public static HttpSchema findEnum(String value) { + for (HttpSchema schema : values()) { + if (value.equalsIgnoreCase(schema.toString())) { + return schema; + } + } + return null; + } + } + + private static final String[] TLS = new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"}; + + private HttpClient client; + private String baseUrl; + + /** + * Constructor to handle http connection + * @param protocol protocol to use (http or https) + * @param host the host url + * @param port the host port + * @throws Exception exception + */ + public HttpClientHelper(String protocol, String host, String port) throws Exception { + HttpSchema schema = HttpSchema.findEnum(protocol); + if (schema == null) { + throw new Exception("Unable to find http schema of " + protocol); + } + this.baseUrl = String.format("%s://%s:%s", schema.toString().toLowerCase(), host, port); + this.client = createClient(schema); + } + + /** + * Create a http client + * @param schema Schema to connect via (HTTP or HTTPS) + * @return newly created http client + * @throws Exception thrown when the client can't be created + */ + private HttpClient createClient(HttpSchema schema) throws Exception { + HttpClientBuilder builder = HttpClientBuilder.create(); + + if (schema == HttpSchema.HTTP) { + return builder.build(); + } + + //Trust all ssl certificates + TrustManager[] trustAllCerts = new TrustManager[] { + new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { return null; } + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } + } + }; + + //Setup the ssl instance using tls + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new SecureRandom()); + + SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, TLS, null, new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { return true; } + }); + + builder = builder.setSSLSocketFactory(sslsf); + + return builder.build(); + } + + /** + * Get the http GET response code + * @param endpoint endpoint to get the response from + * @return responseCode + * @throws Exception general exception + */ + public Integer getGetResponseCode(String endpoint) throws Exception { + String url = this.baseUrl + endpoint; + + HttpResponse response = this.client.execute(new HttpGet(url)); + + HttpEntity entity = response.getEntity(); + + String responseString = EntityUtils.toString(entity); + + logger.info("Response from stomp: " + responseString); + + Integer responseCode = response.getStatusLine().getStatusCode(); + + EntityUtils.consume(entity); + + return responseCode; + }; +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/services/AESService.java b/portal_common/src/main/java/net/locusworks/portal/common/services/AESService.java new file mode 100644 index 0000000..16b37ca --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/services/AESService.java @@ -0,0 +1,181 @@ +package net.locusworks.portal.common.services; + +import javax.annotation.PostConstruct; + +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Service; + +import net.locusworks.common.crypto.AES; +import net.locusworks.common.utils.Utils; +import net.locusworks.portal.common.triggers.SeedTrigger; + +import static net.locusworks.common.Charsets.UTF_8; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Set; + +/** + * Service to handle AES encryption + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + */ +@Service +public class AESService implements ApplicationListener{ + + private static final String PORTAL_AES_SEED = "PORTAL_AES_SEED"; + + private static final String DEFAULT_SEED = "portal.tomcat"; + + private String seedLoc; + + private AES aes; + + @Autowired + private ConfigurationService confService; + + /** + * Initialize the aes engine by loading the seed file. + * This file is needed to decrypt the aes. + */ + @PostConstruct + public void init() { + try { + confService.init(); //initialize our conf file to pick up the latest changes if any + //Check to see if the environment variable is set first + seedLoc = System.getProperty(PORTAL_AES_SEED) == null ? System.getenv(PORTAL_AES_SEED) : System.getProperty(PORTAL_AES_SEED); + if (seedLoc == null) { + //If it can't find it in the environment variable load it from the properties file + seedLoc = confService.getAesSeedFile(); + } + if (seedLoc == null) { + throw new RuntimeException("Seed location is not set... unable to continue"); + } + File seedFile = new File(seedLoc); + if (!seedFile.exists()) { + try(BufferedReader br = getReader()) { + StringBuilder seeder = new StringBuilder(); + while(br.ready()) { + seeder.append(br.readLine()); + } + saveSeed(seeder.toString()); + } + seedFile = new File(seedLoc); + if (!seedFile.canRead()) { + throw new IOException("Unable to read file"); + } + } + + String seed = IOUtils.toString(new FileInputStream(seedFile), UTF_8).trim(); + aes = AES.createInstance(seed); + } catch (Exception e) { + throw new RuntimeException("Unable to read file", e); + } + } + + private BufferedReader getReader() throws IOException { + InputStream is = this.getClass().getResourceAsStream(DEFAULT_SEED); + if (is == null) { + is = this.getClass().getClassLoader().getResourceAsStream(DEFAULT_SEED); + } + if (is == null) { + throw new IOException("Unable to read default seed file from resources"); + } + return new BufferedReader(new InputStreamReader(is, UTF_8)); + } + + /** + * Encrypt a string + * @param plainText string to encrypt + * @return encrypted string + */ + public String encrypt(String plainText) { + return aes.encrypt(plainText); + } + + /** + * Encrypt a byte array + * @param plainText the byte array to encrypt + * @return encrypted string + */ + public byte[] encrypt(byte[] plainText) { + return encrypt(new String(plainText, UTF_8)).getBytes(UTF_8); + } + + /** + * Decrypt an ecnrypted byte array + * @param cypherBytes byte array to decrypt + * @return decrypted string + */ + public String decrypt(byte[] cypherBytes) { + return decrypt(new String(cypherBytes, UTF_8)); + } + + /** + * Decrypt an encrypted string + * @param cypherText the encrypted string to decrypt + * @return decrypted string + */ + public String decrypt(String cypherText) { + return aes.decrypt(cypherText); + } + + /** + * Change the encryption value + * @param oldEncryptedValue the old encryption value + * @param newSeed the new seed to set the value too + * @return new encryption value + */ + public String changeEncryptionValue(String oldEncryptedValue, String newSeed) { + String oldValue = decrypt(oldEncryptedValue); + String newValue = AES.createInstance(newSeed).encrypt(oldValue); + return newValue; + } + + public String changeEncryptionValue(byte[] oldValue, String newSeed) { + return changeEncryptionValue(new String(oldValue, UTF_8), newSeed); + } + + /** + * Change the seed file with the new seed.
+ * Warning: once the seed has changed all encrypted values that + * were encrypted with the previous seed will no longer decrypt. + * Make sure to write a service that will decrypt all the previous values + * then encrypt with new seed + * @param newSeed the new seed to use + * @throws IOException occurs when the seed can't be written + */ + public void saveSeed(String newSeed) throws IOException { + File seedFile = new File(seedLoc); + if (seedFile.exists() && (!seedFile.isFile() || !seedFile.canRead())) { + throw new IOException("Unable to read or locate file"); + } + try(FileWriter fw = new FileWriter(seedFile, false)) { + fw.write(newSeed); + fw.flush(); + } + + try { + //Set the file permission will throw UnsupportedOperationException if on windows machine + //Can ignore the exception + Set perms = Utils.toSet(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE); + Files.setPosixFilePermissions(seedFile.toPath(), perms); + } catch (UnsupportedOperationException ex) {} + } + + @Override + public void onApplicationEvent(SeedTrigger event) { + String seed = event.getSeed(); + this.aes.setSeed(seed); + } +} \ No newline at end of file diff --git a/portal_common/src/main/java/net/locusworks/portal/common/services/ConfigurationService.java b/portal_common/src/main/java/net/locusworks/portal/common/services/ConfigurationService.java new file mode 100644 index 0000000..aedd784 --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/services/ConfigurationService.java @@ -0,0 +1,420 @@ +package net.locusworks.portal.common.services; + +import static net.locusworks.portal.common.utils.Constants.PROPERTIES_FILE; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Enumeration; +import java.util.Map; +import java.util.Properties; +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import net.locusworks.common.configuration.PropertiesManager; +import net.locusworks.common.logger.ApplicationLogger; +import net.locusworks.common.logger.ApplicationLoggerFactory; +import net.locusworks.portal.common.enums.Configuration; +import net.locusworks.portal.common.exceptions.PortalException; +import net.locusworks.portal.json.ConfigurationRequest; + +/** + * Configuration Service class that reads the conf file + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + */ +@Service +public final class ConfigurationService { + + private Properties configuration; + private Properties defaults = null; + private File portalConf = null; + private String keyDir = null; + private long lastModified = 0; + + private static final String DB_ROOT_USER_PROPERTY = "DB_ROOT_USER"; + private static final String DB_ROOT_PASSWD_ENV = "DB_ROOT_PASSWD"; + + private static ApplicationLogger logger = ApplicationLoggerFactory.getLogger(ConfigurationService.class); + + @Autowired + private AESService aesService; + + /** + * Initialize the configuration service + * This happens during server start up + * @throws IOException If the file cannot be read + * @throws Exception any other exception thrown + */ + @PostConstruct + public void init() throws IOException, Exception { + + // load defaults when the webapp loads + try { + defaults = PropertiesManager.loadConfiguration(this.getClass(), PROPERTIES_FILE); + } catch (IOException ex) { + logger.error(String.format("Failed to load default %s: %s", PROPERTIES_FILE, ex.getMessage())); + throw ex; + } + + // create portalConf File object + portalConf = new File(String.format("%s/conf/%s", System.getProperty("catalina.base"), PROPERTIES_FILE)); + + //Create a temporary key directory + keyDir = String.format("%s/keys", System.getProperty("catalina.base")); + File keyDirFile = new File(keyDir); + if (!keyDirFile.exists()) { + try { + keyDirFile.mkdirs(); + } catch (Exception ex) { + throw ex; + } + } + // initial config load + loadConf(); + } + + /** + * Load the configuration file + */ + private void loadConf() { + + // load the active config file + // ignore read error, we can continue with an empty configuration map + // and all default items will be added below and the file created + logger.info("Loading config file: " + portalConf); + try { + configuration = PropertiesManager.loadConfiguration(portalConf); + } catch (Exception e) { + logger.info("Config file: " + portalConf + " will be created from template"); + configuration = new Properties(); + } + + Map results = PropertiesManager.addConfiguration(configuration, defaults); + boolean changed = !results.isEmpty(); + if (!results.isEmpty()) { + logger.info(results, new StringBuilder("Added new configuration items:%n")); + } + + results = PropertiesManager.removeConfiguration(configuration, defaults); + changed |= !results.isEmpty(); + if (!results.isEmpty()) { + logger.info(results, new StringBuilder("Removed unused configuration items:%n")); + } + + if (changed) { + PropertiesManager.saveConfiguration(configuration, portalConf, "Patch Repository properties file"); + } + + lastModified = portalConf.lastModified(); + } + + /** + * Save the configuration values to file + * @param confs Configuration property values to save + * @throws Exception general exception + */ + public void saveToConf(Properties confs) throws Exception { + PropertiesManager.saveConfiguration(confs, portalConf, "Patch Repository properties file"); + logger.info("Saved config file: " + portalConf + ", " + confs.size() + " entries"); + loadConf(); + } + + /** + * Get the database root user name. + * It first checks the catalina.properties file for the root user + * then checks the patchrepo.properties file + * @return root database username + */ + public String getDatabaseRootUsername() { + String rootUser = System.getProperty(DB_ROOT_USER_PROPERTY); + return rootUser != null ? rootUser : configuration.getProperty(Configuration.DB_ROOT_USER.getValue()); + } + + /** + * Get the database root user password + * It first checks the catalina.properties file for the password + * then checks the patchrepo.properties file. + * The password must be encrypted in both locations + * @return root database password + */ + public String getDatabaseRootPassword() { + String rootPassword = System.getProperty(DB_ROOT_PASSWD_ENV); + String dbPasswd = rootPassword == null ? configuration.getProperty(Configuration.DB_ROOT_PASSWORD.getValue()) : rootPassword; + try { + return aesService.decrypt(dbPasswd); + } catch (Exception ex) { + logger.error("Unable to get db root password " + ex.getMessage()); + return null; + } + } + + + /** + * Get the standard database user name + * @return portal database username + */ + public String getDatabaseUsername() { + return configuration.getProperty(Configuration.DB_USER.getValue()); + } + + /** + * Get the standard database password. + * The password is encrypted in the properties file + * @return patchrepo database password + */ + public String getDatabasePassword() { + String dbPasswd = configuration.getProperty(Configuration.DB_PASSWORD.getValue()); + try { + return aesService.decrypt(dbPasswd); + } catch (Exception ex) { + logger.error("Unable to get db password " + ex.getMessage()); + return null; + } + } + + /** + * Get the database host url + * @return database host url + */ + public String getDatabaseHost() { + return configuration.getProperty(Configuration.DB_HOST.getValue()); + } + + /** + * Get the database host port + * @return the database port + */ + public Integer getDatabasePort() { + return Integer.parseInt(configuration.getProperty(Configuration.DB_PORT.getValue())); + } + + /** + * Get the default number of days until a new user expires + * @return number of days after creation the user will expire by default + */ + public Integer getUserExpirationDays() { + return Integer.parseInt(configuration.getProperty(Configuration.USER_EXPIRATION_DAYS.getValue())); + } + + /** + * Directory to store temporary private keys + * @return directory location + */ + public String getKeyDirectory() { + return keyDir; + } + + /** + * Get the CA cert file + * @return the ca cert file + */ + public String getCaCertFile() { + String caCertFile = configuration.getProperty(Configuration.CA_CERT_FILE.toString(), ""); + caCertFile = replaceProperties(caCertFile); + return caCertFile; + } + + /** + * get the ca cert password + * the password is encrypted + * @return the ca cert file password + */ + public String getCaCertPassword() { + String caCertPasswd = configuration.getProperty(Configuration.CA_CERT_PASSWORD.getValue()); + try { + return aesService.decrypt(caCertPasswd); + } catch (Exception ex) { + logger.error("Unable to get db password " + ex.getMessage()); + return null; + } + } + + /** + * Get the aes seed file for aes encryption + * @return seed file location + */ + public String getAesSeedFile() { + String aesSeed = configuration.getProperty(Configuration.AES_SEED_FILE.getValue(), ""); + aesSeed = replaceProperties(aesSeed); + return aesSeed; + } + + /** + * Get configuration value + * @param key key for the value to find + * @return the configuration value + */ + public ConfigurationRequest getConfiguration(String key) { + Configuration type = Configuration.findEnum(key); + return getConfiguration(type); + } + + /** + * Get configuration value + * @param typse the configuration types to get + * @return the configuration value + */ + public ConfigurationRequest getConfigurations(Configuration... types) { + ConfigurationRequest cr = new ConfigurationRequest(); + for (Configuration type : types) { + String key = type.getValue(); + String value = this.configuration.getProperty(key); + switch(type) { + case CA_CERT_FILE: + cr.setCaCertFile(value); + break; + case CA_CERT_PASSWORD: + cr.setCaCertPassword(value); + break; + case DB_HOST: + cr.setDbHost(value); + break; + case DB_PASSWORD: + cr.setDbPassword(value); + break; + case DB_PORT: + cr.setDbPort(Integer.parseInt(value)); + break; + case DB_ROOT_PASSWORD: + cr.setDbRootPassword(value); + break; + case DB_ROOT_USER: + cr.setDbRootUser(value); + break; + case DB_USER: + cr.setDbUsername(value); + break; + case USER_EXPIRATION_DAYS: + cr.setUserExpirationDays(Integer.parseInt(value)); + break; + case AES_SEED_FILE: + cr.setAesSeedFile(value); + break; + case LOG_LEVEL: + cr.setLogLevel(value); + break; + default: + break; + } + } + return cr; + } + + /** + * Get configuration value + * @param type the configuration type to get + * @return the configuration value + */ + public ConfigurationRequest getConfiguration(Configuration type) { + return getConfigurations(type); + } + + /** + * Get the current configuration + * @return configuration + */ + public Properties getConfiguration() { + return configuration; + } + + /** + * Get the last time the configuration file was modified + * @return last modified + */ + public long getLastModified() { + return lastModified; + } + + public void updateEncryption(String newSeed) throws Exception { + String newDBRootPassword = aesService.changeEncryptionValue(configuration.getProperty(Configuration.DB_ROOT_PASSWORD.getValue()), newSeed); + String newDBPassword = aesService.changeEncryptionValue(configuration.getProperty(Configuration.DB_PASSWORD.getValue()), newSeed); + String newCACertPassword = aesService.changeEncryptionValue(configuration.getProperty(Configuration.CA_CERT_PASSWORD.getValue()), newSeed); + + ConfigurationRequest cr = getConfigurations(Configuration.values()); + cr.setCaCertPassword(newCACertPassword); + cr.setDbRootPassword(newDBRootPassword); + cr.setDbPassword(newDBPassword); + + saveConfiguration(cr, false); + } + + /** + * Save the current configuration + * @param request Request with configuration values + * @throws PortalException actionNotPermitted + */ + public void saveConfiguration(ConfigurationRequest request) throws Exception { + saveConfiguration(request, true); + } + + /** + * Save the current configuration + * @param request request with the configuration values + * @param encryptCreds set to true to encrypt credentials false if the credentials are already encrypted + * @throws Exception PortalException actionNotPermitted + */ + public void saveConfiguration(ConfigurationRequest request, boolean encryptCreds) throws Exception { + try { + Properties props = new Properties(); + + //copy what is current in the configuration settings into the new properties file + Enumeration confEnumeration = configuration.propertyNames(); + while(confEnumeration.hasMoreElements()) { + String key = (String)confEnumeration.nextElement(); + String value = configuration.getProperty(key).toString(); + props.setProperty(key, value); + } + boolean changed = false; + for (Field f : request.getClass().getDeclaredFields()) { + f.setAccessible(true); + String fieldName = f.getName(); + + //Ensures we are only saving values that are already configured + Configuration conf = Configuration.findEnum(fieldName); + if (conf == null) continue; + + Object value = f.get(request); + if (value == null) continue; + + String fieldValue = String.valueOf(value); + //Check to see if the old value changed + String oldValue = props.getProperty(conf.getValue()); + if (StringUtils.isAllBlank(oldValue, fieldValue) || oldValue.equals(fieldValue)) { continue; } + + changed = true; + + fieldValue = encryptCreds && conf.is(Configuration.DB_ROOT_PASSWORD, Configuration.DB_PASSWORD, Configuration.CA_CERT_PASSWORD) ? aesService.encrypt(fieldValue) : fieldValue; + + props.setProperty(fieldName, fieldValue); + } + if (changed) { + saveToConf(props); + } + } catch (Exception ex) { + throw PortalException.actionNotPermitted(ex.getMessage()); + } + } + + /** + * Loop through the system properties and replace + * the property value with the system property + * @param value value to check + * @return fromatted string + */ + private String replaceProperties(String value) { + String tmp = new String(value); + + Enumeration sysPropEnum = System.getProperties().propertyNames(); + while (sysPropEnum.hasMoreElements()) { + String key = (String) sysPropEnum.nextElement(); + String variableKey = String.format("${%s}", key); + tmp = tmp.replace(variableKey, System.getProperty(key)); + } + return tmp; + } +} \ No newline at end of file diff --git a/portal_common/src/main/java/net/locusworks/portal/common/triggers/AccountTrigger.java b/portal_common/src/main/java/net/locusworks/portal/common/triggers/AccountTrigger.java new file mode 100644 index 0000000..dcdfeb9 --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/triggers/AccountTrigger.java @@ -0,0 +1,49 @@ +package net.locusworks.portal.common.triggers; + +import java.util.ArrayList; +import java.util.List; + +import net.locusworks.common.utils.Utils; +import net.locusworks.portal.common.enums.TriggerType; + +/** + * Account trigger class to send stomp triggers across the system and to the client + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + */ +public class AccountTrigger extends Trigger { + + private static final long serialVersionUID = 2139401946845273241L; + + private Integer userId; + + public AccountTrigger() { + super(); + } + + public AccountTrigger(TriggerType type, Object... resources) { + this(); + setType(type); + setResources(Utils.toList(resources)); + } + + private List resources = new ArrayList<>(); + + public Integer getUserId() { + return userId; + } + + public void setUserId(Integer userId) { + this.userId = userId; + } + + public List getResources() { + return resources; + } + + public void setResources(List resources) { + this.resources = resources; + } + +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/triggers/SeedTrigger.java b/portal_common/src/main/java/net/locusworks/portal/common/triggers/SeedTrigger.java new file mode 100644 index 0000000..7939ec8 --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/triggers/SeedTrigger.java @@ -0,0 +1,34 @@ +package net.locusworks.portal.common.triggers; + +import org.springframework.context.ApplicationEvent; + +public class SeedTrigger extends ApplicationEvent { + + private static final long serialVersionUID = 8676727530092345425L; + + private String seed; + + public SeedTrigger() { + super(new Object()); + } + + public SeedTrigger(String seed) { + this(); + this.seed = seed; + } + + /** + * @return the seed + */ + public String getSeed() { + return seed; + } + + /** + * @param seed the seed to set + */ + public void setSeed(String seed) { + this.seed = seed; + } + +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/triggers/Trigger.java b/portal_common/src/main/java/net/locusworks/portal/common/triggers/Trigger.java new file mode 100644 index 0000000..d4f051a --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/triggers/Trigger.java @@ -0,0 +1,63 @@ +package net.locusworks.portal.common.triggers; + +import org.springframework.context.ApplicationEvent; + +import net.locusworks.portal.common.enums.TriggerType; + +/** + * Abstract class to send stomp events across the system + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + */ +public abstract class Trigger extends ApplicationEvent { + + private static final long serialVersionUID = 3123109911284540118L; + + private long timestampCreation; + private TriggerType type; + + public Trigger() { + this(new Object()); + } + + /** + * Default constructor + * @param source trigger payload + */ + public Trigger(Object source) { + super(source); + } + + /** + * get the timestamp at which the trigger was created + * @return timestampCreation + */ + public long getTimestampCreation() { + return timestampCreation; + } + + /** + * Set the timestamp the trigger was created + * @param timestampCreation timestamp + */ + public void setTimestampCreation(long timestampCreation) { + this.timestampCreation = timestampCreation; + } + + /** + * Get the trigger type + * @return triggerType + */ + public TriggerType getType() { + return type; + } + + /** + * Set the trigger type + * @param type Trigger type to set + */ + public void setType(TriggerType type) { + this.type = type; + } +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/utils/Constants.java b/portal_common/src/main/java/net/locusworks/portal/common/utils/Constants.java new file mode 100644 index 0000000..c498a94 --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/utils/Constants.java @@ -0,0 +1,27 @@ +package net.locusworks.portal.common.utils; + +import java.io.File; + +import org.apache.commons.io.FilenameUtils; + +/** + * Class to hold final static constant values used across the system + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + */ +public class Constants { + + public static final String SEPARATOR = FilenameUtils.separatorsToUnix(File.separator); + + public static final String SCHEDULER_GROUP_NAME = "scriptScheduler"; + + + public static final String KEY_STORE_PATH = "javax.net.ssl.keyStore"; + public static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; + + public static final String TRUST_STORE_PATH = "javax.net.ssl.trustStore"; + public static final String TRUST_STORE_PASSWORD = "javax.net.ssl.trustStorePassword"; + + public static final String PROPERTIES_FILE = "portal.properties"; + +} diff --git a/portal_common/src/main/java/net/locusworks/portal/common/utils/DateService.java b/portal_common/src/main/java/net/locusworks/portal/common/utils/DateService.java new file mode 100644 index 0000000..b117c5b --- /dev/null +++ b/portal_common/src/main/java/net/locusworks/portal/common/utils/DateService.java @@ -0,0 +1,199 @@ +package net.locusworks.portal.common.utils; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import net.locusworks.portal.common.services.ConfigurationService; + +/** + * Component class to help convert dates to human readable format + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + */ +@Component +public class DateService { + private static final String DEFAULT = "MM/dd/yyyy"; + private static final String EXPANDED = "MM/dd/yyyy HH:mm:ss z"; + private static final String PRETTY_EXPANDED = "MMM d, yyyy HH:mm:ss z"; + + @Autowired + private ConfigurationService confService; + + /** + * Get the date in which a new user would expire + * @return Returns the user expiration date + */ + public Date getUserExpirationDate() { + return createDate(Calendar.DATE, confService.getUserExpirationDays()); + } + + /** + * Create a new date + * @return new Date with todays date + */ + public Date createDate() { + return createDate(Calendar.DATE, 0); + } + + /*** + * Create a date + * @param calField Field in the calendar class to add time to + * @param numToAdd Number to add corresponding to the field can be negative + * @return new date with the offset applied + */ + public Date createDate(Integer calField, Integer numToAdd) { + Calendar cal = Calendar.getInstance(); + + Date date = new Date(); + cal.setTime(date); + cal.add(calField, numToAdd); + + return cal.getTime(); + } + + /** + * Format a date to the default format of "MM/dd/yyyy" + * @param date Date to format + * @return formatted String + */ + public static String defaultFormat(Date date) { + return format(date, DEFAULT); + } + + public static Date defaultFormatToDate(String date) { + return formatToDate(DEFAULT, date); + } + + /** + * Format a date to the expanded format of "MM/dd/yyyy HH:mm:ss z" + * @param date Date to format + * @return formatted date string + */ + public static String expandedFormat(Date date) { + return format(date, EXPANDED); + } + + /** + * Format a string from a give date with the format of "MMM d, yyyy HH:mm:ss z" + * @param date The date to format to a string + * @return formatted string + */ + public static String prettyFormat(Date date) { + return format(date, PRETTY_EXPANDED); + } + + /** + * Format a date + * @param date Date to format + * @param format Format in which to convert the date to + * @return formatted date + */ + public static String format(Date date, String format) { + return new SimpleDateFormat(format).format(date); + } + + /** + * Convert a string value to a date object + * @param format The format to use in reference to the source + * @param source the source to convert + * @return Date object if the conversion was success; null otherwise + */ + public static Date formatToDate(String format, String source) { + try { + return new SimpleDateFormat(format).parse(source); + } catch (Exception ex) { + return null; + } + } + + /** + * Checks to see if a date is within a range with date being now + * Will return false if the provided date or start is null + * end date can be null. Date will only compare against start if this is the case + * @param start starting date + * @param end ending date + * @return true if its between the dates false otherwise + */ + public static boolean inRange(Date start, Date end) { + return inRange(new Date(), start, end); + } + + /** + * Checks to see if a date is within a specified date range + * Will return false if the provided date or start is null + * end date can be null. Date will only compare against start if this is the case + * @param date Date to check + * @param start starting date + * @param end ending date + * @return true if its between the dates false otherwise + */ + public static boolean inRange(Date date, Date start, Date end) { + if (date == null) { + return false; + } + + if (start == null) { + return false; + } + + if (end == null && start.getTime() <= date.getTime()) { + return true; + } + return start.getTime() <= date.getTime() && date.getTime() <= end.getTime(); + } + + /** + * Sets the date to midnight + * @param date date to set + * @return Date that is set to midnight + */ + public static Date toMidnight(Date date) { + return toMidnight(date, 0); + } + + /** + * Sets the date to midnight + * @param date Date to set + * @param addDays Number of days to add (can be negative) to the date when setting it to midnight + * @return date set to midnight for a given date + */ + public static Date toMidnight(Date date, Integer addDays) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.DATE, addDays); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTime(); + } + + /** + * Get a date with specified minutes ahead + * @param minutesToAdd number of minutes to add to the date (can be negative) + * @return date + */ + public static Date getDate(Integer minutesToAdd) { + return getDate(Calendar.MINUTE, minutesToAdd); + } + + /** + * Get a date with specified value added + * @param field Calendar field to add value too + * @param valueToAdd the amount to increase field by (can be negative) + * @return date + */ + public static Date getDate(Integer field, Integer valueToAdd) { + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + if (valueToAdd != null) { + cal.add(field, valueToAdd); + } + return cal.getTime(); + } +} diff --git a/portal_database/README.md b/portal_database/README.md new file mode 100644 index 0000000..dbc6172 --- /dev/null +++ b/portal_database/README.md @@ -0,0 +1,5 @@ +Portal Database +== + +The portal_database module contains classes to support database queries. It uses Hibernate and Spring to construct the queries to deliver JPA classes that can be used +to create, read, update and delete record. Spring Repository is used to craft the queries. diff --git a/portal_database/findbugs-exclude.xml b/portal_database/findbugs-exclude.xml new file mode 100644 index 0000000..b54af6f --- /dev/null +++ b/portal_database/findbugs-exclude.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/portal_database/pom.xml b/portal_database/pom.xml new file mode 100644 index 0000000..0c110ef --- /dev/null +++ b/portal_database/pom.xml @@ -0,0 +1,121 @@ + + 4.0.0 + + + net.locusworks + portal + 1.0.0-RELEASE + + + + ${project.parent.basedir} + 8.0.11 + + + portal_database + Portal Database + Data warehouse that holds the JPO Entities and their corresponding Repository implementations. Spring JPA is used in conjunction with Hibernate to facilitate retrieving and saving records. All CRUD (Create, Read, Update, Delete) operations are done through Spring and Hibernate. + + + + + org.codehaus.mojo + findbugs-maven-plugin + ${findbugs.version} + + true + target/site/${project.artifactId} + + findbugs-exclude.xml + + + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + + + Set author to NGAS + + + + + + + + Set LF line endings + + + + generate-sources + + run + + + + + + + + + + + javax.transaction + jta + 1.1 + + + + mysql + mysql-connector-java + ${mysql.version} + + + com.google.protobuf + protobuf-java + + + + + + + com.google.protobuf + protobuf-java + 3.5.1 + + + + org.hibernate + hibernate-core + ${hibernate.version} + + + org.hibernate + hibernate-entitymanager + ${hibernate.version} + + + + + org.springframework + spring-orm + ${spring.version} + + + + + \ No newline at end of file diff --git a/portal_database/src/main/java/net/locusworks/portal/database/config/PortalBeanConfiguration.java b/portal_database/src/main/java/net/locusworks/portal/database/config/PortalBeanConfiguration.java new file mode 100644 index 0000000..fbc24f0 --- /dev/null +++ b/portal_database/src/main/java/net/locusworks/portal/database/config/PortalBeanConfiguration.java @@ -0,0 +1,117 @@ +package net.locusworks.portal.database.config; + +import java.util.HashSet; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Primary; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.orm.hibernate5.LocalSessionFactoryBean; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.JpaVendorAdapter; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.Database; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/*** + * Spring bean configuration for the database. + * Shows which package is where the spring repositories are and the entities to scan + * @author Isaac Parenteau + * @since 1.0.0-RELEASE + * @version 1.0 + * + */ +@Configuration(value="net.locusworks.portal.database.config.PortalBeanConfiguration") +@EnableTransactionManagement +@EnableJpaRepositories( + basePackages = {"net.locusworks.portal.database.repos"}, + entityManagerFactoryRef = "portalEntityManagerFactory", + transactionManagerRef = "portalTransactionManager" +) +public class PortalBeanConfiguration { + + public static final String PERSISTENCE_UNIT = "portal_pu"; + + public static final HashSet cacheNames = new HashSet<>(); + + private static final String[] PACKAGES_TO_SCAN = {"net.locusworks.portal.database.entities"}; + + private static final Properties hibernateProperties; + static { + hibernateProperties = new Properties(); + hibernateProperties.setProperty("hibernate.connection.zeroDateTimeBehavior", "convertToNull"); + hibernateProperties.setProperty("hibernate.dbcp.maxActive", "50"); + hibernateProperties.setProperty("hibernate.dbcp.maxIdle", "10"); + hibernateProperties.setProperty("hibernate.dbcp.maxWait", "5000"); + hibernateProperties.setProperty("hibernate.jdbc.batch_size property", "50"); + hibernateProperties.setProperty("hibernate.connection.charSet", "UTF-8"); + hibernateProperties.setProperty("hibernate.connection.characterEncoding", "UTF-8"); + hibernateProperties.setProperty("hibernate.connection.useUnicode", "true"); + } + + @Autowired + private DataSource dataSource; + + /** + * Create the entity manager factory bean used in the database connection + * @return entityManagerFactoryBean + */ + @Primary + @Bean + @DependsOn("migration") + public LocalContainerEntityManagerFactoryBean portalEntityManagerFactory() { + LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean(); + lef.setDataSource(dataSource); + lef.setJpaVendorAdapter(portalJpaVendorAdapter()); + lef.setPackagesToScan(PACKAGES_TO_SCAN); + lef.setJpaProperties(hibernateProperties); + lef.setPersistenceUnitName(PERSISTENCE_UNIT); + return lef; + } + + /** + * Create the session factory bean + * @return sessionFactoryBean + */ + @Bean + public LocalSessionFactoryBean portalSessionFactory() { + LocalSessionFactoryBean sessionBean = new LocalSessionFactoryBean(); + sessionBean.setDataSource(dataSource); + sessionBean.setPackagesToScan(PACKAGES_TO_SCAN); + sessionBean.setHibernateProperties(hibernateProperties); + return sessionBean; + } + + /** + * Create the JPA Vendor adaptor bean that is used in the entity manager factory + * @return jpaVendorAdaptor + */ + @Primary + @Bean + public JpaVendorAdapter portalJpaVendorAdapter() { + HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter(); + hibernateJpaVendorAdapter.setShowSql(false); + hibernateJpaVendorAdapter.setGenerateDdl(false); + hibernateJpaVendorAdapter.setDatabase(Database.MYSQL); + return hibernateJpaVendorAdapter; + } + + /** + * Create the transaction manager bean + * @return transactionManager + */ + @Primary + @Bean + public PlatformTransactionManager portalTransactionManager() { + JpaTransactionManager manager = new JpaTransactionManager(); + manager.setEntityManagerFactory(portalEntityManagerFactory().getObject()); + return new JpaTransactionManager(); + } +} diff --git a/portal_database/src/main/java/net/locusworks/portal/database/entities/ApplicationLog.java b/portal_database/src/main/java/net/locusworks/portal/database/entities/ApplicationLog.java new file mode 100644 index 0000000..599a970 --- /dev/null +++ b/portal_database/src/main/java/net/locusworks/portal/database/entities/ApplicationLog.java @@ -0,0 +1,156 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package net.locusworks.portal.database.entities; + +import java.io.Serializable; +import java.util.Date; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * + * @author NGAS + */ +@Entity +@Table(name = "application_log", catalog = "portal", schema = "") +@NamedQueries({ + @NamedQuery(name = "ApplicationLog.findAll", query = "SELECT a FROM ApplicationLog a")}) +public class ApplicationLog implements Serializable { + + private static final long serialVersionUID = 1L; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Basic(optional = false) + @Column(name = "id") + private Integer id; + @Basic(optional = false) + @Column(name = "logged_by") + private String loggedBy; + @Basic(optional = false) + @Column(name = "event_type") + private String eventType; + @Basic(optional = false) + @Column(name = "event") + private String event; + @Basic(optional = false) + @Column(name = "severity") + private String severity; + @Basic(optional = false) + @Column(name = "ip_address") + private String ipAddress; + @Basic(optional = false) + @Column(name = "timestamp") + @Temporal(TemporalType.TIMESTAMP) + private Date timestamp; + + public ApplicationLog() { + } + + public ApplicationLog(Integer id) { + this.id = id; + } + + public ApplicationLog(Integer id, String loggedBy, String eventType, String event, String severity, String ipAddress, Date timestamp) { + this.id = id; + this.loggedBy = loggedBy; + this.eventType = eventType; + this.event = event; + this.severity = severity; + this.ipAddress = ipAddress; + this.timestamp = timestamp; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getLoggedBy() { + return loggedBy; + } + + public void setLoggedBy(String loggedBy) { + this.loggedBy = loggedBy; + } + + public String getEventType() { + return eventType; + } + + public void setEventType(String eventType) { + this.eventType = eventType; + } + + public String getEvent() { + return event; + } + + public void setEvent(String event) { + this.event = event; + } + + public String getSeverity() { + return severity; + } + + public void setSeverity(String severity) { + this.severity = severity; + } + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + @Override + public int hashCode() { + int hash = 0; + hash += (id != null ? id.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object object) { + // TODO: Warning - this method won't work in the case the id fields are not set + if (!(object instanceof ApplicationLog)) { + return false; + } + ApplicationLog other = (ApplicationLog) object; + if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) { + return false; + } + return true; + } + + @Override + public String toString() { + return "net.locusworks.portal.database.entities.ApplicationLog[ id=" + id + " ]"; + } + +} diff --git a/portal_database/src/main/java/net/locusworks/portal/database/entities/ApplicationUser.java b/portal_database/src/main/java/net/locusworks/portal/database/entities/ApplicationUser.java new file mode 100644 index 0000000..6434d09 --- /dev/null +++ b/portal_database/src/main/java/net/locusworks/portal/database/entities/ApplicationUser.java @@ -0,0 +1,245 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package net.locusworks.portal.database.entities; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * + * @author NGAS + */ +@Entity +@Table(name = "application_user", catalog = "portal", schema = "") +@NamedQueries({ + @NamedQuery(name = "ApplicationUser.findAll", query = "SELECT a FROM ApplicationUser a")}) +public class ApplicationUser implements Serializable { + + private static final long serialVersionUID = 1L; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Basic(optional = false) + @Column(name = "id") + private Integer id; + @Basic(optional = false) + @Column(name = "email_address_txt") + private String emailAddressTxt; + @Basic(optional = false) + @Column(name = "effective_date") + @Temporal(TemporalType.DATE) + private Date effectiveDate; + @Basic(optional = false) + @Column(name = "expiration_date") + @Temporal(TemporalType.DATE) + private Date expirationDate; + @OneToMany(cascade = CascadeType.ALL, mappedBy = "createdBy") + private List scriptEnvironmentVariableList; + @OneToOne(cascade = CascadeType.ALL, mappedBy = "applicationUser") + private UserAttribute userAttribute; + @OneToMany(cascade = CascadeType.ALL, mappedBy = "createdBy") + private List passwordList; + @OneToMany(cascade = CascadeType.ALL, mappedBy = "createdBy") + private List gitRepoList; + @OneToMany(cascade = CascadeType.ALL, mappedBy = "createdBy") + private List environmentVariableList; + @OneToMany(cascade = CascadeType.ALL, mappedBy = "createdBy") + private List keywordList; + @OneToMany(cascade = CascadeType.ALL, mappedBy = "createdBy") + private List keyValueList; + @OneToMany(cascade = CascadeType.ALL, mappedBy = "applicationUser") + private List userPasswordTrackingList; + @OneToMany(cascade = CascadeType.ALL, mappedBy = "createdBy") + private List \ No newline at end of file diff --git a/scripts/build-app.sh b/scripts/build-app.sh new file mode 100644 index 0000000..ed9218e --- /dev/null +++ b/scripts/build-app.sh @@ -0,0 +1,159 @@ +#! /bin/bash + +#Variables +clean=false +flywayClean=false +flywayMigrate=false +flywayRepair=false +skip=false +deploy=false +cleanTomcat=false +docs=false +warDeploy=false +currentDir=`pwd` +branchName="" +PROG="`basename $0`" + +#Functions +function cleanup(){ + echo "exiting" + exit +} + +function errorexit(){ + true + echo "errorexit" +} + +function clean_tomcat_dir(){ + echo "Cleaning tomcat directory" + rm -rf $CATALINA_HOME/logs/* + rm -rf $CATALINA_HOME/temp/* + rm -rf $CATALINA_HOME/work/* +} + +function usage(){ + local error="$*" + if [[ $error ]]; then + echo "" + echo "Error: ${error}" + echo "" + fi + echo "Usage: $PROG [OPTION...]" + echo "" + echo "Options" + echo " -h, --help show this usage text" + echo " -j, --docs build the javadocs" + echo " -b, --branch branch name to checkout and build" + echo " -c, --clean performs mvn clean and cleans tomcat directory" + echo " -f, --flyway-clean performs mvn flyway:clean (clears out database)" + echo " -m, --flyway-migrate performs mvn flyway:migrate (runs one or more servers to update" + echo " -r, --flyway-repair performs mvn flyway:repair (fixes checksums)" + echo " -s, --skip skips jslint test on javascript" + echo " -d, --deploy deploys app to tomcat directory" + echo " -w, --war performs tomcat7:redeploy to deploy the war without having to " + echo " clean/remove existing " + echo " -t, --clean-tomcat cleans tomcats work, temp, and log directories" + + if [ "`uname`" == "Darwin" ]; then + echo "Mac users: use short options only!" + fi + + if [[ $error ]]; then + exit 1 + else + exit 0 + fi +} + +#main +short_opts='cb:fmrsdhtjw' +long_opts='help,clean,flyway-clean,flyway-migrate,flyway-repair,noskip,nodeploy,clean-tomcat,branch:,docs,war' + +# note: for mac use short opts without the -o option +if [ "`uname`" == "Darwin" ]; then + #TEMP=`getopt ${short_opts} ${PROG} -- "$@"` + TEMP=`getopt ${short_opts} $*` +else + TEMP=`getopt -o ${short_opts} --long ${long_opts} -n ${PROG} -- "$@"` +fi + +if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi + +# Note the quotes around `$TEMP': they are essential! +eval set -- "$TEMP" +while true ; do + case "$1" in + -c|--clean) clean=true ; shift ;; + -j|--docs) docs=true ; shift ;; + -b|--branch) branchName=$2 ; shift 2 ;; + -f|--flyway-clean) flywayClean=true ; shift ;; + -m|--flyway-migrate) flywayMigrate=true ; shift ;; + -r|--flyway-repair) flywayRepair ; shift ;; + -s|--skip) skip=true ; shift ;; + -d|--deploy) deploy=true ; shift ;; + -t|--clean-tomcat) cleanTomcat=true ; shift ;; + -w|--war) warDeploy=true ; shift ;; + -h|--help) usage; shift;; + --) shift ; break ;; + *) echo "Internal error!" ; exit 1 ;; + esac +done + +cd $PORTAL_HOME + +if [ -n "$branchName" ]; then + git checkout -f $branchName + git pull origin $branchName +fi + +if [ $clean = true ]; then + echo "Performing mvn clean..." + mvn clean +fi + +if [ $flywayClean = true ]; then + echo "Performing flyway clean" + mvn flyway:clean +fi + +if [ $flywayMigrate = true ]; then + echo "Performing flyway migrate" + mvn flyway:migrate +fi + +if [ $flywayRepair = true ]; then + echo "Performing flyway repair" + mvn flyway:repair +fi + +if [ $docs = true ]; then + echo "Building documents" + cd $PORTAL_HOME/scripts + ./build-docs.sh +fi + +if [ $deploy = true ]; then + echo "Undeploying server" + cd $PORTAL_HOME/scripts + ./undeploy-server.sh + if [ $cleanTomcat = true ]; then + clean_tomcat_dir + fi +fi + +cd $PORTAL_HOME + +mvn -DskipTests=$skip install + +if [ $warDeploy = true ]; then + mvn tomcat7:redeploy +fi + +if [ $deploy = true ]; then + echo "Deploying webapp to server" + cd $PORTAL_HOME/scripts + ./deploy-server.sh +fi + +cd $currentDir \ No newline at end of file diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh new file mode 100644 index 0000000..b3236a1 --- /dev/null +++ b/scripts/build-docs.sh @@ -0,0 +1,45 @@ +# !/bin/bash + +. portal-env.sh + +cd $PORTAL_HOME + +declare -a arr=("portal_client" "portal_common" "portal_database" "portal_migration" "portal_webapp") + +echo Deleting old content +echo Deleting old web content +rm -rf web + +echo Deleting old site content +rm -rf target/site + +for i in "${arr[@]}" +do + echo Deleting $i old site content + rm -rf $i/target/site +done + +if [ ! -d "web" ]; then + mkdir web +fi + +for i in "${arr[@]}" +do + if [ ! -d web/$i ]; then + echo "Creating directory web/$i" + mkdir web/$i + fi +done + +echo Creating javadocs +mvn site + +echo Moving documents to web +mv target/site/* web/. + +for i in "${arr[@]}" +do + echo Moving $i to web/$i + mv $i/target/site/* web/$i/. +done +echo Done \ No newline at end of file diff --git a/scripts/deploy-server.sh b/scripts/deploy-server.sh new file mode 100644 index 0000000..e9466ae --- /dev/null +++ b/scripts/deploy-server.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +. portal-env.sh +. portal-catalina.sh env +cd ${PORTAL_HOME} +set -x +cp "portal_webapp/target/patchrepo.war" "${CATALINA_HOME}/webapps/" \ No newline at end of file diff --git a/scripts/portal-catalina.sh b/scripts/portal-catalina.sh new file mode 100644 index 0000000..4caff3f --- /dev/null +++ b/scripts/portal-catalina.sh @@ -0,0 +1,53 @@ +#! /bin/bash + + +. portal-env.sh + + +#define tomcat variables +export CATALINA_BASE="${CATALINA_BASE:-$CATALINA_HOME}" +export JPDA_ADDRESS="${JPDA_ADDRESS:-8001}" +export JPDA_TRANSPORT="${JPDA_TRANSPORT:-dt_socket}" +#export CATALINA_OPTS='-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n' + + +get_most_recent() { + + most_recent_file="`perl -le '@sorted = map {$_->[0]} + sort {$a->[1] <=> $b->[1]} + map {[$_, -M $_]} + @ARGV; + print $sorted[0]' $1`" + +} + +cd "${CATALINA_HOME}" + +# start tail of catalina.out in new window +# shift and $* allows any additional options to be applied to the tail command +# such as -l 1000 to open with the previous 1000 lines +if [ "$1" == "tail" ]; then + shift 1 + cd logs + get_most_recent 'localhost*.log' + localhost_log="${most_recent_file}" + get_most_recent 'localhost_access_log*.txt' + localhost_access_log="${most_recent_file}" + catalina_out="catalina.out" + $terminal tail -f "${catalina_out}" "${localhost_log}" "${localhost_access_log}" $* & + exit +fi + + +#use terminal for the run command +case "$*" in + env) + ;; + *run) + $terminal "${CATALINA_HOME}/bin/catalina.sh" $* & + ;; + + *) + "${CATALINA_HOME}/bin/catalina.sh" $* + ;; +esac \ No newline at end of file diff --git a/scripts/portal-conn-status.sh b/scripts/portal-conn-status.sh new file mode 100644 index 0000000..02662ec --- /dev/null +++ b/scripts/portal-conn-status.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +wait_count=0; +est_count=0; + +while [ 1 ] +do + new=`netstat -an |grep 3306 |grep TIME_WAIT |wc -l` + new2=`netstat -an |grep 3306 |grep ESTABLISHED |wc -l` + if [ $wait_count != $new ]; then + echo Number of connections waiting = $new + wait_count=$new + fi + if [ $est_count != $new2 ]; then + echo Number of connections established = $new2 + est_count=$new2 + fi + sleep 1 + +done \ No newline at end of file diff --git a/scripts/portal-env.sh b/scripts/portal-env.sh new file mode 100644 index 0000000..0a29d74 --- /dev/null +++ b/scripts/portal-env.sh @@ -0,0 +1,21 @@ +#! /bin/bash + +if [ ! -f "envar.sh" ]; then + echo "envar.sh file not found" + echo "Create envar.sh and export PORTAL_HOME, CATALINA_HOME" + echo "PORTAL_HOME points to the patch repository source location" + echo "CATALINA_HOME points to the local tomcat location" + exit +fi + +. envar.sh env + +# choose os-dependent terminal +case "`uname`" in + CYGWIN*) + terminal="mintty --window normal --size 200,60 --hold error --exec" + ;; + *) + terminal="xterm -geometry 200x30 -e " + ;; +esac \ No newline at end of file diff --git a/scripts/portal-grunt.sh b/scripts/portal-grunt.sh new file mode 100644 index 0000000..c1d89a8 --- /dev/null +++ b/scripts/portal-grunt.sh @@ -0,0 +1,15 @@ +#! /bin/bash + +. portal-env.sh +cd "${PORTAL_HOME}/portal_client" + +#use terminal for the serve command +case $1 in + serve|onlyServe) + $terminal grunt $* & + ;; + + *) + grunt $* & + ;; +esac \ No newline at end of file diff --git a/scripts/undeploy-server.sh b/scripts/undeploy-server.sh new file mode 100644 index 0000000..9cd4498 --- /dev/null +++ b/scripts/undeploy-server.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +. portal-catalina.sh env + +set -x +rm "${CATALINA_HOME}/webapps/portal.war" +rm -rf "${CATALINA_HOME}/webapps/portal" \ No newline at end of file diff --git a/test.properties b/test.properties new file mode 100644 index 0000000..c8f7462 --- /dev/null +++ b/test.properties @@ -0,0 +1,3 @@ +ssh.private.key=C:\\Users\\J65594\\.ssh\\id_rsa +ssh.public.key=C:\\Users\\J65594\\.ssh\\id_rsa.pub +git.scm=ssh://git@bigmac.northgrum.com:8010/saipt/stig-apply.git \ No newline at end of file diff --git a/test.properties.init b/test.properties.init new file mode 100644 index 0000000..517ac62 --- /dev/null +++ b/test.properties.init @@ -0,0 +1,3 @@ +ssh.private.key= +ssh.public.key= +git.scm= \ No newline at end of file