mirror of
				https://github.com/mmumshad/ansible-playable.git
				synced 2025-03-09 23:38:54 +00:00 
			
		
		
		
	Initial Commit
This commit is contained in:
		
						commit
						c92f737237
					
				
					 273 changed files with 16964 additions and 0 deletions
				
			
		
							
								
								
									
										7
									
								
								client/.eslintrc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								client/.eslintrc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| { | ||||
|     "extends": "../.eslintrc", | ||||
|     "env": { | ||||
|         "browser": true, | ||||
|         "commonjs": true | ||||
|     } | ||||
| } | ||||
							
								
								
									
										543
									
								
								client/.htaccess
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										543
									
								
								client/.htaccess
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,543 @@ | |||
| # Apache Configuration File | ||||
| 
 | ||||
| # (!) Using `.htaccess` files slows down Apache, therefore, if you have access | ||||
| # to the main server config file (usually called `httpd.conf`), you should add | ||||
| # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. | ||||
| 
 | ||||
| # ############################################################################## | ||||
| # # CROSS-ORIGIN RESOURCE SHARING (CORS)                                       # | ||||
| # ############################################################################## | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Cross-domain AJAX requests                                                 | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Enable cross-origin AJAX requests. | ||||
| # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity | ||||
| # http://enable-cors.org/ | ||||
| 
 | ||||
| # <IfModule mod_headers.c> | ||||
| #    Header set Access-Control-Allow-Origin "*" | ||||
| # </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | CORS-enabled images                                                        | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Send the CORS header for images when browsers request it. | ||||
| # https://developer.mozilla.org/en/CORS_Enabled_Image | ||||
| # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html | ||||
| # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ | ||||
| 
 | ||||
| <IfModule mod_setenvif.c> | ||||
|     <IfModule mod_headers.c> | ||||
|         <FilesMatch "\.(gif|ico|jpe?g|png|svg|svgz|webp)$"> | ||||
|             SetEnvIf Origin ":" IS_CORS | ||||
|             Header set Access-Control-Allow-Origin "*" env=IS_CORS | ||||
|         </FilesMatch> | ||||
|     </IfModule> | ||||
| </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Web fonts access                                                           | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Allow access from all domains for web fonts | ||||
| 
 | ||||
| <IfModule mod_headers.c> | ||||
|     <FilesMatch "\.(eot|font.css|otf|ttc|ttf|woff)$"> | ||||
|         Header set Access-Control-Allow-Origin "*" | ||||
|     </FilesMatch> | ||||
| </IfModule> | ||||
| 
 | ||||
| 
 | ||||
| # ############################################################################## | ||||
| # # ERRORS                                                                     # | ||||
| # ############################################################################## | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | 404 error prevention for non-existing redirected folders                   | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Prevent Apache from returning a 404 error for a rewrite if a directory | ||||
| # with the same name does not exist. | ||||
| # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews | ||||
| # http://www.webmasterworld.com/apache/3808792.htm | ||||
| 
 | ||||
| Options -MultiViews | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Custom error messages / pages                                              | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # You can customize what Apache returns to the client in case of an error (see | ||||
| # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: | ||||
| 
 | ||||
| ErrorDocument 404 /404.html | ||||
| 
 | ||||
| 
 | ||||
| # ############################################################################## | ||||
| # # INTERNET EXPLORER                                                          # | ||||
| # ############################################################################## | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Better website experience                                                  | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Force IE to render pages in the highest available mode in the various | ||||
| # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. | ||||
| 
 | ||||
| <IfModule mod_headers.c> | ||||
|     Header set X-UA-Compatible "IE=edge" | ||||
|     # `mod_headers` can't match based on the content-type, however, we only | ||||
|     # want to send this header for HTML pages and not for the other resources | ||||
|     <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$"> | ||||
|         Header unset X-UA-Compatible | ||||
|     </FilesMatch> | ||||
| </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Cookie setting from iframes                                                | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Allow cookies to be set from iframes in IE. | ||||
| 
 | ||||
| # <IfModule mod_headers.c> | ||||
| #   Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" | ||||
| # </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Screen flicker                                                             | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Stop screen flicker in IE on CSS rollovers (this only works in | ||||
| # combination with the `ExpiresByType` directives for images from below). | ||||
| 
 | ||||
| # BrowserMatch "MSIE" brokenvary=1 | ||||
| # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 | ||||
| # BrowserMatch "Opera" !brokenvary | ||||
| # SetEnvIf brokenvary 1 force-no-vary | ||||
| 
 | ||||
| 
 | ||||
| # ############################################################################## | ||||
| # # MIME TYPES AND ENCODING                                                    # | ||||
| # ############################################################################## | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Proper MIME types for all files                                            | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| <IfModule mod_mime.c> | ||||
| 
 | ||||
|   # Audio | ||||
|     AddType audio/mp4                                   m4a f4a f4b | ||||
|     AddType audio/ogg                                   oga ogg | ||||
| 
 | ||||
|   # JavaScript | ||||
|     # Normalize to standard type (it's sniffed in IE anyways): | ||||
|     # http://tools.ietf.org/html/rfc4329#section-7.2 | ||||
|     AddType application/javascript                      js jsonp | ||||
|     AddType application/json                            json | ||||
| 
 | ||||
|   # Video | ||||
|     AddType video/mp4                                   mp4 m4v f4v f4p | ||||
|     AddType video/ogg                                   ogv | ||||
|     AddType video/webm                                  webm | ||||
|     AddType video/x-flv                                 flv | ||||
| 
 | ||||
|   # Web fonts | ||||
|     AddType application/font-woff                       woff | ||||
|     AddType application/vnd.ms-fontobject               eot | ||||
| 
 | ||||
|     # Browsers usually ignore the font MIME types and sniff the content, | ||||
|     # however, Chrome shows a warning if other MIME types are used for the | ||||
|     # following fonts. | ||||
|     AddType application/x-font-ttf                      ttc ttf | ||||
|     AddType font/opentype                               otf | ||||
| 
 | ||||
|     # Make SVGZ fonts work on iPad: | ||||
|     # https://twitter.com/FontSquirrel/status/14855840545 | ||||
|     AddType     image/svg+xml                           svg svgz | ||||
|     AddEncoding gzip                                    svgz | ||||
| 
 | ||||
|   # Other | ||||
|     AddType application/octet-stream                    safariextz | ||||
|     AddType application/x-chrome-extension              crx | ||||
|     AddType application/x-opera-extension               oex | ||||
|     AddType application/x-shockwave-flash               swf | ||||
|     AddType application/x-web-app-manifest+json         webapp | ||||
|     AddType application/x-xpinstall                     xpi | ||||
|     AddType application/xml                             atom rdf rss xml | ||||
|     AddType image/webp                                  webp | ||||
|     AddType image/x-icon                                ico | ||||
|     AddType text/cache-manifest                         appcache manifest | ||||
|     AddType text/vtt                                    vtt | ||||
|     AddType text/x-component                            htc | ||||
|     AddType text/x-vcard                                vcf | ||||
| 
 | ||||
| </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | UTF-8 encoding                                                             | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Use UTF-8 encoding for anything served as `text/html` or `text/plain`. | ||||
| AddDefaultCharset utf-8 | ||||
| 
 | ||||
| # Force UTF-8 for certain file formats. | ||||
| <IfModule mod_mime.c> | ||||
|     AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml | ||||
| </IfModule> | ||||
| 
 | ||||
| 
 | ||||
| # ############################################################################## | ||||
| # # URL REWRITES                                                               # | ||||
| # ############################################################################## | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Rewrite engine                                                             | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Turning on the rewrite engine and enabling the `FollowSymLinks` option is | ||||
| # necessary for the following directives to work. | ||||
| 
 | ||||
| # If your web host doesn't allow the `FollowSymlinks` option, you may need to | ||||
| # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the | ||||
| # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks | ||||
| 
 | ||||
| # Also, some cloud hosting services require `RewriteBase` to be set: | ||||
| # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site | ||||
| 
 | ||||
| <IfModule mod_rewrite.c> | ||||
|     Options +FollowSymlinks | ||||
|   # Options +SymLinksIfOwnerMatch | ||||
|     RewriteEngine On | ||||
|   # RewriteBase / | ||||
| </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Suppressing / Forcing the "www." at the beginning of URLs                  | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # The same content should never be available under two different URLs especially | ||||
| # not with and without "www." at the beginning. This can cause SEO problems | ||||
| # (duplicate content), therefore, you should choose one of the alternatives and | ||||
| # redirect the other one. | ||||
| 
 | ||||
| # By default option 1 (no "www.") is activated: | ||||
| # http://no-www.org/faq.php?q=class_b | ||||
| 
 | ||||
| # If you'd prefer to use option 2, just comment out all the lines from option 1 | ||||
| # and uncomment the ones from option 2. | ||||
| 
 | ||||
| # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! | ||||
| 
 | ||||
| # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||||
| 
 | ||||
| # Option 1: rewrite www.example.com → example.com | ||||
| 
 | ||||
| <IfModule mod_rewrite.c> | ||||
|     RewriteCond %{HTTPS} !=on | ||||
|     RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] | ||||
|     RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] | ||||
| </IfModule> | ||||
| 
 | ||||
| # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||||
| 
 | ||||
| # Option 2: rewrite example.com → www.example.com | ||||
| 
 | ||||
| # Be aware that the following might not be a good idea if you use "real" | ||||
| # subdomains for certain parts of your website. | ||||
| 
 | ||||
| # <IfModule mod_rewrite.c> | ||||
| #    RewriteCond %{HTTPS} !=on | ||||
| #    RewriteCond %{HTTP_HOST} !^www\..+$ [NC] | ||||
| #    RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] | ||||
| # </IfModule> | ||||
| 
 | ||||
| 
 | ||||
| # ############################################################################## | ||||
| # # SECURITY                                                                   # | ||||
| # ############################################################################## | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Content Security Policy (CSP)                                              | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # You can mitigate the risk of cross-site scripting and other content-injection | ||||
| # attacks by setting a Content Security Policy which whitelists trusted sources | ||||
| # of content for your site. | ||||
| 
 | ||||
| # The example header below allows ONLY scripts that are loaded from the current | ||||
| # site's origin (no inline scripts, no CDN, etc). This almost certainly won't | ||||
| # work as-is for your site! | ||||
| 
 | ||||
| # To get all the details you'll need to craft a reasonable policy for your site, | ||||
| # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or | ||||
| # see the specification: http://w3.org/TR/CSP). | ||||
| 
 | ||||
| # <IfModule mod_headers.c> | ||||
| #    Header set Content-Security-Policy "script-src 'self'; object-src 'self'" | ||||
| #    <FilesMatch "\.(appcache|crx|css|eot|gif|htc|ico|jpe?g|js|m4a|m4v|manifest|mp4|oex|oga|ogg|ogv|otf|pdf|png|safariextz|svg|svgz|ttf|vcf|webapp|webm|webp|woff|xml|xpi)$"> | ||||
| #        Header unset Content-Security-Policy | ||||
| #    </FilesMatch> | ||||
| # </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | File access                                                                | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Block access to directories without a default document. | ||||
| # Usually you should leave this uncommented because you shouldn't allow anyone | ||||
| # to surf through every directory on your server (which may includes rather | ||||
| # private places like the CMS's directories). | ||||
| 
 | ||||
| <IfModule mod_autoindex.c> | ||||
|     Options -Indexes | ||||
| </IfModule> | ||||
| 
 | ||||
| # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||||
| 
 | ||||
| # Block access to hidden files and directories. | ||||
| # This includes directories used by version control systems such as Git and SVN. | ||||
| 
 | ||||
| <IfModule mod_rewrite.c> | ||||
|     RewriteCond %{SCRIPT_FILENAME} -d [OR] | ||||
|     RewriteCond %{SCRIPT_FILENAME} -f | ||||
|     RewriteRule "(^|/)\." - [F] | ||||
| </IfModule> | ||||
| 
 | ||||
| # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||||
| 
 | ||||
| # Block access to backup and source files. | ||||
| # These files may be left by some text editors and can pose a great security | ||||
| # danger when anyone has access to them. | ||||
| 
 | ||||
| <FilesMatch "(^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$"> | ||||
|     Order allow,deny | ||||
|     Deny from all | ||||
|     Satisfy All | ||||
| </FilesMatch> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Secure Sockets Layer (SSL)                                                 | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: | ||||
| # prevent `https://www.example.com` when your certificate only allows | ||||
| # `https://secure.example.com`. | ||||
| 
 | ||||
| # <IfModule mod_rewrite.c> | ||||
| #    RewriteCond %{SERVER_PORT} !^443 | ||||
| #    RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] | ||||
| # </IfModule> | ||||
| 
 | ||||
| # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | ||||
| 
 | ||||
| # Force client-side SSL redirection. | ||||
| 
 | ||||
| # If a user types "example.com" in his browser, the above rule will redirect him | ||||
| # to the secure version of the site. That still leaves a window of opportunity | ||||
| # (the initial HTTP connection) for an attacker to downgrade or redirect the | ||||
| # request. The following header ensures that browser will ONLY connect to your | ||||
| # server via HTTPS, regardless of what the users type in the address bar. | ||||
| # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ | ||||
| 
 | ||||
| # <IfModule mod_headers.c> | ||||
| #    Header set Strict-Transport-Security max-age=16070400; | ||||
| # </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Server software information                                                | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Avoid displaying the exact Apache version number, the description of the | ||||
| # generic OS-type and the information about Apache's compiled-in modules. | ||||
| 
 | ||||
| # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! | ||||
| 
 | ||||
| # ServerTokens Prod | ||||
| 
 | ||||
| 
 | ||||
| # ############################################################################## | ||||
| # # WEB PERFORMANCE                                                            # | ||||
| # ############################################################################## | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Compression                                                                | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| <IfModule mod_deflate.c> | ||||
| 
 | ||||
|     # Force compression for mangled headers. | ||||
|     # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping | ||||
|     <IfModule mod_setenvif.c> | ||||
|         <IfModule mod_headers.c> | ||||
|             SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding | ||||
|             RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding | ||||
|         </IfModule> | ||||
|     </IfModule> | ||||
| 
 | ||||
|     # Compress all output labeled with one of the following MIME-types | ||||
|     # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` | ||||
|     #  and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines | ||||
|     #  as `AddOutputFilterByType` is still in the core directives). | ||||
|     <IfModule mod_filter.c> | ||||
|         AddOutputFilterByType DEFLATE application/atom+xml \ | ||||
|                                       application/javascript \ | ||||
|                                       application/json \ | ||||
|                                       application/rss+xml \ | ||||
|                                       application/vnd.ms-fontobject \ | ||||
|                                       application/x-font-ttf \ | ||||
|                                       application/x-web-app-manifest+json \ | ||||
|                                       application/xhtml+xml \ | ||||
|                                       application/xml \ | ||||
|                                       font/opentype \ | ||||
|                                       image/svg+xml \ | ||||
|                                       image/x-icon \ | ||||
|                                       text/css \ | ||||
|                                       text/html \ | ||||
|                                       text/plain \ | ||||
|                                       text/x-component \ | ||||
|                                       text/xml | ||||
|     </IfModule> | ||||
| 
 | ||||
| </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Content transformations                                                    | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Prevent some of the mobile network providers from modifying the content of | ||||
| # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. | ||||
| 
 | ||||
| # <IfModule mod_headers.c> | ||||
| #    Header set Cache-Control "no-transform" | ||||
| # </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | ETag removal                                                               | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Since we're sending far-future expires headers (see below), ETags can | ||||
| # be removed: http://developer.yahoo.com/performance/rules.html#etags. | ||||
| 
 | ||||
| # `FileETag None` is not enough for every server. | ||||
| <IfModule mod_headers.c> | ||||
|     Header unset ETag | ||||
| </IfModule> | ||||
| 
 | ||||
| FileETag None | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Expires headers (for better cache control)                                 | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # The following expires headers are set pretty far in the future. If you don't | ||||
| # control versioning with filename-based cache busting, consider lowering the | ||||
| # cache time for resources like CSS and JS to something like 1 week. | ||||
| 
 | ||||
| <IfModule mod_expires.c> | ||||
| 
 | ||||
|     ExpiresActive on | ||||
|     ExpiresDefault                                      "access plus 1 month" | ||||
| 
 | ||||
|   # CSS | ||||
|     ExpiresByType text/css                              "access plus 1 year" | ||||
| 
 | ||||
|   # Data interchange | ||||
|     ExpiresByType application/json                      "access plus 0 seconds" | ||||
|     ExpiresByType application/xml                       "access plus 0 seconds" | ||||
|     ExpiresByType text/xml                              "access plus 0 seconds" | ||||
| 
 | ||||
|   # Favicon (cannot be renamed!) | ||||
|     ExpiresByType image/x-icon                          "access plus 1 week" | ||||
| 
 | ||||
|   # HTML components (HTCs) | ||||
|     ExpiresByType text/x-component                      "access plus 1 month" | ||||
| 
 | ||||
|   # HTML | ||||
|     ExpiresByType text/html                             "access plus 0 seconds" | ||||
| 
 | ||||
|   # JavaScript | ||||
|     ExpiresByType application/javascript                "access plus 1 year" | ||||
| 
 | ||||
|   # Manifest files | ||||
|     ExpiresByType application/x-web-app-manifest+json   "access plus 0 seconds" | ||||
|     ExpiresByType text/cache-manifest                   "access plus 0 seconds" | ||||
| 
 | ||||
|   # Media | ||||
|     ExpiresByType audio/ogg                             "access plus 1 month" | ||||
|     ExpiresByType image/gif                             "access plus 1 month" | ||||
|     ExpiresByType image/jpeg                            "access plus 1 month" | ||||
|     ExpiresByType image/png                             "access plus 1 month" | ||||
|     ExpiresByType video/mp4                             "access plus 1 month" | ||||
|     ExpiresByType video/ogg                             "access plus 1 month" | ||||
|     ExpiresByType video/webm                            "access plus 1 month" | ||||
| 
 | ||||
|   # Web feeds | ||||
|     ExpiresByType application/atom+xml                  "access plus 1 hour" | ||||
|     ExpiresByType application/rss+xml                   "access plus 1 hour" | ||||
| 
 | ||||
|   # Web fonts | ||||
|     ExpiresByType application/font-woff                 "access plus 1 month" | ||||
|     ExpiresByType application/vnd.ms-fontobject         "access plus 1 month" | ||||
|     ExpiresByType application/x-font-ttf                "access plus 1 month" | ||||
|     ExpiresByType font/opentype                         "access plus 1 month" | ||||
|     ExpiresByType image/svg+xml                         "access plus 1 month" | ||||
| 
 | ||||
| </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Filename-based cache busting                                               | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # If you're not using a build process to manage your filename version revving, | ||||
| # you might want to consider enabling the following directives to route all | ||||
| # requests such as `/css/style.12345.css` to `/css/style.css`. | ||||
| 
 | ||||
| # To understand why this is important and a better idea than `*.css?v231`, read: | ||||
| # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring | ||||
| 
 | ||||
| # <IfModule mod_rewrite.c> | ||||
| #    RewriteCond %{REQUEST_FILENAME} !-f | ||||
| #    RewriteCond %{REQUEST_FILENAME} !-d | ||||
| #    RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] | ||||
| # </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | File concatenation                                                         | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Allow concatenation from within specific CSS and JS files, e.g.: | ||||
| # Inside of `script.combined.js` you could have | ||||
| #   <!--#include file="libs/jquery.js" --> | ||||
| #   <!--#include file="plugins/jquery.idletimer.js" --> | ||||
| # and they would be included into this single file. | ||||
| 
 | ||||
| # <IfModule mod_include.c> | ||||
| #    <FilesMatch "\.combined\.js$"> | ||||
| #        Options +Includes | ||||
| #        AddOutputFilterByType INCLUDES application/javascript application/json | ||||
| #        SetOutputFilter INCLUDES | ||||
| #    </FilesMatch> | ||||
| #    <FilesMatch "\.combined\.css$"> | ||||
| #        Options +Includes | ||||
| #        AddOutputFilterByType INCLUDES text/css | ||||
| #        SetOutputFilter INCLUDES | ||||
| #    </FilesMatch> | ||||
| # </IfModule> | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # | Persistent connections                                                     | | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Allow multiple requests to be sent over the same TCP connection: | ||||
| # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. | ||||
| 
 | ||||
| # Enable if you serve a lot of static content but, be aware of the | ||||
| # possible disadvantages! | ||||
| 
 | ||||
| # <IfModule mod_headers.c> | ||||
| #    Header set Connection Keep-Alive | ||||
| # </IfModule> | ||||
							
								
								
									
										36
									
								
								client/_index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								client/_index.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| <!doctype html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta http-equiv="x-ua-compatible" content="ie=edge"> | ||||
|     <base href="/"> | ||||
|     <title>Angular Full-Stack Generator</title> | ||||
|     <meta name="description" content=""> | ||||
|     <meta name="viewport" content="width=device-width"> | ||||
|     <!-- Place favicon.ico and apple-touch-icon.png in the root directory --> | ||||
| </head> | ||||
| <body> | ||||
|     <!--[if lt IE 9]> | ||||
|       <p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> | ||||
|     <![endif]--> | ||||
| 
 | ||||
|     <!-- Google Analytics: change UA-XXXXX-X to be your site's ID --> | ||||
|     <script> | ||||
|       (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | ||||
|       (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | ||||
|       m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | ||||
|       })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); | ||||
| 
 | ||||
|       ga('create', 'UA-XXXXX-X'); | ||||
|       ga('send', 'pageview'); | ||||
|     </script> | ||||
| 
 | ||||
|     <!--Unable to bundle ace.js using webpack. Hence have it imported here.--> | ||||
|     <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js"></script> | ||||
|     <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/showdown/0.3.1/showdown.min.js"></script> | ||||
| 
 | ||||
|     <navbar></navbar> | ||||
|     <div ui-view=""></div> | ||||
|     <footer></footer> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										37
									
								
								client/app/account/account.routes.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								client/app/account/account.routes.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export default function routes($stateProvider) { | ||||
|   'ngInject'; | ||||
| 
 | ||||
|   $stateProvider.state('login', { | ||||
|     url: '/login', | ||||
|     template: require('./login/login.html'), | ||||
|     controller: 'LoginController', | ||||
|     controllerAs: 'vm' | ||||
|   }) | ||||
|     .state('logout', { | ||||
|       url: '/logout?referrer', | ||||
|       referrer: 'main', | ||||
|       template: '', | ||||
|       controller($state, Auth) { | ||||
|         'ngInject'; | ||||
| 
 | ||||
|         var referrer = $state.params.referrer || $state.current.referrer || 'main'; | ||||
|         Auth.logout(); | ||||
|         $state.go(referrer); | ||||
|       } | ||||
|     }) | ||||
|     .state('signup', { | ||||
|       url: '/signup', | ||||
|       template: require('./signup/signup.html'), | ||||
|       controller: 'SignupController', | ||||
|       controllerAs: 'vm' | ||||
|     }) | ||||
|     .state('settings', { | ||||
|       url: '/settings', | ||||
|       template: require('./settings/settings.html'), | ||||
|       controller: 'SettingsController', | ||||
|       controllerAs: 'vm', | ||||
|       authenticate: true | ||||
|     }); | ||||
| } | ||||
							
								
								
									
										24
									
								
								client/app/account/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								client/app/account/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import angular from 'angular'; | ||||
| 
 | ||||
| import uiRouter from 'angular-ui-router'; | ||||
| 
 | ||||
| import routing from './account.routes'; | ||||
| import login from './login'; | ||||
| import settings from './settings'; | ||||
| import signup from './signup'; | ||||
| import oauthButtons from '../../components/oauth-buttons'; | ||||
| 
 | ||||
| export default angular.module('app2App.account', [uiRouter, login, settings, signup, oauthButtons]) | ||||
|   .config(routing) | ||||
|   .run(function($rootScope) { | ||||
|     'ngInject'; | ||||
| 
 | ||||
|     $rootScope.$on('$stateChangeStart', function(event, next, nextParams, current) { | ||||
|       if(next.name === 'logout' && current && current.name && !current.authenticate) { | ||||
|         next.referrer = current.name; | ||||
|       } | ||||
|     }); | ||||
|   }) | ||||
|   .name; | ||||
							
								
								
									
										8
									
								
								client/app/account/login/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								client/app/account/login/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import angular from 'angular'; | ||||
| import LoginController from './login.controller'; | ||||
| 
 | ||||
| export default angular.module('app2App.login', []) | ||||
|   .controller('LoginController', LoginController) | ||||
|   .name; | ||||
							
								
								
									
										38
									
								
								client/app/account/login/login.controller.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								client/app/account/login/login.controller.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export default class LoginController { | ||||
|   user = { | ||||
|     name: '', | ||||
|     email: '', | ||||
|     password: '' | ||||
|   }; | ||||
|   errors = { | ||||
|     login: undefined | ||||
|   }; | ||||
|   submitted = false; | ||||
| 
 | ||||
| 
 | ||||
|   /*@ngInject*/ | ||||
|   constructor(Auth, $state) { | ||||
|     this.Auth = Auth; | ||||
|     this.$state = $state; | ||||
|   } | ||||
| 
 | ||||
|   login(form) { | ||||
|     this.submitted = true; | ||||
| 
 | ||||
|     if(form.$valid) { | ||||
|       this.Auth.login({ | ||||
|         email: this.user.email, | ||||
|         password: this.user.password | ||||
|       }) | ||||
|         .then(() => { | ||||
|           // Logged in, redirect to home
 | ||||
|           this.$state.go('main'); | ||||
|         }) | ||||
|         .catch(err => { | ||||
|           this.errors.login = err.message; | ||||
|         }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										53
									
								
								client/app/account/login/login.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								client/app/account/login/login.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| <div class="container"> | ||||
|   <div class="row"> | ||||
|     <div class="col-sm-12"> | ||||
|       <h1>Login</h1> | ||||
|       <p>Accounts are reset on server restart from <code>server/config/seed.js</code>. Default account is <code>test@example.com</code> / <code>test</code></p> | ||||
|       <p>Admin account is <code>admin@example.com</code> / <code>admin</code></p> | ||||
|     </div> | ||||
|     <div class="col-sm-12"> | ||||
|       <form class="form" name="form" ng-submit="vm.login(form)" novalidate> | ||||
| 
 | ||||
|         <div class="form-group"> | ||||
|           <label>Email</label> | ||||
| 
 | ||||
|           <input type="email" name="email" class="form-control" ng-model="vm.user.email" required> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="form-group"> | ||||
|           <label>Password</label> | ||||
| 
 | ||||
|           <input type="password" name="password" class="form-control" ng-model="vm.user.password" required> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="form-group has-error"> | ||||
|           <p class="help-block" ng-show="form.email.$error.required && form.password.$error.required && vm.submitted"> | ||||
|              Please enter your email and password. | ||||
|           </p> | ||||
|           <p class="help-block" ng-show="form.email.$error.email && vm.submitted"> | ||||
|              Please enter a valid email. | ||||
|           </p> | ||||
| 
 | ||||
|           <p class="help-block">{{ vm.errors.login }}</p> | ||||
|         </div> | ||||
| 
 | ||||
|         <div> | ||||
|           <button class="btn btn-inverse btn-lg btn-login" type="submit"> | ||||
|             Login | ||||
|           </button> | ||||
|           <a class="btn btn-default btn-lg btn-register" ui-sref="signup"> | ||||
|             Register | ||||
|           </a> | ||||
|         </div> | ||||
| 
 | ||||
|         <hr/> | ||||
|         <div class="row"> | ||||
|           <div class="col-sm-4 col-md-3"> | ||||
|             <oauth-buttons classes="btn-block"></oauth-buttons> | ||||
|           </div> | ||||
|         </div> | ||||
|       </form> | ||||
|     </div> | ||||
|   </div> | ||||
|   <hr> | ||||
| </div> | ||||
							
								
								
									
										8
									
								
								client/app/account/settings/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								client/app/account/settings/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import angular from 'angular'; | ||||
| import SettingsController from './settings.controller'; | ||||
| 
 | ||||
| export default angular.module('app2App.settings', []) | ||||
|   .controller('SettingsController', SettingsController) | ||||
|   .name; | ||||
							
								
								
									
										36
									
								
								client/app/account/settings/settings.controller.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								client/app/account/settings/settings.controller.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export default class SettingsController { | ||||
|   user = { | ||||
|     oldPassword: '', | ||||
|     newPassword: '', | ||||
|     confirmPassword: '' | ||||
|   }; | ||||
|   errors = { | ||||
|     other: undefined | ||||
|   }; | ||||
|   message = ''; | ||||
|   submitted = false; | ||||
| 
 | ||||
| 
 | ||||
|   /*@ngInject*/ | ||||
|   constructor(Auth) { | ||||
|     this.Auth = Auth; | ||||
|   } | ||||
| 
 | ||||
|   changePassword(form) { | ||||
|     this.submitted = true; | ||||
| 
 | ||||
|     if(form.$valid) { | ||||
|       this.Auth.changePassword(this.user.oldPassword, this.user.newPassword) | ||||
|         .then(() => { | ||||
|           this.message = 'Password successfully changed.'; | ||||
|         }) | ||||
|         .catch(() => { | ||||
|           form.password.$setValidity('mongoose', false); | ||||
|           this.errors.other = 'Incorrect password'; | ||||
|           this.message = ''; | ||||
|         }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										51
									
								
								client/app/account/settings/settings.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								client/app/account/settings/settings.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| <div class="container"> | ||||
|   <div class="row"> | ||||
|     <div class="col-sm-12"> | ||||
|       <h1>Change Password</h1> | ||||
|     </div> | ||||
|     <div class="col-sm-12"> | ||||
|       <form class="form" name="form" ng-submit="vm.changePassword(form)" novalidate> | ||||
| 
 | ||||
|         <div class="form-group"> | ||||
|           <label>Current Password</label> | ||||
| 
 | ||||
|           <input type="password" name="password" class="form-control" ng-model="vm.user.oldPassword" | ||||
|                  mongoose-error/> | ||||
|           <p class="help-block" ng-show="form.password.$error.mongoose"> | ||||
|               {{ vm.errors.other }} | ||||
|           </p> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="form-group"> | ||||
|           <label>New Password</label> | ||||
| 
 | ||||
|           <input type="password" name="newPassword" class="form-control" ng-model="vm.user.newPassword" | ||||
|                  ng-minlength="3" | ||||
|                  required/> | ||||
|           <p class="help-block" | ||||
|              ng-show="(form.newPassword.$error.minlength || form.newPassword.$error.required) && (form.newPassword.$dirty || vm.submitted)"> | ||||
|             Password must be at least 3 characters. | ||||
|           </p> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="form-group"> | ||||
|           <label>Confirm New Password</label> | ||||
| 
 | ||||
|           <input type="password" name="confirmPassword" class="form-control" ng-model="vm.user.confirmPassword" | ||||
|                  match="vm.user.newPassword" | ||||
|                  ng-minlength="3" | ||||
|                  required=""/> | ||||
|           <p class="help-block" | ||||
|              ng-show="form.confirmPassword.$error.match && vm.submitted"> | ||||
|             Passwords must match. | ||||
|           </p> | ||||
| 
 | ||||
|         </div> | ||||
| 
 | ||||
|         <p class="help-block"> {{ vm.message }} </p> | ||||
| 
 | ||||
|         <button class="btn btn-lg btn-primary" type="submit">Save changes</button> | ||||
|       </form> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										8
									
								
								client/app/account/signup/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								client/app/account/signup/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import angular from 'angular'; | ||||
| import SignupController from './signup.controller'; | ||||
| 
 | ||||
| export default angular.module('app2App.signup', []) | ||||
|   .controller('SignupController', SignupController) | ||||
|   .name; | ||||
							
								
								
									
										45
									
								
								client/app/account/signup/signup.controller.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								client/app/account/signup/signup.controller.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import angular from 'angular'; | ||||
| 
 | ||||
| export default class SignupController { | ||||
|   user = { | ||||
|     name: '', | ||||
|     email: '', | ||||
|     password: '' | ||||
|   }; | ||||
|   errors = {}; | ||||
|   submitted = false; | ||||
| 
 | ||||
| 
 | ||||
|   /*@ngInject*/ | ||||
|   constructor(Auth, $state) { | ||||
|     this.Auth = Auth; | ||||
|     this.$state = $state; | ||||
|   } | ||||
| 
 | ||||
|   register(form) { | ||||
|     this.submitted = true; | ||||
| 
 | ||||
|     if(form.$valid) { | ||||
|       return this.Auth.createUser({ | ||||
|         name: this.user.name, | ||||
|         email: this.user.email, | ||||
|         password: this.user.password | ||||
|       }) | ||||
|         .then(() => { | ||||
|           // Account created, redirect to home
 | ||||
|           this.$state.go('main'); | ||||
|         }) | ||||
|         .catch(err => { | ||||
|           err = err.data; | ||||
|           this.errors = {}; | ||||
|           // Update validity of form fields that match the mongoose errors
 | ||||
|           angular.forEach(err.errors, (error, field) => { | ||||
|             form[field].$setValidity('mongoose', false); | ||||
|             this.errors[field] = error.message; | ||||
|           }); | ||||
|         }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										86
									
								
								client/app/account/signup/signup.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								client/app/account/signup/signup.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | |||
| <div class="container"> | ||||
|   <div class="row"> | ||||
|     <div class="col-sm-12"> | ||||
|       <h1>Sign up</h1> | ||||
|     </div> | ||||
|     <div class="col-sm-12"> | ||||
|       <form class="form" name="form" ng-submit="vm.register(form)" novalidate> | ||||
| 
 | ||||
|         <div class="form-group" ng-class="{ 'has-success': form.name.$valid && vm.submitted, | ||||
|                                             'has-error': form.name.$invalid && vm.submitted }"> | ||||
|           <label>Name</label> | ||||
| 
 | ||||
|           <input type="text" name="name" class="form-control" ng-model="vm.user.name" | ||||
|                  required/> | ||||
|           <p class="help-block" ng-show="form.name.$error.required && vm.submitted"> | ||||
|             A name is required | ||||
|           </p> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="form-group" ng-class="{ 'has-success': form.email.$valid && vm.submitted, | ||||
|                                             'has-error': form.email.$invalid && vm.submitted }"> | ||||
|           <label>Email</label> | ||||
| 
 | ||||
|           <input type="email" name="email" class="form-control" ng-model="vm.user.email" | ||||
|                  required | ||||
|                  mongoose-error/> | ||||
|           <p class="help-block" ng-show="form.email.$error.email && vm.submitted"> | ||||
|             Doesn't look like a valid email. | ||||
|           </p> | ||||
|           <p class="help-block" ng-show="form.email.$error.required && vm.submitted"> | ||||
|             What's your email address? | ||||
|           </p> | ||||
|           <p class="help-block" ng-show="form.email.$error.mongoose"> | ||||
|             {{ vm.errors.email }} | ||||
|           </p> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="form-group" ng-class="{ 'has-success': form.password.$valid && vm.submitted, | ||||
|                                             'has-error': form.password.$invalid && vm.submitted }"> | ||||
|           <label>Password</label> | ||||
| 
 | ||||
|           <input type="password" name="password" class="form-control" ng-model="vm.user.password" | ||||
|                  ng-minlength="3" | ||||
|                  required | ||||
|                  mongoose-error/> | ||||
|           <p class="help-block" | ||||
|              ng-show="(form.password.$error.minlength || form.password.$error.required) && vm.submitted"> | ||||
|             Password must be at least 3 characters. | ||||
|           </p> | ||||
|           <p class="help-block" ng-show="form.password.$error.mongoose"> | ||||
|             {{ vm.errors.password }} | ||||
|           </p> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="form-group" ng-class="{ 'has-success': form.confirmPassword.$valid && vm.submitted, | ||||
|                                             'has-error': form.confirmPassword.$invalid && vm.submitted }"> | ||||
|           <label>Confirm Password</label> | ||||
|           <input type="password" name="confirmPassword" class="form-control" ng-model="vm.user.confirmPassword" | ||||
|                  match="vm.user.password" | ||||
|                  ng-minlength="3" required/> | ||||
|           <p class="help-block" | ||||
|              ng-show="form.confirmPassword.$error.match && vm.submitted"> | ||||
|             Passwords must match. | ||||
|           </p> | ||||
|         </div> | ||||
| 
 | ||||
|         <div> | ||||
|           <button class="btn btn-inverse btn-lg btn-register" type="submit"> | ||||
|             Sign up | ||||
|           </button> | ||||
|           <a class="btn btn-default btn-lg btn-login" ui-sref="login"> | ||||
|             Login | ||||
|           </a> | ||||
|         </div> | ||||
| 
 | ||||
|         <hr/> | ||||
|         <div class="row"> | ||||
|           <div class="col-sm-4 col-md-3"> | ||||
|             <oauth-buttons classes="btn-block"></oauth-buttons> | ||||
|           </div> | ||||
|         </div> | ||||
|       </form> | ||||
|     </div> | ||||
|   </div> | ||||
|   <hr> | ||||
| </div> | ||||
							
								
								
									
										14
									
								
								client/app/admin/admin.controller.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								client/app/admin/admin.controller.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export default class AdminController { | ||||
|   /*@ngInject*/ | ||||
|   constructor(User) { | ||||
|     // Use the User $resource to fetch all users
 | ||||
|     this.users = User.query(); | ||||
|   } | ||||
| 
 | ||||
|   delete(user) { | ||||
|     user.$remove(); | ||||
|     this.users.splice(this.users.indexOf(user), 1); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										19
									
								
								client/app/admin/admin.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								client/app/admin/admin.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| .trash { color:rgb(209, 91, 71); } | ||||
| 
 | ||||
| .user-list li { | ||||
| 	display: flex; | ||||
| 	border: none; | ||||
| 	border-bottom: 1px lightgray solid; | ||||
| 	margin-bottom: 0; | ||||
| } | ||||
| .user-list li:last-child { | ||||
| 	border-bottom: none; | ||||
| } | ||||
| .user-list li .user-info { | ||||
| 	flex-grow: 1; | ||||
| } | ||||
| .user-list li .trash { | ||||
| 	display: flex; | ||||
| 	align-items: center; | ||||
| 	text-decoration: none; | ||||
| } | ||||
							
								
								
									
										12
									
								
								client/app/admin/admin.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								client/app/admin/admin.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| <div class="container"> | ||||
|   <p>The delete user and user index api routes are restricted to users with the 'admin' role.</p> | ||||
|   <ul class="list-group user-list"> | ||||
|     <li class="list-group-item" ng-repeat="user in admin.users"> | ||||
| 	    <div class="user-info"> | ||||
| 	        <strong>{{user.name}}</strong><br> | ||||
| 	        <span class="text-muted">{{user.email}}</span> | ||||
| 	    </div> | ||||
|         <a ng-click="admin.delete(user)" class="trash"><span class="fa fa-trash fa-2x"></span></a> | ||||
|     </li> | ||||
|   </ul> | ||||
| </div> | ||||
							
								
								
									
										13
									
								
								client/app/admin/admin.routes.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								client/app/admin/admin.routes.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export default function routes($stateProvider) { | ||||
|   'ngInject'; | ||||
| 
 | ||||
|   $stateProvider.state('admin', { | ||||
|     url: '/admin', | ||||
|     template: require('./admin.html'), | ||||
|     controller: 'AdminController', | ||||
|     controllerAs: 'admin', | ||||
|     authenticate: 'admin' | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										10
									
								
								client/app/admin/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								client/app/admin/index.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import angular from 'angular'; | ||||
| import routes from './admin.routes'; | ||||
| import AdminController from './admin.controller'; | ||||
| 
 | ||||
| export default angular.module('app2App.admin', ['app2App.auth', 'ui.router']) | ||||
|   .config(routes) | ||||
|   .controller('AdminController', AdminController) | ||||
|   .name; | ||||
							
								
								
									
										9
									
								
								client/app/app.config.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								client/app/app.config.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export function routeConfig($urlRouterProvider, $locationProvider) { | ||||
|   'ngInject'; | ||||
| 
 | ||||
|   $urlRouterProvider.otherwise('/'); | ||||
| 
 | ||||
|   $locationProvider.html5Mode(true); | ||||
| } | ||||
							
								
								
									
										7
									
								
								client/app/app.constants.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								client/app/app.constants.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import angular from 'angular'; | ||||
| 
 | ||||
| export default angular.module('app2App.constants', []) | ||||
|   .constant('appConfig', require('../../server/config/environment/shared')) | ||||
|   .name; | ||||
							
								
								
									
										125
									
								
								client/app/app.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								client/app/app.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,125 @@ | |||
| @import '~bootstrap/dist/css/bootstrap.css'; | ||||
| @import '~bootstrap-social/bootstrap-social.css'; | ||||
| /** | ||||
|  * Bootstrap Fonts | ||||
|  */ | ||||
| 
 | ||||
| @font-face { | ||||
|   font-family: 'Glyphicons Halflings'; | ||||
|   src: url('/assets/fonts/bootstrap/glyphicons-halflings-regular.eot'); | ||||
|   src: url('/assets/fonts/bootstrap/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), | ||||
|   url('/assets/fonts/bootstrap/glyphicons-halflings-regular.woff') format('woff'), | ||||
|   url('/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf') format('truetype'), | ||||
|   url('/assets/fonts/bootstrap/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); | ||||
| } | ||||
| 
 | ||||
| @import '~font-awesome/css/font-awesome.css'; | ||||
| 
 | ||||
| /** | ||||
|  *Font Awesome Fonts | ||||
|  */ | ||||
| 
 | ||||
| @font-face { | ||||
|   font-family: 'FontAwesome'; | ||||
|   src: url('/assets/fonts/font-awesome/fontawesome-webfont.eot?v=4.1.0'); | ||||
|   src: url('/assets/fonts/font-awesome/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'), | ||||
|   url('/assets/fonts/font-awesome/fontawesome-webfont.woff?v=4.1.0') format('woff'), | ||||
|   url('/assets/fonts/font-awesome/fontawesome-webfont.ttf?v=4.1.0') format('truetype'), | ||||
|   url('/assets/fonts/font-awesome/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg'); | ||||
|   font-weight: normal; | ||||
|   font-style: normal; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * App-wide Styles | ||||
|  */ | ||||
| 
 | ||||
| .browserupgrade { | ||||
|   margin: 0.2em 0; | ||||
|   background: #ccc; | ||||
|   color: #000; | ||||
|   padding: 0.2em 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Component styles are injected through gulp */ | ||||
| /* inject:css */ | ||||
| @import 'admin/admin.css'; | ||||
| @import 'main/main.css'; | ||||
| @import '../components/footer/footer.css'; | ||||
| @import '../components/modal/modal.css'; | ||||
| @import '../components/oauth-buttons/oauth-buttons.css'; | ||||
| /* endinject */ | ||||
| 
 | ||||
| .navbar { | ||||
|   margin-bottom: 0; | ||||
|   background-color: #2d363a; | ||||
|   border-radius: 0; | ||||
|   border-top: 0; | ||||
|   border-right: 0; | ||||
|   border-left: 0; | ||||
| } | ||||
| 
 | ||||
| .navbar-brand { | ||||
|   padding: 0; | ||||
|   padding-top: 15px; | ||||
|   margin-right: 15px; | ||||
| } | ||||
| 
 | ||||
| .navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus { | ||||
|   color: whitesmoke; | ||||
|   background-color: transparent; | ||||
| } | ||||
| 
 | ||||
| .navbar-default .navbar-brand { | ||||
|   color: white; | ||||
| } | ||||
| 
 | ||||
| .navbar-default .navbar-nav > li > a { | ||||
|   color: white; | ||||
| } | ||||
| 
 | ||||
| .navbar-brand-header { | ||||
|   width: 100%; | ||||
|   margin-left: 100px; | ||||
|   font-size: 25px; | ||||
| } | ||||
| 
 | ||||
| .navbar-brand-header a:hover, a:focus { | ||||
|   color: #2d363a; | ||||
|   text-decoration: none; | ||||
| } | ||||
| 
 | ||||
| #header li a.nav-button:hover { | ||||
|   background-color: #333; | ||||
| } | ||||
| 
 | ||||
| #header li.dropdown.open a.dropdown-toggle { | ||||
|   background-color: #333; | ||||
| } | ||||
| 
 | ||||
| #header #logo { | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   width: 91px; | ||||
|   height: 70px; | ||||
|   text-align: center; | ||||
|   background-color: #2c95dd; | ||||
|   z-index: 100; | ||||
| } | ||||
| 
 | ||||
| #header #logo img { | ||||
|   margin-top: 30px; | ||||
| } | ||||
| 
 | ||||
| #header a { | ||||
|   color: #fff; | ||||
| } | ||||
| 
 | ||||
| #header .dropdown li a { | ||||
|   color: #333; | ||||
| } | ||||
| 
 | ||||
| .ace_editor { height: 800px; } | ||||
| 
 | ||||
							
								
								
									
										126
									
								
								client/app/app.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								client/app/app.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,126 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| import angular from 'angular'; | ||||
| // import ngAnimate from 'angular-animate';
 | ||||
| import ngCookies from 'angular-cookies'; | ||||
| import ngResource from 'angular-resource'; | ||||
| import ngSanitize from 'angular-sanitize'; | ||||
| 
 | ||||
| import uiRouter from 'angular-ui-router'; | ||||
| import uiBootstrap from 'angular-ui-bootstrap'; | ||||
| import 'angular-validation-match'; | ||||
| 
 | ||||
| 
 | ||||
| import 'angular-ui-ace'; | ||||
| 
 | ||||
| import 'yamljs/dist/yaml.min'; | ||||
| 
 | ||||
| import 'ansi-to-html'; | ||||
| 
 | ||||
| import 'angular-markdown-directive'; | ||||
| 
 | ||||
| import treecontrol from 'angular-tree-control'; | ||||
| 
 | ||||
| import 'angular-tree-control/css/tree-control-attribute.css'; | ||||
| import 'angular-tree-control/css/tree-control.css'; | ||||
| 
 | ||||
| import { | ||||
|   routeConfig | ||||
| } from './app.config'; | ||||
| 
 | ||||
| import _Auth from '../components/auth/auth.module'; | ||||
| import account from './account'; | ||||
| import admin from './admin'; | ||||
| import navbar from '../components/navbar/navbar.component'; | ||||
| import footer from '../components/footer/footer.component'; | ||||
| import main from './main/main.component'; | ||||
| import DesignerComponent from './designer/designer.component'; | ||||
| import ProjectComponent from './project/project.component'; | ||||
| import InventoryComponent from './designer/inventory/inventory.component'; | ||||
| import PlaybookComponent from './designer/playbook/playbook.component'; | ||||
| import FileBrowserComponent from './designer/file_browser/file_browser.component'; | ||||
| import RolesComponent from './designer/roles/roles.component'; | ||||
| import RunsComponent from './runs/runs.component'; | ||||
| import CustomModulesComponent from './custom_modules/custom_modules.component'; | ||||
| 
 | ||||
| import Projects from './services/projects/projects.service'; | ||||
| import ansible from './services/ansible/ansible.service'; | ||||
| import YAML from './providers/yaml/yaml.service'; | ||||
| import yamlFile from './services/yamlFile/yamlFile.service'; | ||||
| 
 | ||||
| import customModules from './custom_modules/custom_modules.service'; | ||||
| 
 | ||||
| import ansi2html from './providers/ansi2html/ansi2html.service'; | ||||
| 
 | ||||
| import constants from './app.constants'; | ||||
| import util from '../components/util/util.module'; | ||||
| 
 | ||||
| import NewInventoryController from './designer/inventory/new_inventory/new_inventory.controller'; | ||||
| 
 | ||||
| 
 | ||||
| import NewGroupController from './designer/inventory/new_group/new_group.controller'; | ||||
| import NewHostController from './designer/inventory/new_host/new_host.controller'; | ||||
| import ComplexVarController from './directives/complexVar/complexVar.controller'; | ||||
| import NewPlaybookController from './designer/playbook/new_playbook/new_playbook.controller'; | ||||
| import ExecutionController from './designer/execution/execution.controller'; | ||||
| import NewPlayController from './designer/playbook/new_play/new_play.controller'; | ||||
| import NewTaskController from './designer/tasks/new_task/new_task.controller'; | ||||
| 
 | ||||
| import NewFileController from './designer/roles/new_file/new_file.controller'; | ||||
| import NewRoleController from './designer/roles/new_role/new_role.controller'; | ||||
| import SearchRoleController from './designer/roles/search_role/search_role.controller'; | ||||
| 
 | ||||
| import ComplexVarModalController from './modals/complex_var_modal/complex_var_modal.controller'; | ||||
| 
 | ||||
| import NewModuleController from './custom_modules/new_module/new_module.controller'; | ||||
| 
 | ||||
| import dictToKeyValueArray from './filters/dictToKeyValueArray/dictToKeyValueArray.filter'; | ||||
| import dictToKeyValueArraySimple from './filters/dictToKeyValueArraySimple/dictToKeyValueArraySimple.filter'; | ||||
| import keyValueArrayToDict from './filters/keyValueArrayToDict/keyValueArrayToDict.filter'; | ||||
| import keyValueArrayToArray from './filters/keyValueArrayToArray/keyValueArrayToArray.filter'; | ||||
| import addDotInKey from './filters/addDotInKey/addDotInKey.filter'; | ||||
| import removeDotInKey from './filters/removeDotInKey/removeDotInKey.filter'; | ||||
| import json2yaml from './filters/json2yaml/json2yaml.filter'; | ||||
| 
 | ||||
| import complexVar from './directives/complexVar/complexVar.directive'; | ||||
| import tasks from './designer/tasks/tasks.directive'; | ||||
| 
 | ||||
| import editor from './services/editor/editor.service'; | ||||
| 
 | ||||
| import './app.css'; | ||||
| 
 | ||||
| angular.module('app2App', [ngCookies, ngResource, ngSanitize, uiRouter, uiBootstrap, _Auth, account, | ||||
|   admin, 'validation.match', 'ui.ace', navbar, footer, main, constants, util, ansi2html, | ||||
|   // Components
 | ||||
|   DesignerComponent, ProjectComponent, InventoryComponent, PlaybookComponent, FileBrowserComponent, RolesComponent, RunsComponent, CustomModulesComponent, | ||||
|   // Services
 | ||||
|   YAML, yamlFile, Projects, ansible, ansi2html, editor, customModules, | ||||
|   // Controllers
 | ||||
|   NewInventoryController, NewGroupController, NewHostController, ComplexVarController, NewPlaybookController, ExecutionController, NewPlayController, NewTaskController, ComplexVarModalController, | ||||
|   NewFileController, NewRoleController, SearchRoleController, NewModuleController, | ||||
|   // Filters
 | ||||
|   dictToKeyValueArray, dictToKeyValueArraySimple, keyValueArrayToDict, keyValueArrayToArray, addDotInKey, removeDotInKey, json2yaml, | ||||
|   // Directives
 | ||||
|   complexVar, tasks, treecontrol, 'btford.markdown' | ||||
| 
 | ||||
| ]) | ||||
|   .config(routeConfig) | ||||
|   .run(function($rootScope, $location, Auth) { | ||||
|     'ngInject'; | ||||
|     // Redirect to login if route requires auth and you're not logged in
 | ||||
| 
 | ||||
|     $rootScope.$on('$stateChangeStart', function(event, next) { | ||||
|       Auth.isLoggedIn(function(loggedIn) { | ||||
|         if(next.authenticate && !loggedIn) { | ||||
|           $location.path('/login'); | ||||
|         } | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
| angular.element(document) | ||||
|   .ready(() => { | ||||
|     angular.bootstrap(document, ['app2App'], { | ||||
|       strictDi: true | ||||
|     }); | ||||
|   }); | ||||
							
								
								
									
										235
									
								
								client/app/custom_modules/custom_modules.component.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								client/app/custom_modules/custom_modules.component.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,235 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| const uiRouter = require('angular-ui-router'); | ||||
| 
 | ||||
| import routes from './custom_modules.routes'; | ||||
| 
 | ||||
| export class CustomModulesComponent { | ||||
|   /*@ngInject*/ | ||||
|   constructor($scope,customModules,$sce,ansi2html,Projects,$uibModal,YAML) { | ||||
|     'ngInject'; | ||||
| 
 | ||||
|     $scope.custom_modules = []; | ||||
|     $scope.selectedModule = {module:{},module_code:"",module_unchanged_code:""}; | ||||
|     $scope.selected_module_code = "something"; | ||||
| 
 | ||||
|     $scope.showNewModuleForm = {value:false}; | ||||
| 
 | ||||
|     $scope.getProjects = function(){ | ||||
|       $scope.projects = Projects.resource.query(function(){ | ||||
|         if($scope.projects.length){ | ||||
|           $scope.selectedProjectID = localStorage.selectedProjectID || $scope.projects[0]._id; | ||||
|           $scope.projectSelected($scope.selectedProjectID) | ||||
|         } | ||||
| 
 | ||||
|       }) | ||||
|     }; | ||||
| 
 | ||||
|     $scope.projectSelected = function(projectID){ | ||||
| 
 | ||||
|       localStorage.selectedProjectID = projectID; | ||||
| 
 | ||||
|       $scope.selectedProject = Projects.resource.get({id: projectID},function(){ | ||||
|         Projects.selectedProject = $scope.selectedProject; | ||||
|         $scope.getCustomModules(); | ||||
|       }) | ||||
|     }; | ||||
| 
 | ||||
|     $scope.getProjects(); | ||||
| 
 | ||||
|     $scope.getCustomModules = function(){ | ||||
|       customModules.get(function(response){ | ||||
|         console.log(response.data); | ||||
|         var lines = response.data.split("\n"); | ||||
|         if(lines.length) | ||||
|           lines = lines | ||||
|             .filter(function(line){return line.indexOf(".py") > -1}) | ||||
|             .map(function(item){return {name:item}}); | ||||
|         $scope.custom_modules = lines; | ||||
| 
 | ||||
|         if($scope.selectedModule.module.name){ | ||||
|           $scope.selectedModule.module = $scope.custom_modules.filter(function(item){ | ||||
|             return (item.name == $scope.selectedModule.module.name) | ||||
|           })[0] | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.loadingModuleCode = false | ||||
| 
 | ||||
|     $scope.showModuleCode = function(module_name){ | ||||
|       $scope.loadingModuleCode = true; | ||||
|       if(!module_name){ | ||||
|         $scope.selectedModule.module_code = "Select a module"; | ||||
|         return; | ||||
|       } | ||||
|       customModules.show(module_name,function(response) { | ||||
|         $scope.loadingModuleCode = false; | ||||
|         $scope.selectedModule.module_code = response.data.split("Stream :: close")[0]; | ||||
|         $scope.selectedModule.module_unchanged_code = angular.copy($scope.selectedModule.module_code); | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.$watch('selectedModule.module',function(newValue,oldValue){ | ||||
|       if(newValue.name && newValue.name !== oldValue.name){ | ||||
|         $scope.selectedModule.module_code = "Loading Module Code..."; | ||||
|         $scope.showModuleCode(newValue.name) | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     $scope.code_has_changed = false; | ||||
| 
 | ||||
|     $scope.codeChanged = function(){ | ||||
|       console.log("Code Changed"); | ||||
|       if($scope.selectedModule.module_unchanged_code !== $scope.selectedModule.module_code){ | ||||
|         $scope.code_has_changed = true | ||||
|       }else{ | ||||
|         $scope.code_has_changed = false | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     $scope.discardCodeChanges = function(){ | ||||
|       $scope.selectedModule.module_code = angular.copy($scope.selectedModule.module_unchanged_code); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.saveModule = function(){ | ||||
|       $scope.saving = true; | ||||
|       customModules.save($scope.selectedModule.module.name,$scope.selectedModule.module_code,function(response){ | ||||
|         $scope.saving = false; | ||||
|         $scope.code_has_changed = false; | ||||
|         $scope.selectedModule.module_unchanged_code = angular.copy($scope.selectedModule.module_code); | ||||
|         console.log("Success") | ||||
|       },function(response){ | ||||
|         $scope.saving = false; | ||||
|         console.error(response.data) | ||||
|       }) | ||||
|     }; | ||||
| 
 | ||||
|     $scope.testModule = function(){ | ||||
| 
 | ||||
|       var re = /([^]+DOCUMENTATION = '''\s+)([^]+?)(\s+'''[^]+)/; | ||||
|       var module_string = $scope.selectedModule.module_code.replace(re,'$2'); | ||||
| 
 | ||||
|       $scope.selectedModuleObject = YAML.parse(module_string); | ||||
| 
 | ||||
|       //var options_copy = angular.copy($scope.selectedModuleObject.options);
 | ||||
|       var options_copy = {}; | ||||
|       /*options_copy = options_copy.map(function(item){ | ||||
|        var temp_obj = {}; | ||||
|        temp_obj[item.name] = ""; | ||||
|        return temp_obj | ||||
|        });*/ | ||||
| 
 | ||||
|       var module_name = $scope.selectedModule.module.name; | ||||
|       var module_cached_args = null; | ||||
| 
 | ||||
|       try{ | ||||
|         module_cached_args = JSON.parse(localStorage['test_args_'+module_name]); | ||||
|       }catch (e){ | ||||
|         console.log("Error getting cached arguments."); | ||||
|         module_cached_args = null; | ||||
|       } | ||||
| 
 | ||||
|       angular.forEach($scope.selectedModuleObject.options,function(value,key){ | ||||
|         //var temp_obj = {};
 | ||||
|         //temp_obj[key] = "";
 | ||||
|         options_copy[key] = ""; | ||||
| 
 | ||||
|         if(module_cached_args && key in module_cached_args){ | ||||
|           options_copy[key] = module_cached_args[key]; | ||||
|         } | ||||
| 
 | ||||
|       }); | ||||
| 
 | ||||
| 
 | ||||
|       var variable = {name:'',complexValue:options_copy}; | ||||
|       $scope.showComplexVariable(variable); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     $scope.newModule = function(){ | ||||
|       $scope.showNewModuleForm.value = true; | ||||
|       $scope.$broadcast ('newModule'); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.editModule = function(){ | ||||
|       $scope.showNewModuleForm.value = true; | ||||
|       $scope.$broadcast ('editModule'); | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     $scope.showComplexVariable = function(variable){ | ||||
|       $scope.result = ""; | ||||
|       var modalInstance = $uibModal.open({ | ||||
|         animation: true, | ||||
|         /*templateUrl: 'createTaskContent.html',*/ | ||||
|         templateUrl: 'app/modals/complex_var_modal/complexVariable.html', | ||||
|         controller: 'ComplexVarModalController', | ||||
|         size: 'sm', | ||||
|         backdrop: 'static', | ||||
|         keyboard: false, | ||||
|         closeByEscape: false, | ||||
|         closeByDocument: false, | ||||
|         resolve: { | ||||
|           path: function () { | ||||
|             return variable.name | ||||
|           }, | ||||
|           hostvars: function(){ | ||||
|             return null | ||||
|           }, | ||||
|           members: function(){ | ||||
|             return variable.complexValue | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       modalInstance.result.then(function (module_args) { | ||||
| 
 | ||||
|         var module_name = $scope.selectedModule.module.name; | ||||
| 
 | ||||
|         /*var args = ""; | ||||
|          angular.forEach(selectedItem,function(value,key){ | ||||
| 
 | ||||
|          if(value){ | ||||
|          args += " " + key + "=" + value | ||||
|          } | ||||
| 
 | ||||
|          }); | ||||
| 
 | ||||
|          if(args){ | ||||
|          module_name += " -a " + args | ||||
|          }*/ | ||||
| 
 | ||||
|         localStorage['test_args_'+module_name] = JSON.stringify(module_args); | ||||
| 
 | ||||
|         $scope.testing = true; | ||||
| 
 | ||||
|         customModules.test(module_name,module_args,function(response) { | ||||
|             $scope.testing = false; | ||||
|             $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data.split("Stream :: close")[0]).replace(/\n/g, "<br>")); | ||||
|           }, | ||||
|           function(response) { | ||||
|             $scope.testing = false; | ||||
|             $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data.split("Stream :: close")[0]).replace(/\n/g, "<br>")); | ||||
|           }); | ||||
| 
 | ||||
|       }, function () { | ||||
| 
 | ||||
|       }); | ||||
| 
 | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.custom_modules', [uiRouter]) | ||||
|   .config(routes) | ||||
|   .component('customModules', { | ||||
|     template: require('./custom_modules.html'), | ||||
|     controller: CustomModulesComponent, | ||||
|     controllerAs: 'customModulesCtrl' | ||||
|   }) | ||||
|   .name; | ||||
							
								
								
									
										17
									
								
								client/app/custom_modules/custom_modules.component.spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								client/app/custom_modules/custom_modules.component.spec.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Component: CustomModulesComponent', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.custom_modules')); | ||||
| 
 | ||||
|   var CustomModulesComponent; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($componentController) { | ||||
|     CustomModulesComponent = $componentController('custom_modules', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										55
									
								
								client/app/custom_modules/custom_modules.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								client/app/custom_modules/custom_modules.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| <div class="row" style="margin:20px;"> | ||||
|   <div class="col-md-7"> | ||||
|     <div ng-show="!showNewModuleForm.value"> | ||||
|       <div style="display: inline-block"><select class="form-control" ng-model="selectedProjectID" ng-change="projectSelected(selectedProjectID)" ng-options="project._id as project.name for project in projects"> | ||||
|       </select></div> | ||||
|       <button class="btn btn-default" ng-click="newModule()"> New Module <span class="fa fa-plus"></span> </button> | ||||
|       <button class="btn btn-default" ng-disabled="!selectedModule.module.name || loadingModuleCode" ng-click="editModule()"> Edit Module <span class="fa fa-edit"></span> </button> | ||||
| 
 | ||||
|       <div class="table-responsive"> | ||||
|         <table class="table"> | ||||
|           <thead> | ||||
|           <tr> | ||||
|             <th>Select</th> | ||||
|             <th>Name</th> | ||||
|             <!--<th>Actions</th>--> | ||||
|           </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|           <tr ng-repeat="module in custom_modules"> | ||||
|             <td><input name="moduleGroup" type="radio" ng-model="selectedModule.module" ng-value="module"> | ||||
|             </td> | ||||
|             </td> | ||||
|             <td>{{module.name}}</td> | ||||
|             <!--<td><div class="btn-group"> | ||||
|               <label class="btn btn-default" ng-click="showTaskModal($index)" ><span class="fa fa-edit"></span></label> | ||||
|               <label class="btn btn-danger"  ng-click="deleteTask($index)" confirm="Are you sure you want to delete?"><span class="fa fa-trash"></span></label> | ||||
|               <div style="display: inline-block" tooltip-enable="!task.tags" uib-tooltip="Tag must be assigned to play individually"><label class="btn btn-success" ng-disabled="!task.tags"  ng-click="executeAnsiblePlayBook(task.tags,'Task',task.name, selectedPlay)" ><span class="fa fa-play"></span></label></div> | ||||
|               <label class="btn btn-primary" ng-disabled="$first"  ng-click="moveUp(selectedPlay.play.tasks,$index,'saveTaskListLoading')" ><span class="fa fa-arrow-up"></span></label> | ||||
|               <label class="btn btn-primary" ng-disabled="$last" ng-click="moveDown(selectedPlay.play.tasks,$index,'saveTaskListLoading')" ><span class="fa fa-arrow-down"></span></label> | ||||
|             </div></td>--> | ||||
|           </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|       </div> | ||||
| 
 | ||||
|       <!--<button class="btn btn-primary" ng-disabled="!selectedModule.module.name || !code_has_changed" ng-click="saveModule()"> Save <span ng-if="!saving" class="fa fa-save"></span> <span ng-if="saving" class="fa fa-spin fa-spinner"></span> </button> | ||||
|       <button class="btn btn-warning" ng-disabled="!selectedModule.module.name || !code_has_changed" confirm="Are you sure you want to discard code changes?" ng-click="discardCodeChanges()"> Discard <span class="fa fa-rotate-left"></span> </button>--> | ||||
|       <button class="btn btn-default" ng-disabled="!selectedModule.module.name || loadingModuleCode" ng-click="testModule()"> Test <span ng-if="!testing" class="fa fa-check-circle-o"></span> <span ng-if="testing" class="fa fa-spin fa-spinner"></span> </button> | ||||
| 
 | ||||
|       <div style="background:black;color:lightgrey;width:100%;padding:20px;word-wrap: break-word;" ng-if="result"> | ||||
|         <p class="logconsole" ng-bind-html="result"></p> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="slide-animate" ng-show="showNewModuleForm.value" ng-include="'app/custom_modules/new_module/new_module.html'"></div> | ||||
| 
 | ||||
|   </div> | ||||
|   <div class="col-md-5"> | ||||
|     <div ng-readonly="!showNewModuleForm.value" ui-ace="{theme:'twilight',document:'Python',mode:'python',onChange:codeChanged}" ng-model="selectedModule.module_code"> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|   </div> | ||||
| </div> | ||||
| 
 | ||||
							
								
								
									
										10
									
								
								client/app/custom_modules/custom_modules.routes.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								client/app/custom_modules/custom_modules.routes.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export default function($stateProvider) { | ||||
|   'ngInject'; | ||||
|   $stateProvider | ||||
|     .state('custom_modules', { | ||||
|       url: '/custom_modules', | ||||
|       template: '<custom-modules></custom-modules>' | ||||
|     }); | ||||
| } | ||||
							
								
								
									
										30
									
								
								client/app/custom_modules/custom_modules.service.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								client/app/custom_modules/custom_modules.service.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function customModulesService($http,Projects) { | ||||
| 	// AngularJS will instantiate a singleton by calling "new" on this function
 | ||||
| 
 | ||||
|   var uri = '/api/custom_modules'; | ||||
| 
 | ||||
|   this.get = function(successCallback,errorCallback){ | ||||
|     $http.post(uri + '/query',{ansibleEngine:Projects.selectedProject.ansibleEngine}).then(successCallback,errorCallback) | ||||
|   }; | ||||
| 
 | ||||
|   this.show = function(customModule,successCallback,errorCallback){ | ||||
|     $http.post(uri+ '/' + customModule+'/get',{ansibleEngine:Projects.selectedProject.ansibleEngine}).then(successCallback,errorCallback) | ||||
|   }; | ||||
| 
 | ||||
|   this.test = function(customModule,module_args,successCallback,errorCallback){ | ||||
|     $http.post(uri + '/' + customModule + '/test',{ansibleEngine:Projects.selectedProject.ansibleEngine,moduleArgs:module_args}).then(successCallback,errorCallback) | ||||
|   }; | ||||
| 
 | ||||
|   this.save = function(customModule,customModuleCode,successCallback,errorCallback){ | ||||
|     $http.post(uri + '/' + customModule,{ansibleEngine:Projects.selectedProject.ansibleEngine,custom_module_code:customModuleCode}).then(successCallback,errorCallback) | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.custom_modules_service', []) | ||||
|   .service('customModules', customModulesService) | ||||
|   .name; | ||||
							
								
								
									
										16
									
								
								client/app/custom_modules/custom_modules.service.spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								client/app/custom_modules/custom_modules.service.spec.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Service: customModules', function() { | ||||
|   // load the service's module
 | ||||
|   beforeEach(module('webAppApp.custom_modules')); | ||||
| 
 | ||||
|   // instantiate service
 | ||||
|   var customModules; | ||||
|   beforeEach(inject(function(_customModules_) { | ||||
|     customModules = _customModules_; | ||||
|   })); | ||||
| 
 | ||||
|   it('should do something', function() { | ||||
|     expect(!!customModules).to.be.true; | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										280
									
								
								client/app/custom_modules/new_module/new_module.controller.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								client/app/custom_modules/new_module/new_module.controller.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,280 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function newModuleController($scope,$filter,customModules,ansible,YAML) { | ||||
| 
 | ||||
|   $scope.optionTypes = ['str','list','dict','bool','int','float','path','raw','jsonarg','json','bytes','bits']; | ||||
| 
 | ||||
|   var defaultModule = { | ||||
|     module:null, | ||||
|     short_description:"", | ||||
|     description:"", | ||||
|     version_added:"", | ||||
|     author:"", | ||||
|     notes: "", | ||||
|     requirements: "", | ||||
|     options:[ | ||||
|       { | ||||
|         name:"parameter1", | ||||
|         description: 'Description of parameter 1', | ||||
|         required: true, | ||||
|         default: null, | ||||
|         choices: '"choice1", "choice2"', | ||||
|         aliases: '"option1", "argument1"', | ||||
|         type: "" | ||||
|       } | ||||
|     ] | ||||
|   }; | ||||
| 
 | ||||
| 
 | ||||
|   $scope.loadDefaultTemplate = function(){ | ||||
|     $scope.newModule = angular.copy(defaultModule); | ||||
|     $scope.selectedModule.module_code = "Loading Template.."; | ||||
| 
 | ||||
|     customModules.show('template.py',function(response) { | ||||
|       $scope.selectedModule.module_code = response.data.split("Stream :: close")[0]; | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   $scope.$watch('newModule',function(newValue,oldValue){ | ||||
| 
 | ||||
|     updateDocumentation(newValue); | ||||
|     updateParameters(newValue); | ||||
|     updateExamples(newValue); | ||||
| 
 | ||||
|   },true); | ||||
| 
 | ||||
|   var updateParameters = function(newValue){ | ||||
|     newValue = angular.copy(newValue); | ||||
| 
 | ||||
|     var parameters_definition_lines = []; | ||||
|     var parameters_retreive_lines = []; | ||||
|     angular.forEach(newValue.options,function(option){ | ||||
|       if(option.name) { | ||||
|         var line = option.name + "=dict("; | ||||
| 
 | ||||
|         var line_arguments = []; | ||||
|         if (option.required)line_arguments.push("required=True"); | ||||
|         if (!option.required && option.default)line_arguments.push("default='" + option.default + "'"); | ||||
|         if (option.type)line_arguments.push("type='" + option.type + "'"); | ||||
|         if (option.choices)line_arguments.push("choices=[" + option.choices + "]"); | ||||
|         if (option.aliases)line_arguments.push("aliases=[" + option.aliases + "]"); | ||||
| 
 | ||||
|         line += line_arguments.join(","); | ||||
|         line += ")"; | ||||
| 
 | ||||
|         parameters_definition_lines.push(line); | ||||
|         parameters_retreive_lines.push(option.name + ' = module.params[\'' + option.name + '\']') | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     var parameters_definition_string = parameters_definition_lines.join(",\n            "); | ||||
|     var parameters_retreive_string = parameters_retreive_lines.join("\n    "); | ||||
| 
 | ||||
|     var re = /(# <--Begin Parameter Definition -->\s+            )([^]+)(\s+            # <--END Parameter Definition -->)/; | ||||
|     $scope.selectedModule.module_code = $scope.selectedModule.module_code.replace(re,"$1" + parameters_definition_string + "$3"); | ||||
| 
 | ||||
|     var supports_check_mode_string = '\n'; | ||||
|     if(newValue.supports_check_mode){ | ||||
|       supports_check_mode_string = '\n        supports_check_mode=True\n' | ||||
|     } | ||||
| 
 | ||||
|     var re2 = /(# <--Begin Supports Check Mode -->)([^]+)(        # <--End Supports Check Mode -->)/; | ||||
|     $scope.selectedModule.module_code = $scope.selectedModule.module_code.replace(re2,"$1" + supports_check_mode_string + "$3"); | ||||
| 
 | ||||
|     var re3 = /(# <--Begin Retreiving Parameters  -->\s+    )([^]+)(\s+    # <--End Retreiving Parameters  -->)/; | ||||
|     $scope.selectedModule.module_code = $scope.selectedModule.module_code.replace(re3,"$1" + parameters_retreive_string + "$3"); | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
|   var updateDocumentation = function(newValue){ | ||||
|     newValue = angular.copy(newValue); | ||||
|     newValue.options = convertOptionsToObject(newValue.options); | ||||
| 
 | ||||
|     delete newValue['supports_check_mode']; | ||||
| 
 | ||||
|     if(newValue.description) | ||||
|       newValue.description = newValue.description.split(";"); | ||||
| 
 | ||||
|     if(newValue.notes) | ||||
|       newValue.notes = newValue.notes.split(";"); | ||||
| 
 | ||||
|     if(newValue.requirements) | ||||
|       newValue.requirements = newValue.requirements.split(";"); | ||||
| 
 | ||||
|     $scope.documentation_yaml = '---\n' + $filter('json2yaml')(angular.toJson(newValue)).toString().replace(/__dot__/g,"."); | ||||
| 
 | ||||
|     //var re = /(.*DOCUMENTATION = '''\n)([^]+?)(\n'''.*)/;
 | ||||
|     var re = /([^]+DOCUMENTATION = '''\s+)([^]+?)(\s+'''[^]+)/; | ||||
|     $scope.selectedModule.module_code = $scope.selectedModule.module_code.replace(re,'$1' + $scope.documentation_yaml + '$3'); | ||||
|   }; | ||||
| 
 | ||||
|   var updateExamples = function(newValue){ | ||||
|     newValue = angular.copy(newValue); | ||||
| 
 | ||||
|     var moduleCopy = { | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     moduleCopy[newValue.module] = convertOptionsToExampleObject(newValue.options); | ||||
| 
 | ||||
|     $scope.example_yaml = YAML.stringify(moduleCopy,4); | ||||
| 
 | ||||
|     //var re = /(.*DOCUMENTATION = '''\n)([^]+?)(\n'''.*)/;
 | ||||
|     var re = /([^]+EXAMPLES = '''[^]+# <--  -->\s+)([^]+?)(\s+# <-- \/ -->\s+'''[^]+)/; | ||||
|     $scope.selectedModule.module_code = $scope.selectedModule.module_code.replace(re,'$1' + $scope.example_yaml + '$3'); | ||||
|   }; | ||||
| 
 | ||||
|   var convertOptionsToObject = function(options){ | ||||
| 
 | ||||
|     var result = {}; | ||||
| 
 | ||||
|     angular.forEach(options,function(option){ | ||||
|       if(option.name){ | ||||
|         result[option.name] = { | ||||
|           description: option.description | ||||
|         }; | ||||
| 
 | ||||
|         if(option.required) | ||||
|           result[option.name]['required'] = "True"; | ||||
|         else | ||||
|           delete result[option.name]['required']; | ||||
| 
 | ||||
|         if(!option.required && option.default) | ||||
|           result[option.name]['default'] = option.default; | ||||
| 
 | ||||
|         if(option.choices){ | ||||
|           result[option.name]['choices'] = "[" + option.choices + "]" | ||||
|         } | ||||
| 
 | ||||
|         if(option.aliases){ | ||||
|           result[option.name]['aliases'] = "[" + option.aliases + "]" | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|     }); | ||||
| 
 | ||||
|     return result | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
|   var convertOptionsToExampleObject = function(options){ | ||||
| 
 | ||||
|     var result = {}; | ||||
| 
 | ||||
|     angular.forEach(options,function(option){ | ||||
|       if(option.name){ | ||||
|         result[option.name] = "value"; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     return result | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
|   var convertOptionsToArrays = function(options){ | ||||
| 
 | ||||
|     var result = []; | ||||
| 
 | ||||
|     angular.forEach(options,function(value,key){ | ||||
|       var option = { | ||||
|         name: key, | ||||
|         description: value.description, | ||||
|         required: value.required, | ||||
|         default:value.default | ||||
|       }; | ||||
| 
 | ||||
|       if(value.choices && value.choices.length) | ||||
|         option['choices'] = value.choices.map(function(item){return ('"' + item + '"')}).join(",") | ||||
| 
 | ||||
|       if(value.aliases && value.aliases.length) | ||||
|         option['aliases'] = value.aliases.map(function(item){return ('"' + item + '"')}).join(",") | ||||
| 
 | ||||
|       result.push(option) | ||||
|     }); | ||||
| 
 | ||||
|     return result | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
|   $scope.saveNewModule = function(){ | ||||
|     $scope.saving = true; | ||||
|     customModules.save($scope.newModule.module + '.py',$scope.selectedModule.module_code,function(response){ | ||||
|       $scope.saving = false; | ||||
|       $scope.getCustomModules(); | ||||
| 
 | ||||
|       ansible.getAnsibleModules(function(response){ | ||||
| 
 | ||||
|       }, function(response){ | ||||
| 
 | ||||
|       },null,true); | ||||
|       $scope.cancelNewModule(); | ||||
|     },function(response){ | ||||
|       $scope.saving = false; | ||||
|       console.error(response.data) | ||||
|     }) | ||||
|   }; | ||||
| 
 | ||||
|   $scope.cancelNewModule = function(){ | ||||
|     $scope.showNewModuleForm.value = false; | ||||
|     $scope.$parent.showModuleCode($scope.selectedModule.module.name) | ||||
|   }; | ||||
| 
 | ||||
|   var getPropertiesFromCode = function(module_code){ | ||||
| 
 | ||||
|     //var re = /([^]+DOCUMENTATION = '''\n)([^]+?)(\n'''[^]+)/;
 | ||||
|     var re = /([^]+DOCUMENTATION = '''\s+)([^]+?)(\s+'''[^]+)/; | ||||
|     var module_string = $scope.selectedModule.module_code.replace(re,'$2'); | ||||
| 
 | ||||
|     $scope.newModule = YAML.parse(module_string); | ||||
|     $scope.newModule.options = convertOptionsToArrays($scope.newModule.options); | ||||
| 
 | ||||
|     if($scope.newModule.description && $scope.newModule.description.length) | ||||
|       $scope.newModule.description = $scope.newModule.description.join(";"); | ||||
| 
 | ||||
|     if($scope.newModule.notes && $scope.newModule.notes.length) | ||||
|       $scope.newModule.notes = $scope.newModule.notes.join(";"); | ||||
| 
 | ||||
|     if($scope.newModule.requirements && $scope.newModule.requirements.length) | ||||
|       $scope.newModule.requirements = $scope.newModule.requirements.join(";"); | ||||
| 
 | ||||
| 
 | ||||
|     re = /([^]+# <--Begin Parameter Definition -->\s+            )([^]+)(\s+            # <--END Parameter Definition -->[^]+)/; | ||||
|     var parameter_string = $scope.selectedModule.module_code.replace(re,"$2"); | ||||
| 
 | ||||
|     // Read property type form parameter definition
 | ||||
|     re = /\s+(.*?)=.*type=(.*?)[,\)].*/g; | ||||
|     var m; | ||||
| 
 | ||||
|     while ((m = re.exec(parameter_string)) !== null) { | ||||
|       if (m.index === re.lastIndex) { | ||||
|         re.lastIndex++; | ||||
|       } | ||||
|       // View your result using the m-variable.
 | ||||
|       // eg m[0] etc.
 | ||||
|       if(m[1]){ | ||||
|         angular.forEach($scope.newModule.options,function(option){ | ||||
|           if(option.name === m[1]){ | ||||
|             option.type = m[2].replace(/'/g,'') | ||||
|           } | ||||
|         }) | ||||
|       } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
|   $scope.$on('editModule', function(e) { | ||||
|     getPropertiesFromCode($scope.selected_module_code) | ||||
|   }); | ||||
| 
 | ||||
|   $scope.$on('newModule', function(e) { | ||||
|     $scope.loadDefaultTemplate(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.new_module', []) | ||||
|   .controller('NewModuleController', newModuleController) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: NewModuleCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.new_module')); | ||||
| 
 | ||||
|   var NewModuleCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     NewModuleCtrl = $controller('NewModuleCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										144
									
								
								client/app/custom_modules/new_module/new_module.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								client/app/custom_modules/new_module/new_module.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,144 @@ | |||
| <div class="row" ng-controller="NewModuleController"> | ||||
| 
 | ||||
|     <div class="panel"> | ||||
|       <div class="panel panel-primary"> | ||||
|         <div class="panel-heading">New Module</div> | ||||
|         <div class="panel-body"> | ||||
|           <div class="row"> | ||||
|             <div class="col-md-4"> | ||||
|               <p class="form-group"> | ||||
|                 <label>Module Name</label> | ||||
|                 <input type="text" ng-model="newModule.module" class="form-control" ng-required="true" placeholder="modulename"> | ||||
|               </p> | ||||
|             </div> | ||||
|             <div class="col-md-2"> | ||||
|               <p class="form-group"> | ||||
|                 <label>Version Added</label> | ||||
|                 <input type="text" ng-model="newModule.version_added" class="form-control" ng-required="true" placeholder="X.Y"> | ||||
|               </p> | ||||
|             </div> | ||||
|             <div class="col-md-2"> | ||||
|               <p class="form-group"> | ||||
|                 <label>Check Mode</label> | ||||
|                 <label class="checkbox-inline"><input ng-model="newModule.supports_check_mode" type="checkbox"></label> | ||||
|               </p> | ||||
|             </div> | ||||
|             <div class="col-md-4"> | ||||
|               <p class="form-group"> | ||||
|                 <label>Author</label> | ||||
|                 <input type="text" ng-model="newModule.author" class="form-control" ng-required="true" placeholder="Your AWESOME name, @awesome-github-id"> | ||||
|               </p> | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="row"> | ||||
|             <div class="col-md-4"> | ||||
|               <p class="form-group"> | ||||
|                 <label>Short Description</label> | ||||
|                 <input type="text" ng-model="newModule.short_description" class="form-control" ng-required="true" placeholder="This is a sentence describing the module"> | ||||
|               </p> | ||||
|             </div> | ||||
|             <div class="col-md-8"> | ||||
|               <p class="form-group"> | ||||
|                 <label>Description</label> | ||||
|                 <input type="text" ng-model="newModule.description" class="form-control" ng-required="true" placeholder="Longer description of the module; You might include instructions"> | ||||
|               </p> | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
| 
 | ||||
|           <p class="form-group"> | ||||
|             <label>Notes</label> | ||||
|             <input type="text" ng-model="newModule.notes" class="form-control" ng-required="true" placeholder="Other things consumers of your module should know"> | ||||
|           </p> | ||||
| 
 | ||||
|           <p class="form-group"> | ||||
|             <label>Requirements</label> | ||||
|             <input type="text" ng-model="newModule.requirements" class="form-control" ng-required="true" placeholder="List of required things separated by semicolon; like the factor package; or a specific platform"> | ||||
|           </p> | ||||
| 
 | ||||
|           <div class="panel"> | ||||
|             <div class="panel panel-default"> | ||||
|               <div class="panel-heading">Options</div> | ||||
|               <div class="panel-body"> | ||||
|                 <div class="row"> | ||||
|                   <div class="col-md-2" style="text-align: center"> | ||||
|                     <label>Name</label> | ||||
|                   </div> | ||||
|                   <div class="col-md-2" style="text-align: center"> | ||||
|                     <label>Description</label> | ||||
|                   </div> | ||||
|                   <div class="col-md-1" style="text-align: center"> | ||||
|                     <label>Type</label> | ||||
|                   </div> | ||||
|                   <div class="col-md-1" style="text-align: center"> | ||||
|                     <label>Required</label> | ||||
|                   </div> | ||||
|                   <div class="col-md-1" style="text-align: center"> | ||||
|                     <label>Default</label> | ||||
|                   </div> | ||||
|                   <div class="col-md-2" style="text-align: center"> | ||||
|                     <label>Aliases</label> | ||||
|                   </div> | ||||
|                   <div class="col-md-2" style="text-align: center"> | ||||
|                     <label>Choices</label> | ||||
|                   </div> | ||||
|                   <div class="col-md-1" style="text-align: center"> | ||||
|                     <label>Actions</label> | ||||
|                   </div> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <div ng-repeat="option in newModule.options"> | ||||
| 
 | ||||
|                   <div class="row"> | ||||
|                     <div class="col-md-2" style="padding:2px;"> | ||||
|                       <input type="text" ng-model="option.name" class="form-control"> | ||||
|                     </div> | ||||
|                     <div class="col-md-2" style="padding:2px;"> | ||||
|                       <!--<input type="text" ng-model="member.value">--> | ||||
|                       <input type="text" ng-model="option.description" class="form-control"> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="col-md-1" style="padding:2px;"> | ||||
|                       <select class="form-control" ng-model="option.type" ng-options="optionType for optionType in optionTypes"> | ||||
|                       </select> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="col-md-1" style="padding:2px;text-align: center"> | ||||
|                       <label class="checkbox-inline"><input ng-model="option.required" type="checkbox" value=""></label> | ||||
|                     </div> | ||||
|                     <div class="col-md-1" style="padding:2px;"> | ||||
|                       <input type="text" ng-disabled="option.required" ng-model="option.default" class="form-control"> | ||||
|                     </div> | ||||
|                     <div class="col-md-2" style="padding:2px;"> | ||||
|                       <input type="text" ng-model="option.aliases" class="form-control"> | ||||
|                     </div> | ||||
|                     <div class="col-md-2" style="padding:2px;"> | ||||
|                       <input type="text" ng-model="option.choices" class="form-control"> | ||||
|                     </div> | ||||
|                     <div class="col-md-1" style="padding:2px;text-align: center;"> | ||||
|                       <div class="btn-group"> | ||||
|                         <label class="btn btn-default" uib-tooltip="Insert new row after" ng-click="newModule.options.splice($index+1,0,{})" style="padding-top: 3px;padding-bottom: 3px;padding-left:5px;padding-right:5px;"><span class="fa fa-plus"></span></label> | ||||
|                         <label class="btn btn-danger" uib-tooltip="Delete row" ng-click="newModule.options.splice($index,1)" style="padding-top: 3px;padding-bottom: 3px; padding-left:5px; padding-right:5px;"><span class="fa fa-minus"></span></label> | ||||
|                       </div> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                   <!--<input type="text" ng-model="member.value" uib-typeahead="('"\{\{' + var.name + '\}\}"') as (var.name + ' = ' + var.value) for var in hostvars | filter: $viewValue"  class="form-control">--> | ||||
|                 </div> | ||||
|                 <!--<button class="btn btn-danger" ng-click="deleteProject()" confirm="Are you sure you want to delete?">Delete <span ng-if="!deleteProjectLoading" class="fa fa-trash"></span> <span ng-if="deleteProjectLoading" class="fa fa-spinner fa-spin"></span></button>--> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
| 
 | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
| 
 | ||||
|       <button class="btn btn-primary" ng-disabled="!newModule.module" ng-click="saveNewModule()"> Save <span ng-if="!saving" class="fa fa-save"></span> <span ng-if="saving" class="fa fa-spin fa-spinner"></span> </button> | ||||
|       <button class="btn btn-default" confirm="Are you sure you want to discard the changes?" ng-click="cancelNewModule()"> Cancel <span class="fa fa-times"></span> </button> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
| </div> | ||||
							
								
								
									
										89
									
								
								client/app/designer/designer.component.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								client/app/designer/designer.component.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,89 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| const uiRouter = require('angular-ui-router'); | ||||
| 
 | ||||
| import routes from './designer.routes'; | ||||
| 
 | ||||
| export class DesignerComponent { | ||||
|   /*@ngInject*/ | ||||
|   constructor($scope,Projects,ansible) { | ||||
|     'ngInject'; | ||||
|     $scope.selectedInventoryFileName = null; | ||||
| 
 | ||||
|     /** | ||||
|      * Get list of projects from server | ||||
|      */ | ||||
|     $scope.getProjects = function(){ | ||||
|       $scope.projects = Projects.resource.query(function(){ | ||||
|         if($scope.projects.length){ | ||||
|           $scope.selectedProjectID = localStorage.selectedProjectID || $scope.projects[0]._id; | ||||
|           $scope.projectSelected($scope.selectedProjectID) | ||||
|         } | ||||
| 
 | ||||
|       }) | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * On ProjectSelected - set selectedProjectID in cache | ||||
|      * @param projectID | ||||
|      */ | ||||
|     $scope.projectSelected = function(projectID){ | ||||
|       localStorage.selectedProjectID = projectID; | ||||
| 
 | ||||
|       $scope.selectedProject = Projects.resource.get({id: projectID},function(){ | ||||
|         Projects.selectedProject = $scope.selectedProject; | ||||
|         $scope.listOfInventoryFiles(); | ||||
|         $scope.$broadcast('projectLoaded'); | ||||
|       }) | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Get List of inventory files in project root folder | ||||
|      */ | ||||
|     $scope.listOfInventoryFiles = function(){ | ||||
| 
 | ||||
|       var rolesTestFolder = null; | ||||
| 
 | ||||
|       /*if(roleName){ | ||||
|        rolesTestFolder = projectFolder + '/' + roleName + '/tests' | ||||
|        }*/ | ||||
| 
 | ||||
|       ansible.getInventoryList(function(response){ | ||||
|           $scope.inventoryFiles = response.data; | ||||
|           console.log($scope.inventoryFiles); | ||||
|           Projects.selectedInventoryFileName = localStorage.selectedInventoryFileName || $scope.inventoryFiles[0]; | ||||
|           localStorage.selectedInventoryFileName = $scope.inventoryFiles[0]; | ||||
|           $scope.selectedInventoryFileName = localStorage.selectedInventoryFileName | ||||
|         }, | ||||
|         function(response){ | ||||
|           $scope.err_msg = response.data | ||||
|         },rolesTestFolder) | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * Set selected inventory file in local cache. | ||||
|      * @param selectedInventoryFileName - Selected inventory file name | ||||
|      */ | ||||
|     $scope.inventoryFileSelected = function(selectedInventoryFileName){ | ||||
|       localStorage.selectedInventoryFileName = selectedInventoryFileName; | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * Main - Get Projects | ||||
|      */ | ||||
| 
 | ||||
|     $scope.getProjects(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.designer', [uiRouter]) | ||||
|   .config(routes) | ||||
|   .component('designer', { | ||||
|     template: require('./designer.html'), | ||||
|     controller: DesignerComponent, | ||||
|     controllerAs: 'designerCtrl' | ||||
|   }) | ||||
|   .name; | ||||
							
								
								
									
										17
									
								
								client/app/designer/designer.component.spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								client/app/designer/designer.component.spec.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Component: DesignerComponent', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.designer')); | ||||
| 
 | ||||
|   var DesignerComponent; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($componentController) { | ||||
|     DesignerComponent = $componentController('designer', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										10
									
								
								client/app/designer/designer.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								client/app/designer/designer.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| .logconsole { | ||||
|   font:12px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas'; | ||||
|   color: #fff; | ||||
|   padding:5px; | ||||
|   margin:5px; | ||||
|   font-size:14px; | ||||
|   background: black; | ||||
| } | ||||
| 
 | ||||
| .ace_editor { height: 800px; } | ||||
							
								
								
									
										55
									
								
								client/app/designer/designer.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								client/app/designer/designer.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| <div style="padding:15px;"> | ||||
|   <div class="row"> | ||||
|     <div class="col-md-2"> | ||||
|       <div class="panel"> | ||||
|         <div class="panel panel-default"> | ||||
|           <div class="panel-heading">Projects</div> | ||||
|           <div class="panel-body"> | ||||
|             <select class="form-control" ng-model="selectedProjectID" ng-change="projectSelected(selectedProjectID)" ng-options="project._id as project.name for project in projects"> | ||||
|             </select> | ||||
|             <div ng-if="selectedProject.ansibleVersion" class="hint"> | ||||
|               Ansible Version:{{selectedProject.ansibleVersion}} | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="panel"> | ||||
|         <div class="panel panel-default"> | ||||
|           <div class="panel-heading">Inventory Files</div> | ||||
|           <div class="panel-body"> | ||||
|             <select class="form-control" ng-model="selectedInventoryFileName" ng-options="inventoryFile as inventoryFile for inventoryFile in inventoryFiles" ng-change="inventoryFileSelected(selectedInventoryFileName)"> | ||||
|             </select> | ||||
|             <div ng-if="selectedProject.ansibleVersion" class="hint"> | ||||
|               An inventory file to work with | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="panel"> | ||||
|         <div class="panel panel-default"> | ||||
|           <div class="panel-heading">Menu</div> | ||||
|           <div class="panel-body"> | ||||
| 
 | ||||
|             <div class="btn-group-vertical" role="group" aria-label="..."> | ||||
|               <button class="btn btn-default" ui-sref="designer.inventory">Inventory</button> | ||||
|               <button class="btn btn-default" ui-sref="designer.playbook">Playbooks</button> | ||||
|               <button class="btn btn-default" ui-sref="designer.roles">Roles</button> | ||||
|               <button class="btn btn-default" ui-sref="designer.file_browser">File Browser</button> | ||||
|             </div> | ||||
| 
 | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="alert alert-danger" ng-if="err_msg">{{err_msg}}</div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="col-md-10"> | ||||
| 
 | ||||
|       <div ui-view></div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										10
									
								
								client/app/designer/designer.routes.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								client/app/designer/designer.routes.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export default function($stateProvider) { | ||||
|   'ngInject'; | ||||
|   $stateProvider | ||||
|     .state('designer', { | ||||
|       url: '/designer', | ||||
|       template: '<designer></designer>' | ||||
|     }); | ||||
| } | ||||
							
								
								
									
										185
									
								
								client/app/designer/execution/executeModal.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								client/app/designer/execution/executeModal.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,185 @@ | |||
| <!-- Modal content--> | ||||
| <div class="modal-content"> | ||||
|   <div class="modal-header"> | ||||
|     <button type="button" class="close" data-dismiss="modal" ng-click="cancel()">×</button> | ||||
|     <h4 class="modal-title">Play</h4> | ||||
|   </div> | ||||
|   <div class="modal-body"> | ||||
|     <div class="row" id="PlaybookExecutionModal"> | ||||
|       <!--<button class="btn btn-default" ng-click="view = (view === 'console' ? 'table' : 'console')">Toggle View</button>--> | ||||
| 
 | ||||
|       <div style="background:black;color:lightgrey;width:100%;padding:20px;" ng-if="result" ng-show="view=='console'"> | ||||
|         <p class="logconsole" ng-bind-html="result"></p> | ||||
|       </div> | ||||
| 
 | ||||
|       <!--<div ng-if="ansibleOutputObject" ng-show="view=='table'" style="margin-top:20px;"> | ||||
| 
 | ||||
|         <!–<div class="row" ng-repeat="resultItem in ansibleOutputResult"> | ||||
|           <div class="col-md-1"><div class="btn btn-default">{{resultItem.type}}</div></div> | ||||
|           <div class="col-md-5"><div class="btn btn-primary">{{resultItem.name}}</div></div> | ||||
|           <div class="col-md-6"><div class="btn btn-info" ng-if="resultItem.host">{{resultItem.host}}</div></div> | ||||
|           <div class="col-md-offset-1 col-md-2"><div class="btn " ng-class="{'btn-success': resultItem.status == 'ok' || resultItem.status == 'changed', 'btn-danger': resultItem.status == 'fatal', 'btn-warning': resultItem.status == 'skipping'}" ng-if="resultItem.status">Status = {{resultItem.status}}</div></div> | ||||
|           <div class="col-md-3"><div class="btn btn-info" ng-if="resultItem.status_2.trim()">Status2 = {{resultItem.status_2}}</div></div> | ||||
|           <div class="col-md-3"><div class="btn" ng-if="resultItem.resultObject.changed.trim()" ng-class="{'btn-success': resultItem.resultObject.changed == true, 'btn-warning': resultItem.resultObject.changed == false}">Changed = {{resultItem.resultObject.changed}}</div></div> | ||||
|           <div class="col-md-3"><div class="btn" ng-if="resultItem.resultObject.failed" ng-class="{'btn-danger': resultItem.resultObject.failed}">Failed = {{resultItem.resultObject.failed}}</div></div> | ||||
|           <div class="col-md-offset-1 col-md-11" ng-if="resultItem.resultObject.msg" ><p style="background:grey;" class="logconsole">{{resultItem.resultObject.msg}}</p></div> | ||||
|           <div class="col-md-offset-1 col-md-11" ng-if="resultItem.resultObject.formattedStdErr" ><p style="color:#cd616b;" class="logconsole" ng-bind-html="resultItem.resultObject.formattedStdErr"></p></div> | ||||
|           <div class="col-md-offset-1 col-md-11" ng-if="resultItem.resultObject.formattedStdOut" ><p style="color:green;" class="logconsole" ng-bind-html="resultItem.resultObject.formattedStdOut"></p></div> | ||||
|           <!–<div class="col-md-offset-1 col-md-11"><p class="logconsole">{{resultItem.resultString}}</p></div>–> | ||||
|           <span class="fa fa-spinner fa-spin" ng-if="AnsiblePlayBookLoading && $last"></span> | ||||
|         </div>–> | ||||
| 
 | ||||
|         <!–<div ng-repeat="(host, stats) in ansibleOutputObject.stats"> | ||||
| 
 | ||||
|           <span><b>Host : </b>{{host}}</span> | ||||
|           <span class="btn btn-default btn-info">OK <span class="badge">{{stats.ok}}</span></span> | ||||
|           <span class="btn btn-default" ng-class="{'btn-success': stats.changed}" >Changed <span class="badge">{{stats.changed}}</span></span> | ||||
|           <span class="btn btn-default" ng-class="{'btn-warning': stats.skipped}">Skipped <span class="badge">{{stats.skipped}}</span></span> | ||||
|           <span class="btn btn-default" ng-class="{'btn-danger': stats.unreachable}">Unreachable <span class="badge">{{stats.unreachable}}</span></span> | ||||
|           <span class="btn btn-default" ng-class="{'btn-danger': stats.failures}">Failures <span class="badge">{{stats.failures}}</span></span> | ||||
| 
 | ||||
|         </div>–> | ||||
| 
 | ||||
|         <h3>Run Statistics</h3> | ||||
|         <div class="table-responsive"> | ||||
|           <table class="table"> | ||||
|             <thead> | ||||
|             <tr> | ||||
|               <th>Host</th> | ||||
|               <th>Ok</th> | ||||
|               <th>Changed</th> | ||||
|               <th>Skipped</th> | ||||
|               <th>Unreachable</th> | ||||
|               <th>Failures</th> | ||||
|             </tr> | ||||
|             </thead> | ||||
|             <tbody> | ||||
|             <tr ng-repeat="(host, stats) in ansibleOutputObject.stats"> | ||||
|               <td>{{host}}</td> | ||||
|               <td>{{stats.ok}}</td> | ||||
|               <td><span ng-class="{'label label-success': stats.changed}">{{stats.changed}}</span></td> | ||||
|               <td><span ng-class="{'label label-warning': stats.skipped}">{{stats.skipped}}</span></td> | ||||
|               <td><span ng-class="{'label label-danger': stats.unreachable}">{{stats.unreachable}}</span></td> | ||||
|               <td><span ng-class="{'label label-danger': stats.failures}">{{stats.failures}}</span></td> | ||||
|             </tr> | ||||
|             </tbody> | ||||
|           </table> | ||||
|         </div> | ||||
| 
 | ||||
| 
 | ||||
|         <div class="row" ng-repeat="playObject in ansibleOutputObject.plays"> | ||||
| 
 | ||||
|           <!–<div class="col-md-3"><div class="btn btn-default">Play - {{playObject.play.name}}</div></div>–> | ||||
| 
 | ||||
|           <div class="col-md-12"> | ||||
|             <uib-accordion close-others="false"> | ||||
|               <div uib-accordion-group class="panel-default" is-open="true" ng-repeat="taskObject in playObject.tasks"> | ||||
|                 <uib-accordion-heading> | ||||
|                   Play - {{playObject.play.name}} - Tasks - {{taskObject.task.name}} | ||||
|                   <span class="fa fa-spinner fa-spin" ng-if="AnsiblePlayBookLoading && $last"></span> | ||||
|                 </uib-accordion-heading> | ||||
| 
 | ||||
|                 <div class="col-md-12" ng-repeat="(hostName, hostObject) in taskObject.hosts"> | ||||
|                   <div class="col-md-3"><b>Host :</b> {{hostName}}</div> | ||||
|                   <div class="col-md-3"><b>Method :</b> {{hostObject.invocation && hostObject.invocation.module_name}}</div> | ||||
|                   <div class="col-md-2"><div class="btn" ng-class="{'btn-success': hostObject.changed == true, 'btn-warning': hostObject.changed == false}" style="padding: 2px 5px">Changed <span class="badge">{{hostObject.changed}}</span></div></div> | ||||
|                   <div class="col-md-2" ng-if="hostObject.skipped == true"><div class="btn" ng-class="{'btn-warning': hostObject.skipped == true}" style="padding: 2px 5px">Skipped <span class="badge">{{hostObject.skipped}}</span></div></div> | ||||
|                   <div class="col-md-1"><div class="btn btn-default" ng-show="hostObject.module_stderr || hostObject.stdout || hostObject.stderr || hostObject.msg" ng-click="hostObject.showLogs = !hostObject.showLogs" style="padding: 2px 5px">Logs</div></div> | ||||
|                   <div class="col-md-1"><div class="btn btn-warning" ng-show="hostObject.warnings.length" ng-click="hostObject.showWarnings = !hostObject.showWarnings" style="padding: 2px 5px">Warnings</div></div> | ||||
| 
 | ||||
|                   <div class="col-md-2"><div class="btn btn-danger" ng-show="hostObject.rc && hostObject.rc != 0">Return Code - {{hostObject.rc}}</div></div> | ||||
| 
 | ||||
|                   <div ng-show="hostObject.showLogs" class="col-md-12" ng-if="hostObject.stdout" ><p style="color:grey;" class="logconsole" ng-bind-html="hostObject.stdout | replaceLineBreaks"></p></div> | ||||
|                   <div ng-show="hostObject.showLogs" class="col-md-12" ng-if="hostObject.msg" ><p style="color:grey;" class="logconsole" ng-bind-html="hostObject.msg | replaceLineBreaks"></p></div> | ||||
|                   <div ng-show="hostObject.showLogs" class="col-md-12" ng-if="hostObject.stderr" ><p style="color:#cd616b;" class="logconsole" ng-bind-html="hostObject.stderr | replaceLineBreaks"></p></div> | ||||
|                   <div ng-show="hostObject.showLogs" class="col-md-12" ng-if="hostObject.module_stderr" ><p style="color:#cd616b;" class="logconsole" ng-bind-html="hostObject.module_stderr | replaceLineBreaks"></p></div> | ||||
|                   <div ng-show="hostObject.showWarnings" class="col-md-12" ng-if="hostObject.warnings.length" ><p style="color:orangered;" class="logconsole" ng-bind-html="hostObject.warnings.join('\n') | replaceLineBreaks"></p></div> | ||||
| 
 | ||||
|                 </div> | ||||
| 
 | ||||
|               </div> | ||||
|             </uib-accordion> | ||||
|           </div> | ||||
| 
 | ||||
|         </div> | ||||
| 
 | ||||
| 
 | ||||
|       </div>--> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="row" ng-if="!readOnly"> | ||||
|       <div class="col-md-3"> | ||||
|         <div class="input-group"> | ||||
|           <span class="input-group-addon" >Inventory Files</span> | ||||
|           <!--<input ng-model="newPlay.hosts" type="text" class="form-control" placeholder="Playbook Name">--> | ||||
|           <select class="form-control" ng-model="selectedInventoryFile.value" ng-options="inventoryFile as inventoryFile for inventoryFile in inventoryFiles" ng-disabled="!inventoryFiles"> | ||||
|           </select> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="col-md-9"> | ||||
|         <btn class="checkbox-inline"><input type="checkbox" ng-true-value="'Check'" ng-false-value="'No_Check'" ng-model="check_mode.value">Check Mode</btn> | ||||
|         <btn class="checkbox-inline"><input type="checkbox" ng-true-value="'verbose'" ng-model="verbose.value">Verbose</btn> | ||||
|         <btn class="checkbox-inline"><input type="checkbox" ng-true-value="'verbose_detail'" ng-model="verbose_detail.value">Verbose Detail</btn> | ||||
|         <btn class="checkbox-inline"><input type="checkbox" ng-model="refreshLog">Refresh Logs</btn> | ||||
|         <btn class="checkbox-inline"><input type="checkbox" ng-model="additional_tags.show">Tags</btn> | ||||
|         <btn class="checkbox-inline"><input type="checkbox" ng-model="additional_tags.vars">Vars</btn> | ||||
|       </div> | ||||
|       <div class="clearfix"></div> | ||||
|       <div class="col-md-6" ng-show="additional_tags.show"> | ||||
|         <div class="table-responsive"> | ||||
|           <table class="table"> | ||||
|             <thead> | ||||
|             <tr> | ||||
|               <th>Select</th> | ||||
|               <th>Tag</th> | ||||
|             </tr> | ||||
|             </thead> | ||||
|             <tbody> | ||||
|             <tr ng-repeat="tag in all_tags"> | ||||
|               <td><input type="checkbox" ng-model="tag.selected"> | ||||
|               </td> | ||||
|               <td>{{tag.name}}</td> | ||||
| 
 | ||||
|             </tr> | ||||
|             </tbody> | ||||
|           </table> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="col-md-6" ng-show="additional_tags.show"> | ||||
|         <div class="table-responsive"> | ||||
|           <table class="table"> | ||||
|             <thead> | ||||
|             <tr> | ||||
|               <th>Select</th> | ||||
|               <th>Host</th> | ||||
|             </tr> | ||||
|             </thead> | ||||
|             <tbody> | ||||
|             <tr ng-repeat="host in all_hosts"> | ||||
|               <td><input type="checkbox" ng-model="host.selected"> | ||||
|               </td> | ||||
|               <td>{{host.name}}</td> | ||||
| 
 | ||||
|             </tr> | ||||
|             </tbody> | ||||
|           </table> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div ng-show="additional_vars.show"> | ||||
|         <complex-var members="complexVar" path="'Parent'" type="'object'" input-width="'400px'"> | ||||
|         </complex-var> | ||||
|       </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|   </div> | ||||
|   <div class="alert alert-danger" ng-if="err_msg">{{err_msg}}</div> | ||||
|   <div class="modal-footer"> | ||||
|     <button class="btn btn-success" type="button" ng-if="!readOnly" ng-disabled="!selectedInventoryFile.value || !readyForPlay" ng-click="Run()">Play <span class="fa fa-play" ng-if="!AnsiblePlayBookLoading"></span> <span class="fa fa-spinner fa-spin" ng-if="AnsiblePlayBookLoading"></span></button> | ||||
|     <button class="btn btn-default" type="button" ng-click="cancel()">Close</button> | ||||
|   </div> | ||||
| </div> | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										475
									
								
								client/app/designer/execution/execution.controller.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										475
									
								
								client/app/designer/execution/execution.controller.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,475 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function executionController($scope,$sce, $uibModalInstance, $timeout, ansi2html, ansible, tags, selectedProject, selectedPlaybook, selectedPlay, executionType, executionName, readOnly, runData, projectFolder, roleName) { | ||||
|   'ngInject'; | ||||
|   $scope.view = 'console'; | ||||
|   $scope.selectedInventoryFile = {value:null}; | ||||
|   $scope.verbose_detail = {value:null}; | ||||
|   $scope.verbose = {value:'verbose'}; | ||||
|   $scope.check_mode = { | ||||
|     value: 'No_Check' | ||||
|   }; | ||||
|   $scope.additional_tags = {show: false}; | ||||
|   $scope.additional_vars = {show: false}; | ||||
|   $scope.refreshLog = true; | ||||
|   $scope.all_tags = []; | ||||
|   $scope.all_hosts = []; | ||||
|   $scope.readOnly = readOnly; | ||||
|   $scope.readyForPlay = false; | ||||
| 
 | ||||
|   /** | ||||
|    * Execute Ansible Playbook | ||||
|    */ | ||||
|   $scope.executeAnsiblePlayBook = function(){ | ||||
|     $scope.AnsiblePlayBookLoading = true; | ||||
|     var reqBody = {}; | ||||
|     //reqBody.inventory_file_contents = inventory_file_contents;
 | ||||
|     //reqBody.playbook_file_contents = yaml;
 | ||||
|     //reqBody.tags = tags || [];
 | ||||
|     reqBody.tags = []; | ||||
| 
 | ||||
|     $scope.all_tags.map(tag => { | ||||
|       if(tag.selected){ | ||||
|       if(tag.name) | ||||
|         reqBody.tags.push(tag.name.trim()) | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|     reqBody.limit_to_hosts = []; | ||||
| 
 | ||||
|     $scope.all_hosts.map(host => { | ||||
|       if(host.selected){ | ||||
|       if(host.name) | ||||
|         reqBody.limit_to_hosts.push(host.name.trim()) | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|     reqBody.verbose = $scope.verbose_detail.value || $scope.verbose.value; | ||||
|     reqBody.check_mode = $scope.check_mode.value; | ||||
| 
 | ||||
|     reqBody.inventory_file_name = $scope.selectedInventoryFile.value; | ||||
| 
 | ||||
|     if(roleName){ | ||||
|       reqBody.inventory_file_name = roleName + '/tests/' + reqBody.inventory_file_name; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     console.log("Check Mode = " + reqBody.check_mode); | ||||
| 
 | ||||
|     reqBody.selectedPlaybook = selectedPlaybook.playbook; | ||||
|     reqBody.executionType  = executionType; | ||||
|     reqBody.executionName  = executionName; | ||||
| 
 | ||||
|     reqBody.ansibleEngine = angular.copy(selectedProject.ansibleEngine); | ||||
| 
 | ||||
|     // Override project folder for roles
 | ||||
|     if(projectFolder) | ||||
|       reqBody.ansibleEngine.projectFolder = projectFolder; | ||||
| 
 | ||||
|     if(selectedPlay && selectedPlay.play) | ||||
|       reqBody.host = selectedPlay.play.hosts; | ||||
| 
 | ||||
|     $scope.result = "Running..."; | ||||
| 
 | ||||
|     ansible.executeAnsiblePlayBook(reqBody,function(response){ | ||||
|       //$scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data).replace(/\n/g, "<br>"));
 | ||||
|       $scope.refreshLog = true; | ||||
|       $scope.executionData = response.data; | ||||
| 
 | ||||
|       setTimeout(function(){ | ||||
|         $scope.refreshLogs(); | ||||
|       },3000); | ||||
| 
 | ||||
|     }, function(response){ | ||||
|       if(response.data) | ||||
|         $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data).replace(/\n/g, "<br>")); | ||||
|       $scope.AnsiblePlayBookLoading = false; | ||||
|       console.log("error" + $scope.result) | ||||
|     }, 'PlaybookExecutionModal'); | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
|   /*setTimeout(function(){ | ||||
|    $scope.executeAnsiblePlayBook(); | ||||
|    },200);*/ | ||||
| 
 | ||||
| 
 | ||||
|   /** | ||||
|    * Get logs | ||||
|    */ | ||||
|   $scope.getLogs = function(){ | ||||
|     ansible.getLogs($scope.executionData,function(successResponse) { | ||||
|       $scope.result = $sce.trustAsHtml(ansi2html.toHtml(successResponse.data.replace('SCRIPT_FINISHED','')).replace(/\n/g, "<br>")); | ||||
| 
 | ||||
|       if(successResponse.data.indexOf('SCRIPT_FINISHED') > -1){ | ||||
|         $scope.refreshLog = false; | ||||
|         $scope.AnsiblePlayBookLoading = false; | ||||
|       } | ||||
|       $scope.processAnsibleOutput(successResponse.data) | ||||
| 
 | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Refersh Logs | ||||
|    */ | ||||
|   $scope.refreshLogs = function(){ | ||||
|     if($scope.logRefreshTimer){ | ||||
|       $timeout.cancel( $scope.logRefreshTimer ); | ||||
|     } | ||||
| 
 | ||||
|     $scope.getLogs(); | ||||
|     $scope.logRefreshTimer = $timeout( | ||||
|       function(){ | ||||
|         //$scope.getLogs(tile);
 | ||||
|         if($scope.refreshLog) { | ||||
|           $scope.refreshLogs(); | ||||
|         } | ||||
|       }, | ||||
|       10000 | ||||
|     ); | ||||
| 
 | ||||
|     $scope.$on( | ||||
|       "$destroy", | ||||
|       function( event ) { | ||||
|         $timeout.cancel( $scope.logRefreshTimer ); | ||||
|       } | ||||
|     ); | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Close the modal | ||||
|    */ | ||||
|   $scope.ok = function () { | ||||
|     $uibModalInstance.close(null); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Cancel modal | ||||
|    */ | ||||
|   $scope.cancel = function () { | ||||
|     $uibModalInstance.dismiss('cancel'); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Run a random command on the server | ||||
|    * TODO: Remove this later. | ||||
|    * @param command | ||||
|    */ | ||||
|   $scope.runCommand = function(command){ | ||||
|     command = command || $scope.command; | ||||
|     ansible.executeCommand( command, | ||||
|       function(response){ | ||||
|         $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data).replace(/\n/g, "<br>").replace(/ /g," ")); | ||||
| 
 | ||||
|       }, function(response){ | ||||
|         $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data).replace(/\n/g, "<br>")); | ||||
| 
 | ||||
|       }) | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Run Ansible Playbook | ||||
|    * @constructor | ||||
|    */ | ||||
|   $scope.Run = function(){ | ||||
|     $scope.executeAnsiblePlayBook(); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * This is used when viewing the logs from the Runs view | ||||
|    */ | ||||
|   if($scope.readOnly){ | ||||
|     $scope.executionData = runData; | ||||
|     $scope.refreshLog = true; | ||||
|     $scope.refreshLogs() | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Get List of inventory files | ||||
|    */ | ||||
|   $scope.listOfInventoryFiles = function(){ | ||||
| 
 | ||||
|     var rolesTestFolder = null; | ||||
| 
 | ||||
|     if(roleName){ | ||||
|       rolesTestFolder = projectFolder + '/' + roleName + '/tests' | ||||
|     } | ||||
| 
 | ||||
|     ansible.getInventoryList(function(response){ | ||||
|         $scope.inventoryFiles = response.data; | ||||
|         if($scope.inventoryFiles.length) | ||||
|           $scope.selectedInventoryFile = {value:$scope.inventoryFiles[0]}; | ||||
|         /** | ||||
|          * Run Get Tags | ||||
|          */ | ||||
|         if(!readOnly) | ||||
|           $scope.getTags(); | ||||
|       }, | ||||
|       function(response){ | ||||
|         /*$scope.err_msg = response.data;*/ | ||||
|         $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data).replace(/\n/g, "<br>")); | ||||
|         $scope.view = 'console' | ||||
|       },rolesTestFolder) | ||||
|   }; | ||||
| 
 | ||||
|   $scope.listOfInventoryFiles(); | ||||
| 
 | ||||
|   /** | ||||
|    * Get List of Tags based on playbook and inventory file | ||||
|    */ | ||||
|   $scope.getTags = function(){ | ||||
|     var inventory_file_name = $scope.selectedInventoryFile.value; | ||||
| 
 | ||||
|     if(roleName){ | ||||
|       inventory_file_name = roleName + '/tests/' + inventory_file_name; | ||||
|     } | ||||
| 
 | ||||
|     var selectedPlaybookName = selectedPlaybook.playbook; | ||||
| 
 | ||||
|     var ansibleEngine = angular.copy(selectedProject.ansibleEngine); | ||||
| 
 | ||||
|     // Override project folder for roles
 | ||||
|     if(projectFolder) | ||||
|       ansibleEngine.projectFolder = projectFolder; | ||||
| 
 | ||||
|     ansible.getTagList(selectedPlaybookName,inventory_file_name,ansibleEngine, | ||||
|       function(response){ | ||||
|         console.log(response.data) | ||||
| 
 | ||||
|         /*var re = /TAGS: \[(.*)\]/g; | ||||
|          var m; | ||||
| 
 | ||||
|          var all_tags = [] | ||||
| 
 | ||||
|          while ((m = re.exec(response.data)) !== null) { | ||||
|          if (m.index === re.lastIndex) { | ||||
|          re.lastIndex++; | ||||
|          } | ||||
|          // View your result using the m-variable.
 | ||||
|          // eg m[0] etc.
 | ||||
|          if(m[1]) | ||||
|          all_tags.push(m[1]) | ||||
|          } | ||||
| 
 | ||||
|          $scope.all_tags = all_tags.join(',').split(',');*/ | ||||
| 
 | ||||
|         if(!response.data.playbooks)return null; | ||||
| 
 | ||||
|         var playbooks = response.data.playbooks; | ||||
|         $scope.all_hosts = []; | ||||
|         $scope.all_tags = []; | ||||
| 
 | ||||
|         angular.forEach(playbooks, playbook => { | ||||
|           angular.forEach(playbook.plays, play => { | ||||
|           $scope.all_hosts = $scope.all_hosts.concat(play.hosts); | ||||
|         $scope.all_tags = $scope.all_tags.concat(play.tags); | ||||
|         angular.forEach(play.tasks, task => { | ||||
|           $scope.all_tags = $scope.all_tags.concat(task.tags); | ||||
|       }) | ||||
|       }) | ||||
|       }); | ||||
| 
 | ||||
|         // Get Unique List of tags
 | ||||
|         $scope.all_tags = Array.from(new Set($scope.all_tags)); | ||||
| 
 | ||||
|         // Get Unique List of hosts
 | ||||
|         $scope.all_hosts = Array.from(new Set($scope.all_hosts)); | ||||
| 
 | ||||
|         $scope.all_hosts = $scope.all_hosts.map(host => {return {name:host,selected:false}}); | ||||
| 
 | ||||
|         $scope.all_tags = $scope.all_tags.map(tag => {return {name:tag,selected:false}}); | ||||
| 
 | ||||
|         if(tags){ | ||||
|           angular.forEach(tags, tag => { | ||||
|             var tag_found = false; | ||||
|           angular.forEach($scope.all_tags, (all_tag,index) => { | ||||
|             if(tag == all_tag.name){ | ||||
|             tag_found = true; | ||||
|             all_tag.selected = true | ||||
|           } | ||||
|         }); | ||||
|           if(!tag_found) | ||||
|             $scope.all_tags.push({name:tag,selected:true}) | ||||
|         }) | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         $scope.readyForPlay = true; | ||||
| 
 | ||||
|       }, | ||||
|       function(error){ | ||||
|         //console.log(error.data)
 | ||||
|         //$scope.err_msg = error.data;
 | ||||
|         $scope.result = $sce.trustAsHtml(ansi2html.toHtml(error.data).replace(/\n/g, "<br>")); | ||||
|         $scope.view = 'console' | ||||
|       }) | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Process Ansible Output and show graphically | ||||
|    * @param ansibleOutput | ||||
|    */ | ||||
|   $scope.processAnsibleOutput_old = function(ansibleOutput){ | ||||
| 
 | ||||
|     $scope.ansibleOutputResult = []; | ||||
|     //https://regex101.com/r/yD6lZ6/1
 | ||||
|     //var re = /(PLAY|TASK) \[(.*)\] (.*)\n(.*?):([^]*?)(?=TASK|PLAY)/gm;
 | ||||
|     //var re = /(PLAY|TASK) \[(.*)\] (.*)\n(?:(.*?)\s?(.*): \[(.*)\](.*)=> ([^]*?)(?=TASK|PLAY)|(.*?): \[(.*)\](.*)|(.*)|(?=TASK|PLAY))/gm
 | ||||
|     //var re = /(PLAY|TASK) \[(.*)\] (.*)\n(?:(.*?)\s?(.*): \[(.*)\](.*)=> ([^]*?)(?=\n\n)|(.*?): \[(.*)\](.*)|(.*)|(?=\n\n))/gm;
 | ||||
|     var re = /({[^]+})/g | ||||
|     var m; | ||||
| 
 | ||||
|     while ((m = re.exec(ansibleOutput)) !== null) { | ||||
|       if (m.index === re.lastIndex) { | ||||
|         re.lastIndex++; | ||||
|       } | ||||
|       // View your result using the m-variable.
 | ||||
|       // eg m[0] etc.
 | ||||
| 
 | ||||
|       var type = m[1]; //TASK,PLAY
 | ||||
|       var name = m[2]; // ansible-role-vra : debug delete instance
 | ||||
|       var status = m[5]; //ok, skipping, failed
 | ||||
|       var host = m[6]; //localhost , localhost -> localhost
 | ||||
|       var status_2 = m[7]; | ||||
|       var result = m[8]; | ||||
| 
 | ||||
|       if(result){ | ||||
|         //var result_object_string = result.replace(/({[^]+})[^]+/,"$1");
 | ||||
|         var result_object_string = result; | ||||
|         var resultObject = null; | ||||
|         try{ | ||||
|           resultObject = JSON.parse(result_object_string); | ||||
| 
 | ||||
|           //resultObject.formattedStdErr = resultObject.stderr.replace(/\\r\\n/g,'<br>').replace(/\\n/g, "<br>");
 | ||||
|           resultObject.formattedStdErr = resultObject.stderr.replace(/\n/g,'<br>'); | ||||
|           resultObject.formattedStdOut = resultObject.stderr.replace(/\n/g,'<br>'); | ||||
| 
 | ||||
|         }catch(e){ | ||||
|           console.log("Error converting ansible output result object to javascript") | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       $scope.ansibleOutputResult.push({ | ||||
|         type:type, | ||||
|         name:name, | ||||
|         status:status, | ||||
|         host:host, | ||||
|         status_2:status_2, | ||||
|         resultString:result, | ||||
|         resultObject:resultObject | ||||
|       }) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   $scope.processAnsibleOutput = function(ansibleOutput){ | ||||
| 
 | ||||
|     $scope.ansibleOutputResult = []; | ||||
|     $scope.ansibleOutputObject = { | ||||
|       'plays' : [], | ||||
|       'stats' : {} | ||||
| 
 | ||||
|     } | ||||
|     //var re = /(.*{[^]+}.*)/g;
 | ||||
|     var re = /--------BEGIN--------([^]+?)--------END--------/gm; | ||||
|     var m; | ||||
| 
 | ||||
|     while ((m = re.exec(ansibleOutput)) !== null) { | ||||
|       if (m.index === re.lastIndex) { | ||||
|         re.lastIndex++; | ||||
|       } | ||||
|       // View your result using the m-variable.
 | ||||
|       // eg m[0] etc.
 | ||||
| 
 | ||||
|       try{ | ||||
|         //$scope.ansibleOutputObject = JSON.parse(m[1]);
 | ||||
|         var resultItem = JSON.parse(m[1]); | ||||
|         if('play' in resultItem){ | ||||
|           $scope.ansibleOutputObject.plays.push(resultItem); | ||||
|         } else if('task' in resultItem){ | ||||
| 
 | ||||
|           var current_play = $scope.ansibleOutputObject.plays[$scope.ansibleOutputObject.plays.length-1] | ||||
|           var newTask = true; | ||||
|           angular.forEach(current_play.tasks, (task, index)=>{ | ||||
|             if(task.task.id === resultItem.task.id){ | ||||
|             newTask = false; | ||||
|             current_play.tasks[index] = resultItem | ||||
|           } | ||||
|         }) | ||||
| 
 | ||||
| 
 | ||||
|           if(newTask) | ||||
|             current_play.tasks.push(resultItem); | ||||
| 
 | ||||
|         } else if('stats' in resultItem){ | ||||
|           $scope.ansibleOutputObject.stats = resultItem.stats; | ||||
|         } | ||||
| 
 | ||||
|       }catch(e){ | ||||
|         console.log("Error parsing ansible output" + e); | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|       //var plays = $scope.ansibleOutputObject.plays;
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     console.log($scope.ansibleOutputObject); | ||||
| 
 | ||||
|     /*while ((m = re.exec(ansibleOutput)) !== null) { | ||||
|      if (m.index === re.lastIndex) { | ||||
|      re.lastIndex++; | ||||
|      } | ||||
|      // View your result using the m-variable.
 | ||||
|      // eg m[0] etc.
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|      var type = m[1]; //TASK,PLAY
 | ||||
|      var name = m[2]; // ansible-role-vra : debug delete instance
 | ||||
|      var status = m[5]; //ok, skipping, failed
 | ||||
|      var host = m[6]; //localhost , localhost -> localhost
 | ||||
|      var status_2 = m[7]; | ||||
|      var result = m[8]; | ||||
| 
 | ||||
|      if(result){ | ||||
|      //var result_object_string = result.replace(/({[^]+})[^]+/,"$1");
 | ||||
|      var result_object_string = result; | ||||
|      var resultObject = null; | ||||
|      try{ | ||||
|      resultObject = JSON.parse(result_object_string); | ||||
| 
 | ||||
|      //resultObject.formattedStdErr = resultObject.stderr.replace(/\\r\\n/g,'<br>').replace(/\\n/g, "<br>");
 | ||||
|      resultObject.formattedStdErr = resultObject.stderr.replace(/\n/g,'<br>'); | ||||
|      resultObject.formattedStdOut = resultObject.stderr.replace(/\n/g,'<br>'); | ||||
| 
 | ||||
|      }catch(e){ | ||||
|      console.log("Error converting ansible output result object to javascript") | ||||
|      } | ||||
|      } | ||||
| 
 | ||||
|      $scope.ansibleOutputResult.push({ | ||||
|      type:type, | ||||
|      name:name, | ||||
|      status:status, | ||||
|      host:host, | ||||
|      status_2:status_2, | ||||
|      resultString:result, | ||||
|      resultObject:resultObject | ||||
|      }) | ||||
| 
 | ||||
|      }*/ | ||||
| 
 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.execution', []) | ||||
|   .controller('ExecutionController', executionController) | ||||
|   .name; | ||||
							
								
								
									
										17
									
								
								client/app/designer/execution/execution.controller.spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								client/app/designer/execution/execution.controller.spec.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: ExecutionCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.execution')); | ||||
| 
 | ||||
|   var ExecutionCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     ExecutionCtrl = $controller('ExecutionCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										104
									
								
								client/app/designer/file_browser/file_browser.component.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								client/app/designer/file_browser/file_browser.component.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,104 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| const uiRouter = require('angular-ui-router'); | ||||
| 
 | ||||
| import routes from './file_browser.routes'; | ||||
| 
 | ||||
| export class FileBrowserComponent { | ||||
|   /*@ngInject*/ | ||||
|   constructor($scope,ansible,editor) { | ||||
|     'ngInject'; | ||||
|     $scope.treeOptions = { | ||||
|       nodeChildren: "children", | ||||
|       dirSelectable: true, | ||||
|       isLeaf: function (node) { | ||||
|         return !(node.type === 'directory'); | ||||
|       }, | ||||
|       injectClasses: { | ||||
|         ul: "a1", | ||||
|         li: "a2", | ||||
|         liSelected: "a7", | ||||
|         iExpanded: "a3", | ||||
|         iCollapsed: "a4", | ||||
|         iLeaf: "a5", | ||||
|         label: "a6", | ||||
|         labelSelected: "a8" | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     $scope.editContent = false; | ||||
|     $scope.selectedFile = {showSource: true}; | ||||
| 
 | ||||
|     var loadProjectFiles = function(){ | ||||
|       ansible.getProjectFiles(function(response){ | ||||
|         $scope.projectFiles = response.data; | ||||
|       },function(error){ | ||||
| 
 | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.$on('projectLoaded',function(){ | ||||
|       loadProjectFiles(); | ||||
|     }); | ||||
| 
 | ||||
|     if($scope.$parent.selectedProject && $scope.$parent.selectedProject.ansibleEngine) { | ||||
|       loadProjectFiles(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show selected item in the tree | ||||
|      * @param file | ||||
|      * @param parent | ||||
|      */ | ||||
|     $scope.showSelected = function (file, parent) { | ||||
| 
 | ||||
|       if (file.children) { | ||||
|         $scope.selectedFile.content = JSON.stringify(file, null, '\t'); | ||||
|         $scope.docType = 'json'; | ||||
|         $scope.selectedFile.tasks = null; | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       var command = 'cat "' + file.path + '"'; | ||||
|       $scope.showSource = true; | ||||
|       $scope.markdownContent = ''; | ||||
|       $scope.docType = 'text'; | ||||
|       $scope.selectedFile.content = 'Loading..'; | ||||
|       $scope.selectedFile.tasks = null; | ||||
|       $scope.selectedFileName = file.name; | ||||
|       $scope.selectedFilePath = file.path; | ||||
|       $scope.parentNode = parent; | ||||
| 
 | ||||
|       ansible.executeCommand(command, | ||||
|         function (response) { | ||||
|           console.log(response.data) | ||||
|           editor.setContentAndType(response.data, file, $scope.selectedFile); | ||||
| 
 | ||||
|           var parentDirectory = file.path.replace(/^(.+)\/(.+)\/([^/]+)$/, "$2"); | ||||
|           if (parentDirectory == 'tasks') { | ||||
|             $scope.selectedFile.tasks = YAML.parse(response.data) || []; | ||||
|           } | ||||
| 
 | ||||
|           if (parentDirectory == 'group_vars' || parentDirectory == 'host_vars') { | ||||
|             $scope.selectedFile.docType = 'yaml'; | ||||
|           } | ||||
| 
 | ||||
|           //$scope.selectedFile.content = response.data;
 | ||||
| 
 | ||||
|         }, function (response) { | ||||
|           $scope.selectedFile.content = response.data; | ||||
| 
 | ||||
|         }) | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.file_browser', [uiRouter]) | ||||
|   .config(routes) | ||||
|   .component('fileBrowser', { | ||||
|     template: require('./file_browser.html'), | ||||
|     controller: FileBrowserComponent, | ||||
|     controllerAs: 'fileBrowserCtrl' | ||||
|   }) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Component: FileBrowserComponent', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.file_browser')); | ||||
| 
 | ||||
|   var FileBrowserComponent; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($componentController) { | ||||
|     FileBrowserComponent = $componentController('file_browser', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										33
									
								
								client/app/designer/file_browser/file_browser.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								client/app/designer/file_browser/file_browser.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| 
 | ||||
| <div class="row"> | ||||
|   <h3>File Browser</h3> | ||||
|   <div class="col-md-6"> | ||||
|     <treecontrol ng-show="projectFiles.children" class="tree-classic" | ||||
|                  tree-model="projectFiles.children" | ||||
|                  options="treeOptions" | ||||
|                  on-selection="showSelected(node, $parentNode)" | ||||
|                  selected-node="selectedFile" | ||||
|                  filter-expression="{name: '!.git'}"> | ||||
|       {{node.name}} | ||||
|     </treecontrol> | ||||
|   </div> | ||||
| 
 | ||||
|   <div class="col-md-6"> | ||||
|     <button class="btn btn-default" ng-show="selectedFile.docType == 'markdown' && !selectedFile.showSource" ng-click="selectedFile.showSource = true"> | ||||
|       Show Source | ||||
|     </button> | ||||
|     <button class="btn btn-default" ng-show="selectedFile.docType == 'markdown' && selectedFile.showSource" ng-click="selectedFile.showSource = false"> | ||||
|       Hide Source | ||||
|     </button> | ||||
|     <!--{{docType}}--> | ||||
|     <!--{{selectedFile.showSource}}--> | ||||
|     <div ng-show="selectedFile.showSource" ng-readonly="!editContent" | ||||
|          ui-ace="{theme:'twilight',document:selectedFile.docType,mode:selectedFile.docType,onChange:codeChanged,onLoad:aceLoaded}" ng-model="selectedFile.content"> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     <div ng-show="!selectedFile.showSource" btf-markdown="selectedFile.markdownContent"> | ||||
|     </div> | ||||
|   </div> | ||||
| 
 | ||||
| </div> | ||||
							
								
								
									
										10
									
								
								client/app/designer/file_browser/file_browser.routes.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								client/app/designer/file_browser/file_browser.routes.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export default function($stateProvider) { | ||||
|   'ngInject'; | ||||
|   $stateProvider | ||||
|     .state('designer.file_browser', { | ||||
|       url: '/file_browser', | ||||
|       template: '<file-browser></file-browser>' | ||||
|     }); | ||||
| } | ||||
							
								
								
									
										278
									
								
								client/app/designer/inventory/inventory.component.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								client/app/designer/inventory/inventory.component.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,278 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| const uiRouter = require('angular-ui-router'); | ||||
| 
 | ||||
| import routes from './inventory.routes'; | ||||
| 
 | ||||
| 
 | ||||
| export class InventoryComponent { | ||||
|   /*@ngInject*/ | ||||
|   constructor($scope, $uibModal, ansible) { | ||||
| 
 | ||||
|     'ngInject'; | ||||
|     $scope.selectedInventory = {inventory: "", content: ""}; | ||||
| 
 | ||||
|     $scope.editInventory = {value: false}; | ||||
|     $scope.selectedGroup = {group: null}; | ||||
|     $scope.selectedHost = {host: null}; | ||||
| 
 | ||||
|     $scope.complexVar = {}; | ||||
| 
 | ||||
|     $scope.$on('projectLoaded', function () { | ||||
|       $scope.getInventorys() | ||||
|     }); | ||||
| 
 | ||||
|     //To fix a warning message in console
 | ||||
|     $scope.aceLoaded = function(_editor){ | ||||
|       _editor.$blockScrolling = Infinity; | ||||
|     }; | ||||
| 
 | ||||
|     // --------------------------------------- PLAYBOOKS ----------------
 | ||||
| 
 | ||||
|     $scope.getInventorys = function () { | ||||
|       ansible.getInventoryList( | ||||
|         function (response) { | ||||
|           $scope.inventorys = response.data; | ||||
|         }, | ||||
|         function (response) { | ||||
|           console.log(response.data) | ||||
|         } | ||||
|       ) | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     if ($scope.$parent.selectedProject && $scope.$parent.selectedProject.ansibleEngine) { | ||||
|       $scope.getInventorys() | ||||
|     } | ||||
| 
 | ||||
|     $scope.loadingModuleCode = false; | ||||
| 
 | ||||
|     $scope.showInventoryCode = function (inventory_name) { | ||||
|       $scope.loadingModuleCode = true; | ||||
|       if (!inventory_name) { | ||||
|         $scope.selectedInventory.content = "Select a module"; | ||||
|         return; | ||||
|       } | ||||
|       ansible.readInventory(inventory_name, function (response) { | ||||
|         $scope.loadingModuleCode = false; | ||||
|         $scope.selectedInventory.content = response.data.split("Stream :: close")[0]; | ||||
| 
 | ||||
|         $scope.inventory_data_json = ansible.parseINIString($scope.selectedInventory.content); | ||||
|         $scope.inventory_data_json['name'] = inventory_name | ||||
| 
 | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.$watch('selectedInventory.inventory', function (newValue, oldValue) { | ||||
|       if (newValue && newValue !== oldValue) { | ||||
|         $scope.selectedInventory.content = "Loading Code..."; | ||||
|         $scope.showInventoryCode(newValue) | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     $scope.showCreatInventoryModal = function () { | ||||
|       var modalInstance = $uibModal.open({ | ||||
|         animation: true, | ||||
|         /*templateUrl: 'createTaskContent.html',*/ | ||||
|         templateUrl: 'app/designer/inventory/new_inventory/new_inventory.html', | ||||
|         controller: 'NewInventoryController', | ||||
|         size: 'md', | ||||
|         backdrop: 'static', | ||||
|         keyboard: false, | ||||
|         closeByEscape: false, | ||||
|         closeByDocument: false, | ||||
|         resolve: { | ||||
|           selectedProject: function () { | ||||
|             return $scope.$parent.selectedProject | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       modalInstance.result.then(function () { | ||||
|         $scope.getInventorys(); | ||||
|       }, function () { | ||||
| 
 | ||||
|       }); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     $scope.editGroup = function (group) { | ||||
|       $scope.showCreateGroupModal(group); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.showCreateGroupModal = function (editGroup) { | ||||
|       var modalInstance = $uibModal.open({ | ||||
|         animation: true, | ||||
|         /*templateUrl: 'createTaskContent.html',*/ | ||||
|         templateUrl: 'app/designer/inventory/new_group/new_group.html', | ||||
|         controller: 'NewGroupController', | ||||
|         size: 'lg', | ||||
|         backdrop: 'static', | ||||
|         keyboard: false, | ||||
|         closeByEscape: false, | ||||
|         closeByDocument: false, | ||||
|         resolve: { | ||||
|           selectedProject: function () { | ||||
|             return $scope.$parent.selectedProject | ||||
|           }, | ||||
| 
 | ||||
|           editGroup: function () { | ||||
|             return editGroup | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       modalInstance.result.then(function (group) { | ||||
|         if(!editGroup)$scope.addGroup(group); | ||||
|         else $scope.selectedInventory.content = ansible.jsonToAnsibleInventoryIni($scope.inventory_data_json); | ||||
| 
 | ||||
|         $scope.saveInventory(); | ||||
|       }, function () { | ||||
| 
 | ||||
|       }); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     $scope.editHost = function (host) { | ||||
| 
 | ||||
|       var hostMemberOfGroups = getHostMemberOfGroups(host); | ||||
| 
 | ||||
|       $scope.showCreateHostModal({name: host, members: hostMemberOfGroups.join(',')}); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.showCreateHostModal = function (editHost) { | ||||
|       var modalInstance = $uibModal.open({ | ||||
|         animation: true, | ||||
|         /*templateUrl: 'createTaskContent.html',*/ | ||||
|         templateUrl: 'app/designer/inventory/new_host/new_host.html', | ||||
|         controller: 'NewHostController', | ||||
|         size: 'lg', | ||||
|         backdrop: 'static', | ||||
|         keyboard: false, | ||||
|         closeByEscape: false, | ||||
|         closeByDocument: false, | ||||
|         resolve: { | ||||
|           selectedProject: function () { | ||||
|             return $scope.$parent.selectedProject | ||||
|           }, | ||||
|           editHost: function () { | ||||
|             return editHost | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       modalInstance.result.then(function (host) { | ||||
|         $scope.addHost(host); | ||||
|         $scope.saveInventory(); | ||||
|       }, function () { | ||||
| 
 | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.saveInventory = function () { | ||||
|       $scope.saveInventoryLoading = true; | ||||
|       ansible.createInventory($scope.selectedInventory.inventory, $scope.selectedInventory.content, | ||||
|         function (response) { | ||||
|           $scope.saveInventoryLoading = false; | ||||
|           $scope.editInventory.value = false; | ||||
|         }, | ||||
|         function (response) { | ||||
|           $scope.saveInventoryLoading = false; | ||||
|           $scope.err_msg = response.data; | ||||
|         }) | ||||
|     }; | ||||
| 
 | ||||
|     $scope.deleteInventory = function () { | ||||
|       $scope.deleteInventoryLoading = true; | ||||
|       ansible.deleteInventory($scope.selectedInventory.inventory, | ||||
|         function (response) { | ||||
|           $scope.deleteInventoryLoading = false; | ||||
|           $scope.selectedInventory.inventory = ""; | ||||
|           $scope.getInventorys(); | ||||
|         }, | ||||
|         function (response) { | ||||
|           $scope.deleteInventoryLoading = false; | ||||
|           $scope.err_msg = response.data; | ||||
|         }) | ||||
|     }; | ||||
| 
 | ||||
|     $scope.addGroup = function (group) { | ||||
| 
 | ||||
|       $scope.inventory_data_json.groups.push(group); | ||||
|       $scope.selectedInventory.content = ansible.jsonToAnsibleInventoryIni($scope.inventory_data_json); | ||||
|       // To refresh All Hosts list
 | ||||
|       $scope.inventory_data_json = ansible.parseINIString($scope.selectedInventory.content) | ||||
|     }; | ||||
| 
 | ||||
|     $scope.addHost = function (host) { | ||||
|       if ($scope.inventory_data_json.hosts.indexOf(host.name) < 0) | ||||
|         $scope.inventory_data_json.hosts.push(host.name); | ||||
| 
 | ||||
|       var host_member_of_groups = host.members.split(','); | ||||
| 
 | ||||
|       angular.forEach($scope.inventory_data_json.groups, function (group) { | ||||
|         if ((host_member_of_groups.indexOf(group.name)) > -1 && group.members.indexOf(host.name) < 0) { | ||||
|           group.members.push(host.name) | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       $scope.selectedInventory.content = ansible.jsonToAnsibleInventoryIni($scope.inventory_data_json); | ||||
|       // To refresh All Hosts list
 | ||||
|       $scope.inventory_data_json = ansible.parseINIString($scope.selectedInventory.content) | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     $scope.deleteGroup = function (index) { | ||||
|       $scope.inventory_data_json.groups.splice(index, 1); | ||||
|       $scope.selectedInventory.content = ansible.jsonToAnsibleInventoryIni($scope.inventory_data_json); | ||||
|       // To refresh All Hosts list
 | ||||
|       $scope.inventory_data_json = ansible.parseINIString($scope.selectedInventory.content); | ||||
| 
 | ||||
|       $scope.saveInventory(); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     $scope.deleteHost = function (index, group) { | ||||
| 
 | ||||
|       var hostname = $scope.inventory_data_json.hosts[index]; | ||||
| 
 | ||||
|       $scope.inventory_data_json.hosts.splice(index, 1); | ||||
| 
 | ||||
|       angular.forEach($scope.inventory_data_json.groups, function (group) { | ||||
|         var memberIndex = group.members.indexOf(hostname) | ||||
|         if (memberIndex > -1) { | ||||
|           group.members.splice(memberIndex, 1) | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       $scope.selectedInventory.content = ansible.jsonToAnsibleInventoryIni($scope.inventory_data_json); | ||||
|       // To refresh All Hosts list
 | ||||
|       $scope.inventory_data_json = ansible.parseINIString($scope.selectedInventory.content); | ||||
| 
 | ||||
|       $scope.saveInventory(); | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     var getHostMemberOfGroups = function (host) { | ||||
|       var groups = []; | ||||
|       angular.forEach($scope.inventory_data_json.groups, function (group) { | ||||
|         var memberIndex = group.members.indexOf(host); | ||||
|         if (memberIndex > -1) { | ||||
|           groups.push(group.name) | ||||
|         } | ||||
|       }); | ||||
|       return groups; | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.inventory', [uiRouter]) | ||||
|   .config(routes) | ||||
|   .component('inventory', { | ||||
|     template: require('./inventory.html'), | ||||
|     controller: InventoryComponent, | ||||
|     controllerAs: 'inventoryCtrl' | ||||
|   }) | ||||
|   .name; | ||||
							
								
								
									
										17
									
								
								client/app/designer/inventory/inventory.component.spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								client/app/designer/inventory/inventory.component.spec.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Component: InventoryComponent', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.inventory')); | ||||
| 
 | ||||
|   var InventoryComponent; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($componentController) { | ||||
|     InventoryComponent = $componentController('inventory', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										0
									
								
								client/app/designer/inventory/inventory.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								client/app/designer/inventory/inventory.css
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										154
									
								
								client/app/designer/inventory/inventory.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								client/app/designer/inventory/inventory.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,154 @@ | |||
| <div class="col-md-8"> | ||||
|   <button class="btn btn-default" ng-click="showCreatInventoryModal()">Create Inventory <span class="fa fa-plus"></span></button> | ||||
|   <!--<button class="btn btn-default" ng-if="!editInventory.value" ng-click="editInventory.value = true">Edit <span ng-if="!saveInventoryLoading"  class="fa fa-edit"></span></button> | ||||
|   <button class="btn btn-primary" ng-if="editInventory.value" ng-disabled="!selectedInventory.inventory" ng-click="saveInventory()">Save <span ng-if="!saveInventoryLoading"  class="fa fa-save"></span><span ng-if="saveInventoryLoading" class="fa fa-spinner fa-spin"></span></button>--> | ||||
|   <button class="btn btn-danger" confirm="Are you sure you want to delete inventory? " ng-disabled="!selectedInventory.inventory" ng-click="deleteInventory()">Delete <span ng-if="!deleteInventoryLoading"  class="fa fa-trash-o"></span><span ng-if="deleteInventoryLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
|   <div class="table-responsive"> | ||||
|     <table class="table"> | ||||
|       <thead> | ||||
|       <tr> | ||||
|         <th>Select</th> | ||||
|         <th>Name</th> | ||||
|       </tr> | ||||
|       </thead> | ||||
|       <tbody> | ||||
|       <tr ng-repeat="inventory in inventorys"> | ||||
|         <td><input name="playGroup" type="radio" ng-model="selectedInventory.inventory" ng-value="inventory"> | ||||
|         </td> | ||||
|         <td>{{inventory}}</td> | ||||
|       </tr> | ||||
|       </tbody> | ||||
|     </table> | ||||
|   </div> | ||||
| 
 | ||||
|   <div class="row"> | ||||
|     <div class="col-md-6"> | ||||
| 
 | ||||
|       <div class="panel"> | ||||
|         <div class="panel panel-default"> | ||||
|           <div class="panel-heading">Groups - {{inventory_data_json.groups.length}}</div> | ||||
|           <div class="panel-body"> | ||||
|             <button class="btn btn-default" ng-disabled="!selectedInventory.inventory" ng-click="showCreateGroupModal()">Create Group <span class="fa fa-plus"></span></button> | ||||
|             <div class="table-responsive"> | ||||
|               <table class="table"> | ||||
|                 <thead> | ||||
|                 <tr> | ||||
|                   <th>Select</th> | ||||
|                   <th>Name</th> | ||||
|                   <th>Members</th> | ||||
|                   <th>Actions</th> | ||||
|                 </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                 <tr ng-repeat="group in inventory_data_json.groups"> | ||||
|                   <td><input name="groupsRadioGroup" type="radio" ng-model="selectedGroup.group" ng-value="group"> | ||||
|                   </td> | ||||
|                   <td>{{group.name}}</td> | ||||
|                   <td>{{group.members.length}}</td> | ||||
|                   <td> | ||||
|                     <div class="btn-group"> | ||||
|                       <label ng-show="group.type == 'userdefined'" class="btn btn-default btn-xs" ng-click="editGroup(group)" ><span class="fa fa-edit"></span></label> | ||||
|                       <label ng-show="group.type == 'userdefined'" class="btn btn-danger btn-xs" confirm="Are you sure you want to delete this group?" ng-click="deleteGroup($index)" ><span class="fa fa-trash-o"></span></label> | ||||
|                     </div> | ||||
|                   </td> | ||||
|                 </tr> | ||||
|                 </tbody> | ||||
|               </table> | ||||
|             </div> | ||||
| 
 | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="panel" ng-show="selectedGroup.group.members"> | ||||
|         <div class="panel panel-default"> | ||||
|           <div class="panel-heading">Group Members - {{selectedGroup.group.name}}</div> | ||||
|           <div class="panel-body"> | ||||
| 
 | ||||
|             <div class="table-responsive" > | ||||
|               <table class="table"> | ||||
|                 <thead> | ||||
|                 <tr> | ||||
|                   <th>Select</th> | ||||
|                   <th>Name</th> | ||||
|                   <th>Actions</th> | ||||
| 
 | ||||
|                 </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                 <tr ng-repeat="host in selectedGroup.group.members"> | ||||
|                   <td><input name="hostsRadioGroup" type="radio" ng-model="selectedHost.host" ng-value="host"> | ||||
|                   </td> | ||||
|                   <td>{{host}}</td> | ||||
|                   <td> | ||||
|                     <div class="btn-group"> | ||||
|                       <label class="btn btn-danger btn-xs" confirm="Are you sure you want to delete host and remove it from all groups?" ng-click="deleteHost($index)" ><span class="fa fa-trash-o"></span></label> | ||||
|                     </div> | ||||
|                   </td> | ||||
|                 </tr> | ||||
|                 </tbody> | ||||
|               </table> | ||||
|             </div> | ||||
| 
 | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="col-md-6"> | ||||
|       <div class="panel"> | ||||
|         <div class="panel panel-default"> | ||||
|           <div class="panel-heading">Hosts - {{inventory_data_json.hosts.length}}</div> | ||||
|           <div class="panel-body"> | ||||
|             <button class="btn btn-default" ng-disabled="!selectedInventory.inventory" ng-click="showCreateHostModal()">Create Host <span class="fa fa-plus"></span></button> | ||||
|             <div class="table-responsive"> | ||||
|               <table class="table"> | ||||
|                 <thead> | ||||
|                 <tr> | ||||
|                   <th>Select</th> | ||||
|                   <th>Name</th> | ||||
|                   <th>Actions</th> | ||||
| 
 | ||||
|                 </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                 <tr ng-repeat="host in inventory_data_json.hosts"> | ||||
|                   <td><input name="hostsRadioGroup" type="radio" ng-model="selectedHost.host" ng-value="host"> | ||||
|                   </td> | ||||
|                   <td>{{host}}</td> | ||||
|                   <td> | ||||
|                     <div class="btn-group"> | ||||
|                       <label class="btn btn-default btn-xs" ng-click="editHost(host)" ><span class="fa fa-edit"></span></label> | ||||
|                       <label class="btn btn-danger btn-xs" confirm="Are you sure you want to delete host and remove it from all groups?" ng-click="deleteHost($index)" ><span class="fa fa-trash-o"></span></label> | ||||
|                     </div> | ||||
|                   </td> | ||||
|                 </tr> | ||||
|                 </tbody> | ||||
|               </table> | ||||
|             </div> | ||||
| 
 | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|   </div> | ||||
|   <div class="row"> | ||||
|     <div class="col-md-6"> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|   </div> | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| <div class="col-md-4"> | ||||
| 
 | ||||
|   <div ng-readonly="!editInventory.value" ui-ace="{theme:'twilight',document:'INI',mode:'ini',onChange:codeChanged,onLoad:aceLoaded}" ng-model="selectedInventory.content"> | ||||
| 
 | ||||
|   </div> | ||||
| 
 | ||||
| </div> | ||||
							
								
								
									
										10
									
								
								client/app/designer/inventory/inventory.routes.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								client/app/designer/inventory/inventory.routes.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export default function($stateProvider) { | ||||
|   'ngInject'; | ||||
|   $stateProvider | ||||
|     .state('designer.inventory', { | ||||
|       url: '/inventory', | ||||
|       template: '<inventory></inventory>' | ||||
|     }); | ||||
| } | ||||
|  | @ -0,0 +1,82 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function newGroupController($scope,$uibModalInstance, yamlFile, ansible, selectedProject, editGroup, YAML, $timeout) { | ||||
|   'ngInject'; | ||||
|   $scope.newGroup = editGroup || {name:null}; | ||||
|   $scope.variableViewType = {type:'YAML'}; | ||||
| 
 | ||||
|   $scope.complexVar = {}; | ||||
|   $scope.complexVarString = {}; | ||||
| 
 | ||||
|   console.log("$scope.newGroup.name" + $scope.newGroup.name); | ||||
| 
 | ||||
|   $scope.aceLoaded = function (_editor) { | ||||
|     _editor.$blockScrolling = Infinity; | ||||
|   }; | ||||
| 
 | ||||
|   if($scope.newGroup.members){ | ||||
|     $scope.newGroup.members = $scope.newGroup.members.join(','); | ||||
|   } | ||||
| 
 | ||||
|   if($scope.newGroup.name){ | ||||
|     $scope.getGroupLoading = true; | ||||
|     ansible.getGroupVarsFile($scope.newGroup.name, | ||||
|       function(response){ | ||||
|         $scope.getGroupLoading = false; | ||||
|         $scope.complexVarStringYaml = response.data; | ||||
|         $scope.complexVar = YAML.parse(response.data); | ||||
| 
 | ||||
|         $timeout(function(){ | ||||
|           $scope.$broadcast('membersUpdated') | ||||
|         },100); | ||||
| 
 | ||||
|       },function(response){ | ||||
|         $scope.getGroupLoading = false; | ||||
|         $scope.err_msg = response.data; | ||||
|       }) | ||||
|   } | ||||
| 
 | ||||
|   $scope.$watch('complexVar',function(){ | ||||
|     $scope.complexVarString = JSON.stringify($scope.complexVar, null, '\t'); | ||||
|     $scope.complexVarStringYaml = yamlFile.jsonToYamlFile($scope.complexVar, 'Group Variables - ' + $scope.newGroup.name); | ||||
|   }, true); | ||||
| 
 | ||||
|   $scope.$watch('newGroup',function(){ | ||||
|     $scope.complexVarString = JSON.stringify($scope.complexVar, null, '\t'); | ||||
|     $scope.complexVarStringYaml = yamlFile.jsonToYamlFile($scope.complexVar, 'Group Variables - ' + $scope.newGroup.name); | ||||
|   }, true); | ||||
| 
 | ||||
|   $scope.createGroup = function(){ | ||||
|     $scope.createGroupLoading = true; | ||||
|     ansible.updateGroupVarsFile($scope.newGroup.name,$scope.complexVarStringYaml, | ||||
|       function(response){ | ||||
|         $scope.createGroupLoading = false; | ||||
|         console.log("Success"); | ||||
|         $scope.ok() | ||||
|       },function(response){ | ||||
|         $scope.createGroupLoading = false; | ||||
|         $scope.err_msg = response.data; | ||||
|       }); | ||||
|   }; | ||||
| 
 | ||||
|   $scope.ok = function () { | ||||
|     var resultGroup = {name:$scope.newGroup.name}; | ||||
| 
 | ||||
|     if($scope.newGroup.members){ | ||||
|       resultGroup.members = $scope.newGroup.members.split(','); | ||||
|       $scope.newGroup.members = $scope.newGroup.members.split(',') | ||||
|     } | ||||
| 
 | ||||
|     $uibModalInstance.close(resultGroup); | ||||
|   }; | ||||
| 
 | ||||
|   $scope.cancel = function () { | ||||
|     $uibModalInstance.dismiss('cancel'); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.new_group', []) | ||||
|   .controller('NewGroupController', newGroupController) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: NewGroupCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.new_group')); | ||||
| 
 | ||||
|   var NewGroupCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     NewGroupCtrl = $controller('NewGroupCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										45
									
								
								client/app/designer/inventory/new_group/new_group.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								client/app/designer/inventory/new_group/new_group.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| <!-- Modal content--> | ||||
| <div class="modal-content"> | ||||
|   <div class="modal-header"> | ||||
|     <h4 class="modal-title">New Group</h4> | ||||
|   </div> | ||||
|   <div class="modal-body"> | ||||
| 
 | ||||
|     <p class="form-group"> | ||||
|       <label>Group Name</label> | ||||
|       <input type="text" ng-model="newGroup.name" class="form-control"> | ||||
|     </p> | ||||
| 
 | ||||
|     <p class="form-group"> | ||||
|       <label>Group Members</label> | ||||
|       <input type="text" ng-model="newGroup.members" class="form-control" placeholder="Hosts list separated by coma"> | ||||
|     </p> | ||||
| 
 | ||||
|     <label> Group Variables </label> | ||||
|     <div class="row"> | ||||
|       <div class="col-md-6"> | ||||
|         <complex-var members="complexVar" path="'Parent'" type="'object'" input-width="'400px'"> | ||||
| 
 | ||||
|         </complex-var> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="col-md-6"> | ||||
|         <input name="typeRadioGroup" type="radio" ng-model="variableViewType.type" ng-value="'JSON'"> JSON | ||||
|         <input name="typeRadioGroup" type="radio" ng-model="variableViewType.type" ng-value="'YAML'"> YAML | ||||
|         <div ng-readonly="true" ui-ace="{th1eme:'twilight',document:'yaml',mode:'yaml',onChange:codeChanged,onLoad:aceLoaded}" ng-show="variableViewType.type == 'YAML'" ng-model="complexVarStringYaml" style="max-height: 200px;"> | ||||
| 
 | ||||
|         </div> | ||||
|         <div ng-readonly="true" ui-ace="{th1eme:'twilight',document:'json',mode:'json',onChange:codeChanged,onLoad:aceLoaded}" ng-show="variableViewType.type == 'JSON'"  ng-model="complexVarString" style="max-height: 200px;"> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
|   </div> | ||||
|   <div class="alert alert-danger" ng-if="err_msg">{{err_msg}}</div> | ||||
|   <div class="modal-footer"> | ||||
|     <button class="btn btn-primary" ng-disabled="!newGroup.name" ng-click="createGroup()">Create <span ng-if="createGroupLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
|     <button class="btn btn-default" type="button" ng-click="cancel()">Close</button> | ||||
|   </div> | ||||
| </div> | ||||
|  | @ -0,0 +1,69 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function newHostController($scope,$uibModalInstance, yamlFile, ansible, selectedProject, editHost, YAML, $timeout) { | ||||
|   $scope.newHost = editHost || {name:null}; | ||||
| 
 | ||||
|   $scope.variableViewType = {type:'YAML'}; | ||||
| 
 | ||||
|   $scope.complexVar = {}; | ||||
|   $scope.complexVarString = {}; | ||||
| 
 | ||||
|   if($scope.newHost.name){ | ||||
|     $scope.getHostLoading = true; | ||||
|     ansible.getHostVarsFile($scope.newHost.name, | ||||
|       function(response){ | ||||
|         $scope.getHostLoading = false; | ||||
|         $scope.complexVarStringYaml = response.data; | ||||
|         $scope.complexVar = YAML.parse(response.data); | ||||
|         $timeout(function(){ | ||||
|           $scope.$broadcast('membersUpdated') | ||||
|         },100); | ||||
| 
 | ||||
|       },function(response){ | ||||
|         $scope.getHostLoading = false; | ||||
|         $scope.err_msg = response.data; | ||||
|       }) | ||||
|   } | ||||
| 
 | ||||
|   $scope.$watch('complexVar',function(){ | ||||
|     $scope.complexVarString = JSON.stringify($scope.complexVar, null, '\t'); | ||||
|     $scope.complexVarStringYaml = yamlFile.jsonToYamlFile($scope.complexVar, 'Host Variables - ' + $scope.newHost.name); | ||||
|   }, true); | ||||
| 
 | ||||
|   $scope.$watch('newHost',function(){ | ||||
|     $scope.complexVarString = JSON.stringify($scope.complexVar, null, '\t'); | ||||
|     $scope.complexVarStringYaml = yamlFile.jsonToYamlFile($scope.complexVar, 'Host Variables - ' + $scope.newHost.name); | ||||
|   }, true); | ||||
| 
 | ||||
|   $scope.aceLoaded = function(_editor){ | ||||
|     _editor.$blockScrolling = Infinity; | ||||
|   }; | ||||
| 
 | ||||
|   $scope.createHost = function(){ | ||||
|     $scope.createHostLoading = true; | ||||
|     ansible.updateHostVarsFile($scope.newHost.name,$scope.complexVarStringYaml, | ||||
|       function(response){ | ||||
|         $scope.createHostLoading = false; | ||||
|         console.log("Success"); | ||||
|         $scope.ok() | ||||
|       },function(response){ | ||||
|         $scope.createHostLoading = false; | ||||
|         $scope.err_msg = response.data; | ||||
|       }); | ||||
|   }; | ||||
| 
 | ||||
|   $scope.ok = function () { | ||||
|     if(!$scope.newHost.members)$scope.newHost.members = 'Un grouped'; | ||||
|     $uibModalInstance.close($scope.newHost); | ||||
|   }; | ||||
| 
 | ||||
|   $scope.cancel = function () { | ||||
|     $uibModalInstance.dismiss('cancel'); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.new_host', []) | ||||
|   .controller('NewHostController', newHostController) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: NewHostCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.new_host')); | ||||
| 
 | ||||
|   var NewHostCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     NewHostCtrl = $controller('NewHostCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										46
									
								
								client/app/designer/inventory/new_host/new_host.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								client/app/designer/inventory/new_host/new_host.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| <!-- Modal content--> | ||||
| <div class="modal-content"> | ||||
|   <div class="modal-header"> | ||||
|     <h4 class="modal-title">New Host</h4> | ||||
|   </div> | ||||
|   <div class="modal-body"> | ||||
| 
 | ||||
|     <p class="form-group"> | ||||
|       <label>Host Name</label> | ||||
|       <input type="text" ng-model="newHost.name" class="form-control"> | ||||
|     </p> | ||||
| 
 | ||||
|     <p class="form-group"> | ||||
|       <label>Member of Groups</label> | ||||
|       <input type="text" ng-model="newHost.members" class="form-control" placeholder="Hosts list separated by coma"> | ||||
|     </p> | ||||
| 
 | ||||
|     <label> Host Variables </label> | ||||
|     <div class="row"> | ||||
|       <div class="col-md-6"> | ||||
|         <complex-var members="complexVar" path="'Parent'" type="'object'" input-width="'400px'"> | ||||
| 
 | ||||
|         </complex-var> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="col-md-6"> | ||||
|         <input name="typeRadioHost" type="radio" ng-model="variableViewType.type" ng-value="'JSON'"> JSON | ||||
|         <input name="typeRadioHost" type="radio" ng-model="variableViewType.type" ng-value="'YAML'"> YAML | ||||
|         <div ng-readonly="true" ui-ace="{th1eme:'twilight',document:'yaml',mode:'yaml',onChange:codeChanged}" ng-show="variableViewType.type == 'YAML'" ng-model="complexVarStringYaml" style="max-height: 200px;"> | ||||
| 
 | ||||
|         </div> | ||||
|         <div ng-readonly="true" ui-ace="{th1eme:'twilight',document:'json',mode:'json',onChange:codeChanged,onLoad:aceLoaded}" ng-show="variableViewType.type == 'JSON'"  ng-model="complexVarString" style="max-height: 200px;"> | ||||
| 
 | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
|   </div> | ||||
|   <div class="alert alert-danger" ng-if="err_msg">{{err_msg}}</div> | ||||
|   <div class="modal-footer"> | ||||
|     <button class="btn btn-primary" ng-disabled="!newHost.name" ng-click="createHost()">Create <span ng-if="createHostLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
|     <button class="btn btn-default" type="button" ng-click="cancel()">Close</button> | ||||
|   </div> | ||||
| </div> | ||||
|  | @ -0,0 +1,40 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function newInventoryController($scope,$uibModalInstance,ansible,selectedProject) { | ||||
|   $scope.newInventory = {name:null}; | ||||
| 
 | ||||
|   $scope.createInventoryLoading = false; | ||||
| 
 | ||||
|   $scope.createInventory = function(){ | ||||
| 
 | ||||
|     if($scope.newInventory.name.match(/\./)){ | ||||
|       $scope.err_msg = "Inventory files should not have extension" | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     $scope.createInventoryLoading = true; | ||||
|     ansible.createInventory($scope.newInventory.name,'# Inventory File - ' + $scope.newInventory.name, | ||||
|       function(response){ | ||||
|         $scope.createInventoryLoading = false; | ||||
|         $scope.ok(); | ||||
|       }, | ||||
|       function(response){ | ||||
|         $scope.createInventoryLoading = false; | ||||
|         $scope.err_msg = response.data; | ||||
|       }) | ||||
|   }; | ||||
| 
 | ||||
|   $scope.ok = function () { | ||||
|     $uibModalInstance.close(null); | ||||
|   }; | ||||
| 
 | ||||
|   $scope.cancel = function () { | ||||
|     $uibModalInstance.dismiss('cancel'); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.new_inventory', []) | ||||
|   .controller('NewInventoryController', newInventoryController) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: NewInventoryCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.new_inventory')); | ||||
| 
 | ||||
|   var NewInventoryCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     NewInventoryCtrl = $controller('NewInventoryCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
|  | @ -0,0 +1,21 @@ | |||
| <!-- Modal content--> | ||||
| <div class="modal-content"> | ||||
|   <div class="modal-header"> | ||||
|     <h4 class="modal-title">New Inventory</h4> | ||||
|   </div> | ||||
|   <div class="modal-body"> | ||||
| 
 | ||||
|     <p class="form-group"> | ||||
|       <label>Inventory Name</label> | ||||
|       <input type="text" ng-model="newInventory.name" class="form-control"> | ||||
|     </p> | ||||
| 
 | ||||
|     <div class="alert alert-info"> Inventory files should be named without an extension </div> | ||||
| 
 | ||||
|   </div> | ||||
|   <div class="alert alert-danger" ng-if="err_msg">{{err_msg}}</div> | ||||
|   <div class="modal-footer"> | ||||
|     <button class="btn btn-primary" ng-disabled="!newInventory.name" ng-click="createInventory()">Create <span ng-if="createInventoryLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
|     <button class="btn btn-default" type="button" ng-click="cancel()">Close</button> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										134
									
								
								client/app/designer/playbook/new_play/new_play.controller.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								client/app/designer/playbook/new_play/new_play.controller.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,134 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function newPlayController($scope, $uibModalInstance, ansible, plays, selectedPlayIndex) { | ||||
|   $scope.loading_msg = ''; | ||||
|   $scope.title = "Create Play"; | ||||
|   $scope.editMode = false; | ||||
|   $scope.editHostMode = false; | ||||
| 
 | ||||
|   var selectedPlay; | ||||
|   if(selectedPlayIndex > -1){ | ||||
|     selectedPlay = plays[selectedPlayIndex]; | ||||
|     $scope.title = "Edit Play"; | ||||
|     $scope.editMode = true; | ||||
|     if(selectedPlay && selectedPlay.tags)$scope.tags = selectedPlay.tags.join(','); | ||||
|   } | ||||
| 
 | ||||
|   $scope.newPlay = selectedPlay || {}; | ||||
| 
 | ||||
|   $scope.newPlay_roles = $scope.newPlay.roles; | ||||
| 
 | ||||
|   $scope.createPlayLoading = false; | ||||
| 
 | ||||
|   $scope.createPlay = function () { | ||||
|     $scope.ok($scope.newPlay) | ||||
|   }; | ||||
| 
 | ||||
|   $scope.ok = function (newPlay) { | ||||
|     if($scope.tags) | ||||
|       newPlay.tags = $scope.tags.split(','); | ||||
| 
 | ||||
| 
 | ||||
|     if($scope.newPlay_roles && $scope.newPlay_roles.length){ | ||||
|       var roles = []; | ||||
|       angular.forEach($scope.newPlay_roles,function(role){ | ||||
|         roles.push(role.text) | ||||
|       }); | ||||
|       newPlay.roles = roles; | ||||
|     }else if(newPlay.roles){ | ||||
|       delete newPlay.roles; | ||||
|     } | ||||
| 
 | ||||
|     $uibModalInstance.close(newPlay); | ||||
|   }; | ||||
| 
 | ||||
|   $scope.cancel = function () { | ||||
|     $uibModalInstance.dismiss('cancel'); | ||||
|   }; | ||||
| 
 | ||||
| 
 | ||||
|   $scope.getHostsFromInventory = function(){ | ||||
| 
 | ||||
|     var hosts = []; | ||||
| 
 | ||||
|     angular.forEach($scope.inventory_data_json.hosts, function(host){ | ||||
|       hosts.push({name:host}) | ||||
|     }); | ||||
| 
 | ||||
|     angular.forEach($scope.inventory_data_json.groups, function(group){ | ||||
|       if(group.name !== 'Un grouped') | ||||
|         hosts.push({name:group.name}) | ||||
|     }); | ||||
| 
 | ||||
|     return hosts; | ||||
|   }; | ||||
| 
 | ||||
|   $scope.listOfInventoryFiles = function(){ | ||||
|     $scope.loading_msg = 'Loading Inventory Files'; | ||||
|     ansible.getInventoryList(function(response){ | ||||
|         $scope.loading_msg = ''; | ||||
|         $scope.inventoryFiles = response.data; | ||||
|       }, | ||||
|       function(response){ | ||||
|         $scope.loading_msg = ''; | ||||
|         $scope.err_msg = response.data | ||||
|       }) | ||||
|   }; | ||||
| 
 | ||||
| 
 | ||||
|   $scope.listOfRoles = function(){ | ||||
|     $scope.loading_msg = 'Loading Roles'; | ||||
|     ansible.getRoleList(function(response){ | ||||
|         $scope.loading_msg = ''; | ||||
|         $scope.roleList = response.data; | ||||
|       }, | ||||
|       function(response){ | ||||
|         $scope.loading_msg = ''; | ||||
|         $scope.err_msg = response.data | ||||
|       }) | ||||
|   }; | ||||
| 
 | ||||
|   $scope.inventorySelected = function(selectedInventoryFile){ | ||||
|     $scope.loading_msg = 'Loading Hosts'; | ||||
|     ansible.readInventory(selectedInventoryFile, | ||||
|       function(response){ | ||||
|         $scope.loading_msg = ''; | ||||
|         $scope.inventory_data_json = ansible.parseINIString(response.data); | ||||
|         $scope.hosts = $scope.getHostsFromInventory(); | ||||
| 
 | ||||
|       },function(response){ | ||||
|         $scope.loading_msg = ''; | ||||
|         $scope.err_msg = response.data | ||||
|       }) | ||||
|   }; | ||||
| 
 | ||||
|   $scope.getHostObject = function(hostname){ | ||||
|     var result = $scope.hosts.filter(function(host){ | ||||
|       return host.name == hostname | ||||
|     }); | ||||
| 
 | ||||
|     if(result.length){ | ||||
|       return result[0] | ||||
|     } | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
|   $scope.listOfInventoryFiles(); | ||||
|   $scope.listOfRoles(); | ||||
| 
 | ||||
|   $scope.loadTags = function(query){ | ||||
|     if($scope.roleList){ | ||||
|       var tempList = $scope.roleList.filter(function(role){ | ||||
|         return role.indexOf(query) > -1 | ||||
|       }); | ||||
| 
 | ||||
|       return tempList | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.new_play', []) | ||||
|   .controller('NewPlayController', newPlayController) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: NewPlayCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.new_play')); | ||||
| 
 | ||||
|   var NewPlayCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     NewPlayCtrl = $controller('NewPlayCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										96
									
								
								client/app/designer/playbook/new_play/new_play.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								client/app/designer/playbook/new_play/new_play.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,96 @@ | |||
| <!-- Modal content--> | ||||
| <div class="modal-content"> | ||||
|   <div class="modal-header"> | ||||
|     <button type="button" class="close" data-dismiss="modal">×</button> | ||||
|     <h4 class="modal-title">{{title}}</h4> | ||||
|   </div> | ||||
|   <div class="modal-body"> | ||||
|     <div class="row"> | ||||
|       <div class="col-md-6"> | ||||
| 
 | ||||
|           <p class="form-group"> | ||||
|             <label>Name</label> | ||||
|             <input ng-model="newPlay.name" type="text" class="form-control" placeholder="Play Name"> | ||||
|           </p> | ||||
| 
 | ||||
|           <p class="form-group"> | ||||
|             <label>Tags</label> | ||||
|             <input ng-model="tags" type="text" class="form-control" placeholder="Tags separated by comma"> | ||||
|           </p> | ||||
| 
 | ||||
|           <p class="form-group" ng-show="!editMode || editHostMode"> | ||||
|             <label>Inventory Files</label> | ||||
|             <select class="form-control" ng-disabled="loading_msg" ng-change="inventorySelected(selectedInventoryFile);" ng-model="selectedInventoryFile" ng-options="inventoryFile as inventoryFile for inventoryFile in inventoryFiles"> | ||||
|             </select> | ||||
|           </p> | ||||
| 
 | ||||
|           <p class="form-group" ng-show="!editMode || editHostMode"> | ||||
|             <label>Hosts</label> | ||||
|             <select class="form-control" ng-disabled="loading_msg" ng-model="newPlay.hosts" ng-options="host.name as host.name for host in hosts"> | ||||
|             </select> | ||||
|           </p> | ||||
| 
 | ||||
|           <p class="form-group" ng-show="editMode && !editHostMode"> | ||||
|             <label>Hosts </label> | ||||
|             <input ng-model="newPlay.hosts" ng-disabled="true" type="text" class="form-control" placeholder="Host Name"> | ||||
|           </p> | ||||
|           <div class="input-group-btn" ng-show="editMode && !editHostMode"> | ||||
|             <button class="btn btn-default" ng-click="editHostMode = true"> <span class="fa fa-edit"></span> Edit Hosts </button> | ||||
|           </div> | ||||
| 
 | ||||
|           <label>Roles</label> | ||||
|           <tags-input ng-model="newPlay_roles" add-from-autocomplete-only="true" placeholder="Add roles to play"> | ||||
|             <auto-complete source="loadTags($query)" min-length="0" load-on-empty="true"  load-on-focus="true" load-on-down-arrow="true" ></auto-complete> | ||||
|           </tags-input> | ||||
| 
 | ||||
|       </div> | ||||
|       <div class="col-md-6"> | ||||
| 
 | ||||
|         <h4 ng-if="selectedHostInfo.ip">IP - {{selectedHostInfo.ip}}</h4> | ||||
| 
 | ||||
|         <h4 ng-if="selectedHostInfo.members">Members:</h4> | ||||
|         <ul class="list-group" ng-if="selectedHostInfo.members"> | ||||
|           <li class="list-group-item" ng-repeat="member in selectedHostInfo.members.split(',')">{{member}}</li> | ||||
|         </ul> | ||||
| 
 | ||||
|         <div ng-if="hostGroups"> | ||||
|           <h4 >Member Of:</h4> | ||||
|           <ul class="list-group"> | ||||
|             <li class="list-group-item" ng-repeat="group in hostGroups">{{group.name}}</li> | ||||
|           </ul> | ||||
|         </div> | ||||
| 
 | ||||
|         <!--<div ui-ace="{theme:'twilight',document:'INI',mode:'ini',showGutter:false}" ng-model="hostInfo"> | ||||
| 
 | ||||
|         </div>--> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="row" ng-if="selectedHostInfo"> | ||||
|       <h4>Variables</h4> | ||||
|       <div class="input-group"> | ||||
|         <span class="input-group-addon" >Groups</span> | ||||
|         <select class="form-control" ng-change="variableGroupSelected(selectedVariableGroup.value)" ng-model="selectedVariableGroup.value" ng-options="group.name as group.name for group in [selectedHostInfo].concat(hostGroups)"> | ||||
|         </select> | ||||
|       </div> | ||||
|       <div ui-ace="{theme:'twilight',document:'INI',mode:'ini'}" ng-model="variableInfo"> | ||||
| 
 | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class="alert alert-danger" ng-if="err_msg">{{err_msg}}</div> | ||||
|   <div class="modal-footer"> | ||||
| 
 | ||||
| 
 | ||||
|     <div class="row"> | ||||
|       <div class="col-md-3" style="text-align: left"> | ||||
|           <span ng-show="loading_msg" class="fa fa-spin fa-spinner"></span> {{loading_msg}} | ||||
|       </div> | ||||
|       <div class="col-md-9" style="text-align: right"> | ||||
|         <button class="btn btn-primary" ng-click="createPlay()">Save <span ng-if="createPlayLoading" class="fa fa-spinner fa-spin"></span> </button> | ||||
|         <button class="btn btn-default" type="button" ng-click="cancel()">Close</button> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
|   </div> | ||||
| </div> | ||||
|  | @ -0,0 +1,35 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function newPlaybookController($scope,$uibModalInstance,ansible) { | ||||
|   $scope.newPlaybook = {name:null}; | ||||
| 
 | ||||
|   $scope.createPlaybookLoading = false; | ||||
| 
 | ||||
|   $scope.createPlaybook = function(){ | ||||
|     $scope.createPlaybookLoading = true; | ||||
| 
 | ||||
|     ansible.createPlaybook($scope.newPlaybook.name + '.yml',"", | ||||
|       function(response){ | ||||
|         $scope.createPlaybookLoading = false; | ||||
|         $scope.ok(); | ||||
|       }, | ||||
|       function(response){ | ||||
|         $scope.createPlaybookLoading = false; | ||||
|         $scope.err_msg = response.data; | ||||
|       }) | ||||
|   }; | ||||
| 
 | ||||
|   $scope.ok = function () { | ||||
|     $uibModalInstance.close(null); | ||||
|   }; | ||||
| 
 | ||||
|   $scope.cancel = function () { | ||||
|     $uibModalInstance.dismiss('cancel'); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.new_playbook', []) | ||||
|   .controller('NewPlaybookController', newPlaybookController) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: NewPlaybookCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.new_playbook')); | ||||
| 
 | ||||
|   var NewPlaybookCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     NewPlaybookCtrl = $controller('NewPlaybookCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										19
									
								
								client/app/designer/playbook/new_playbook/new_playbook.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								client/app/designer/playbook/new_playbook/new_playbook.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| <!-- Modal content--> | ||||
| <div class="modal-content"> | ||||
|   <div class="modal-header"> | ||||
|     <h4 class="modal-title">New Playbook</h4> | ||||
|   </div> | ||||
|   <div class="modal-body"> | ||||
| 
 | ||||
|     <p class="form-group"> | ||||
|       <label>Playbook Name</label> | ||||
|       <input type="text" ng-model="newPlaybook.name" class="form-control"> | ||||
|     </p> | ||||
| 
 | ||||
|   </div> | ||||
|   <div class="alert alert-danger" ng-if="err_msg">{{err_msg}}</div> | ||||
|   <div class="modal-footer"> | ||||
|     <button class="btn btn-primary" ng-disabled="!newPlaybook.name" ng-click="createPlaybook()">Create <span ng-if="createPlaybookLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
|     <button class="btn btn-default" type="button" ng-click="cancel()">Close</button> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										328
									
								
								client/app/designer/playbook/playbook.component.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										328
									
								
								client/app/designer/playbook/playbook.component.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,328 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| const uiRouter = require('angular-ui-router'); | ||||
| 
 | ||||
| import routes from './playbook.routes'; | ||||
| 
 | ||||
| export class PlaybookComponent { | ||||
|   /*@ngInject*/ | ||||
|   constructor($scope,$uibModal,YAML,ansible,yamlFile) { | ||||
|     'ngInject'; | ||||
|     $scope.isopen = {playbooks:true,plays:false,tasks:false}; | ||||
| 
 | ||||
|     $scope.selectedPlaybook = {playbook: "",content: ""}; | ||||
|     $scope.selectedPlay = {play: ""}; | ||||
| 
 | ||||
|     $scope.showSaveButton = {}; | ||||
|     $scope.loadingButtons = {}; | ||||
| 
 | ||||
|     $scope.editPlaybook = {value:false}; | ||||
| 
 | ||||
|     $scope.loadingModuleCode = false; | ||||
| 
 | ||||
|     $scope.$on('projectLoaded',function(){ | ||||
|       $scope.getPlaybooks() | ||||
|     }); | ||||
| 
 | ||||
|     //To fix a warning message in console
 | ||||
|     $scope.aceLoaded = function(_editor){ | ||||
|       _editor.$blockScrolling = Infinity; | ||||
|     }; | ||||
| 
 | ||||
|     // --------------------------------------- PLAYBOOKS ----------------
 | ||||
| 
 | ||||
|     $scope.getPlaybooks = function(){ | ||||
|       ansible.getPlaybookList( | ||||
|         function(response){ | ||||
|           $scope.playbooks = response.data; | ||||
|         }, | ||||
|         function(response){ | ||||
|           console.log(response.data) | ||||
|         } | ||||
|       ) | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     if($scope.$parent.selectedProject && $scope.$parent.selectedProject.ansibleEngine){ | ||||
|       $scope.getPlaybooks() | ||||
|     } | ||||
| 
 | ||||
|     $scope.showPlaybookCode = function(playbook_name){ | ||||
|       $scope.loadingModuleCode = true; | ||||
| 
 | ||||
|       if(!playbook_name){ | ||||
|         $scope.selectedPlaybook.content = "Select a module"; | ||||
|         return; | ||||
|       } | ||||
|       ansible.readPlaybook(playbook_name,function(response) { | ||||
|         $scope.isopen.playbooks = true; | ||||
|         $scope.isopen.plays = true | ||||
|         $scope.loadingModuleCode = false; | ||||
|         $scope.selectedPlaybook.content = response.data.split("Stream :: close")[0]; | ||||
|         $scope.getPlaysFromPlayBook($scope.selectedPlaybook.content); | ||||
| 
 | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.$watch('selectedPlaybook.playbook',function(newValue,oldValue){ | ||||
|       if(newValue && newValue !== oldValue){ | ||||
|         $scope.selectedPlaybook.content = "Loading Code..."; | ||||
|         $scope.showPlaybookCode(newValue); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     $scope.$watch('selectedPlay.play',function(newValue,oldValue){ | ||||
|       if(newValue && newValue !== oldValue){ | ||||
|         $scope.selectedPlay.play.tasks = $scope.selectedPlay.play.tasks || []; | ||||
|         $scope.isopen.playbooks = false; | ||||
|         $scope.isopen.plays = false; | ||||
|         $scope.isopen.tasks = true; | ||||
|         $scope.isopen.roles = true; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     $scope.showCreatePlaybookModal = function(){ | ||||
|       var modalInstance = $uibModal.open({ | ||||
|         animation: true, | ||||
|         /*templateUrl: 'createTaskContent.html',*/ | ||||
|         templateUrl: 'app/designer/playbook/new_playbook/new_playbook.html', | ||||
|         controller: 'NewPlaybookController', | ||||
|         size: 'md', | ||||
|         backdrop  : 'static', | ||||
|         keyboard  : false, | ||||
|         closeByEscape : false, | ||||
|         closeByDocument : false, | ||||
|         resolve: { | ||||
|           selectedProject: function(){ | ||||
|             return $scope.$parent.selectedProject | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       modalInstance.result.then(function () { | ||||
|         $scope.getPlaybooks(); | ||||
|       }, function () { | ||||
| 
 | ||||
|       }); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     $scope.editPlaybookMethod = function(){ | ||||
|       $scope.editPlaybook.value = true; | ||||
|       $scope.uneditedPlaybokContents = $scope.selectedPlaybook.content | ||||
|     }; | ||||
| 
 | ||||
|     $scope.cancelPlaybookChanges = function(){ | ||||
|       $scope.editPlaybook.value = false; | ||||
|       $scope.selectedPlaybook.content = $scope.uneditedPlaybokContents | ||||
|     }; | ||||
| 
 | ||||
|     $scope.savePlaybook = function(buttonVariable){ | ||||
|       console.log("Saving Playbook") | ||||
|       $scope.loadingButtons[buttonVariable] = true; | ||||
| 
 | ||||
|       ansible.createPlaybook($scope.selectedPlaybook.playbook,$scope.selectedPlaybook.content, | ||||
|         function(response){ | ||||
|           $scope.loadingButtons[buttonVariable] = false; | ||||
|           $scope.showSaveButton[buttonVariable] = false; | ||||
|           $scope.editPlaybook.value = false; | ||||
|         }, | ||||
|         function(response){ | ||||
|           $scope.loadingButtons[buttonVariable] = false; | ||||
|           $scope.showSaveButton[buttonVariable] = false; | ||||
|           $scope.err_msg = response.data; | ||||
|         }) | ||||
|     }; | ||||
| 
 | ||||
|     $scope.deletePlaybook = function(){ | ||||
|       $scope.deletePlaybookLoading = true; | ||||
|       ansible.deletePlaybook($scope.selectedPlaybook.playbook, | ||||
|         function(response){ | ||||
|           $scope.deletePlaybookLoading = false; | ||||
|           $scope.selectedPlaybook.playbook = ""; | ||||
|           $scope.getPlaybooks(); | ||||
|         }, | ||||
|         function(response){ | ||||
|           $scope.deletePlaybookLoading = false; | ||||
|           $scope.err_msg = response.data; | ||||
|         }) | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     //--------------- PLAY --------------
 | ||||
| 
 | ||||
|     $scope.showCreatePlayModal = function(selectedPlayIndex){ | ||||
|       var modalInstance = $uibModal.open({ | ||||
|         animation: true, | ||||
|         /*templateUrl: 'createPlayContent.html',*/ | ||||
|         templateUrl: 'app/designer/playbook/new_play/new_play.html', | ||||
|         controller: 'NewPlayController', | ||||
|         size: 'lg', | ||||
|         backdrop  : 'static', | ||||
|         keyboard  : false, | ||||
|         closeByEscape : false, | ||||
|         closeByDocument : false, | ||||
|         resolve: { | ||||
|           selectedProject: function () { | ||||
|             return $scope.$parent.selectedProject; | ||||
|           }, | ||||
|           plays: function () { | ||||
|             return $scope.plays; | ||||
|           }, | ||||
|           selectedPlayIndex: function () { | ||||
|             return selectedPlayIndex; | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       modalInstance.result.then( | ||||
|         function (newPlay) { | ||||
|           if(selectedPlayIndex == null) | ||||
|             $scope.plays.push(newPlay); | ||||
| 
 | ||||
|           $scope.clearEmptyTasks($scope.plays); | ||||
| 
 | ||||
|           $scope.selectedPlaybook.content = yamlFile.jsonToYamlFile($scope.plays, 'Playbook file: ' +  $scope.selectedPlaybook.playbook) | ||||
|           $scope.savePlaybook(); | ||||
|         }, function () { | ||||
| 
 | ||||
|         }); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     // FUNCTION - GET PLAYS FROM PLAYBOOK
 | ||||
| 
 | ||||
|     $scope.getPlaysFromPlayBook = function(playbookYamlData){ | ||||
|       $scope.plays = YAML.parse(playbookYamlData) || [] | ||||
|     }; | ||||
| 
 | ||||
|     // FUNCTION - DELETE PLAY
 | ||||
| 
 | ||||
|     $scope.deletePlay = function(index){ | ||||
|       $scope.plays.splice(index,1); | ||||
|       $scope.selectedPlaybook.content = yamlFile.jsonToYamlFile($scope.plays, 'Playbook file: ' +  $scope.selectedPlaybook.playbook) | ||||
|       $scope.savePlaybook(); | ||||
|       $scope.selectedPlay = {play: ""}; | ||||
|     }; | ||||
| 
 | ||||
|     // ------------------- EXECUTE PLAYBOOK MODAL -------------
 | ||||
| 
 | ||||
|     $scope.executeAnsiblePlayBook = function(tags,executionType,executionName,selectedPlay){ | ||||
|       console.log("Tags type" + typeof tags) | ||||
|       var modalInstance = $uibModal.open({ | ||||
|         animation: true, | ||||
|         /*templateUrl: 'createTaskContent.html',*/ | ||||
|         templateUrl: 'app/designer/execution/executeModal.html', | ||||
|         controller: 'ExecutionController', | ||||
|         size: 'lg', | ||||
|         backdrop  : 'static', | ||||
|         keyboard  : false, | ||||
|         closeByEscape : false, | ||||
|         closeByDocument : false, | ||||
|         resolve: { | ||||
|           tags: function(){ | ||||
|             return tags | ||||
|           }, | ||||
|           selectedProject: function(){ | ||||
|             return $scope.$parent.selectedProject | ||||
|           }, | ||||
|           selectedPlaybook: function(){ | ||||
|             return $scope.selectedPlaybook | ||||
|           }, | ||||
|           selectedPlay: function(){ | ||||
|             return selectedPlay | ||||
|           }, | ||||
|           executionType: function(){ | ||||
|             return executionType | ||||
|           }, | ||||
|           executionName: function(){ | ||||
|             return executionName | ||||
|           }, | ||||
|           readOnly: function(){ | ||||
|             return false | ||||
|           }, | ||||
|           runData: function(){ | ||||
|             return null | ||||
|           }, | ||||
|           projectFolder: function(){ | ||||
|             return null | ||||
|           }, | ||||
|           roleName: function(){ | ||||
|             return null | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     $scope.clearEmptyTasks = function(plays){ | ||||
|       //Check for empty tasks list
 | ||||
|       angular.forEach(plays,function(play){ | ||||
|         if((play.tasks && !play.tasks.length) || !play.tasks){ | ||||
|           delete play.tasks | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     // ---------------------- TASKS -------------------
 | ||||
| 
 | ||||
|     $scope.updatePlaybookFileContent = function(save,buttonVariable){ | ||||
| 
 | ||||
|       var playsCopy = angular.copy($scope.plays); | ||||
| 
 | ||||
|       $scope.clearEmptyTasks(playsCopy); | ||||
| 
 | ||||
|       $scope.selectedPlaybook.content = yamlFile.jsonToYamlFile(playsCopy, 'Playbook file: ' +  $scope.selectedPlaybook.playbook) | ||||
|       if(save) | ||||
|         $scope.savePlaybook(buttonVariable); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.moveUp = function(list,index,buttonVariable){ | ||||
|       if(!$scope.preChangeData) $scope.preChangeData = angular.copy(list); | ||||
|       var temp = angular.copy(list[index]); | ||||
|       list[index] = list[index-1]; | ||||
|       list[index-1] = temp; | ||||
| 
 | ||||
|       $scope.updatePlaybookFileContent(false); | ||||
| 
 | ||||
|       $scope.showSaveButton[buttonVariable] = true | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     $scope.cancelChange = function(buttonVariable){ | ||||
|       if($scope.preChangeData){ | ||||
|         $scope.plays = angular.copy($scope.preChangeData); | ||||
|         $scope.preChangeData = null | ||||
| 
 | ||||
|       } | ||||
|       $scope.updatePlaybookFileContent(false); | ||||
| 
 | ||||
|       $scope.showSaveButton[buttonVariable] = false | ||||
|     }; | ||||
| 
 | ||||
|     $scope.moveDown = function(list,index,buttonVariable){ | ||||
|       if(!$scope.preChangeData) $scope.preChangeData = angular.copy(list); | ||||
|       var temp = angular.copy(list[index]); | ||||
|       list[index] = list[index+1]; | ||||
|       list[index+1] = temp; | ||||
| 
 | ||||
|       $scope.updatePlaybookFileContent(false); | ||||
| 
 | ||||
|       $scope.showSaveButton[buttonVariable] = true | ||||
| 
 | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.playbook', [uiRouter]) | ||||
|   .config(routes) | ||||
|   .component('playbook', { | ||||
|     template: require('./playbook.html'), | ||||
|     controller: PlaybookComponent, | ||||
|     controllerAs: 'playbookCtrl' | ||||
|   }) | ||||
|   .name; | ||||
							
								
								
									
										17
									
								
								client/app/designer/playbook/playbook.component.spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								client/app/designer/playbook/playbook.component.spec.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Component: PlaybookComponent', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.playbook')); | ||||
| 
 | ||||
|   var PlaybookComponent; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($componentController) { | ||||
|     PlaybookComponent = $componentController('playbook', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										0
									
								
								client/app/designer/playbook/playbook.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								client/app/designer/playbook/playbook.css
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										166
									
								
								client/app/designer/playbook/playbook.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								client/app/designer/playbook/playbook.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,166 @@ | |||
| <div class="col-md-6"> | ||||
|   <uib-accordion close-others="false"> | ||||
|     <div uib-accordion-group class="panel-default" is-open="isopen.playbooks"> | ||||
|       <uib-accordion-heading> | ||||
|         Playbooks <i class="pull-right glyphicon" | ||||
|                      ng-class="{'glyphicon-chevron-down': isopen.playbooks, 'glyphicon-chevron-right': !isopen.playbooks}"></i> | ||||
|       </uib-accordion-heading> | ||||
| 
 | ||||
|       <button class="btn btn-default" ng-click="showCreatePlaybookModal()">Create Playbook <span | ||||
|         class="fa fa-plus"></span></button> | ||||
|       <button class="btn btn-default" ng-if="!editPlaybook.value" ng-click="editPlaybookMethod()">Edit <span | ||||
|         ng-if="!showSaveButton.savePlaybookLoading" class="fa fa-edit"></span></button> | ||||
|       <button class="btn btn-primary" ng-if="editPlaybook.value" ng-disabled="!selectedPlaybook.playbook" | ||||
|               ng-click="savePlaybook('savePlaybookLoading')">Save <span ng-if="!showSaveButton.savePlaybookLoading" | ||||
|                                                                         class="fa fa-save"></span><span | ||||
|         ng-if="showSaveButton.savePlaybookLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
|       <button class="btn btn-warning" ng-if="editPlaybook.value" confirm="Are you sure you want to discard the changes?" | ||||
|               ng-click="cancelPlaybookChanges()">Cancel <span class="fa fa-times-circle"></span></button> | ||||
|       <button class="btn btn-danger" confirm="Are you sure you want to delete playbook? " | ||||
|               ng-disabled="!selectedPlaybook.playbook" ng-click="deletePlaybook()">Delete <span | ||||
|         ng-show="!deletePlaybookLoading" class="fa fa-save"></span><span ng-show="deletePlaybookLoading" | ||||
|                                                                          class="fa fa-spinner fa-spin"></span></button> | ||||
|       <button class="btn btn-success" ng-disabled="!selectedPlaybook.playbook" | ||||
|               ng-click="executeAnsiblePlayBook(null,'Playbook')">Play <span class="fa fa-play"></span></button> | ||||
|       <div class="table-responsive"> | ||||
|         <table class="table"> | ||||
|           <thead> | ||||
|           <tr> | ||||
|             <th>Select</th> | ||||
|             <th>Name</th> | ||||
|             <!--<th>Actions</th>--> | ||||
|           </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|           <tr ng-repeat="playbook in playbooks"> | ||||
|             <td><input name="playbookGroup" type="radio" ng-model="selectedPlaybook.playbook" ng-value="playbook"> | ||||
|             </td> | ||||
|             <td>{{playbook}}</td> | ||||
|             <!--<td><div class="btn-group"> | ||||
|               <label class="btn btn-default btn-sm" ng-click="showCreatePlayModal($index)" ><span class="fa fa-edit"></span></label> | ||||
|               <label class="btn btn-danger btn-sm" confirm="Are you sure you want to delete?"  ng-click="deletePlay($index)" ><span class="fa fa-trash-o"></span></label> | ||||
|               <div style="display: inline-block" tooltip-enable="!play.tags" uib-tooltip="Tag must be assigned to play individually"><label class="btn btn-success btn-sm" ng-disabled="!play.tags"  ng-click="executeAnsiblePlayBook(play.tags,'Play',play.name,play)" ><span class="fa fa-play"></span></label></div> | ||||
|             </div></td>--> | ||||
|           </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div uib-accordion-group class="panel-default" is-open="isopen.plays" ng-show="plays"> | ||||
|       <uib-accordion-heading> | ||||
|         Plays <i class="pull-right glyphicon" | ||||
|                  ng-class="{'glyphicon-chevron-down': isopen.plays, 'glyphicon-chevron-right': !isopen.plays}"></i> | ||||
|       </uib-accordion-heading> | ||||
| 
 | ||||
|       <div class="table-responsive"> | ||||
|         <table class="table"> | ||||
|           <thead> | ||||
|           <tr> | ||||
|           <tr> | ||||
|             <th>Select</th> | ||||
|             <th>Name</th> | ||||
|             <!--<th>Tags</th>--> | ||||
|             <th>Hosts</th> | ||||
|             <th>Actions</th> | ||||
|           </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|           <tr ng-repeat="play in plays"> | ||||
|             <td><input name="playGroup" type="radio" ng-model="selectedPlay.play" ng-value="play"> | ||||
|             </td> | ||||
|             <td>{{play.name}}</td> | ||||
|             <!--<td>{{play.tags.join(', ')}}</td>--> | ||||
|             <td>{{play.hosts}}</td> | ||||
|             <td> | ||||
|               <div class="btn-group"> | ||||
|                 <label class="btn btn-default btn-sm" ng-click="showCreatePlayModal($index)"><span | ||||
|                   class="fa fa-edit"></span></label> | ||||
|                 <label class="btn btn-danger btn-sm" confirm="Are you sure you want to delete?" | ||||
|                        ng-click="deletePlay($index)"><span class="fa fa-trash-o"></span></label> | ||||
|                 <div style="display: inline-block" tooltip-enable="!play.tags" | ||||
|                      uib-tooltip="Tag must be assigned to play individually"><label class="btn btn-success btn-sm" | ||||
|                                                                                     ng-disabled="!play.tags" | ||||
|                                                                                     ng-click="executeAnsiblePlayBook(play.tags,'Play',play.name,play)"><span | ||||
|                   class="fa fa-play"></span></label></div> | ||||
|                 <label class="btn btn-primary btn-sm" ng-disabled="$first" | ||||
|                        ng-click="moveUp(plays,$index,'savePlayLoading')"><span class="fa fa-arrow-up"></span></label> | ||||
|                 <label class="btn btn-primary btn-sm" ng-disabled="$last" | ||||
|                        ng-click="moveDown(plays,$index,'savePlayLoading')"><span | ||||
|                   class="fa fa-arrow-down"></span></label> | ||||
|               </div> | ||||
|             </td> | ||||
|           </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|       </div> | ||||
| 
 | ||||
|       <button class="btn btn-default" ng-click="showCreatePlayModal()">Create <span class="fa fa-plus"></span></button> | ||||
|       <button class="btn btn-primary" ng-if="showSaveButton.savePlayLoading" ng-click="savePlaybook('savePlayLoading')"> | ||||
|         Save <span ng-if="loadingButtons.savePlayLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
|       <button class="btn btn-warning" ng-if="showSaveButton.savePlayLoading" ng-click="cancelChange('savePlayLoading')"> | ||||
|         Cancel <span class="fa fa-times"></span></button> | ||||
|     </div> | ||||
| 
 | ||||
|     <div uib-accordion-group class="panel-default" is-open="isopen.tasks" ng-show="selectedPlay.play"> | ||||
|       <uib-accordion-heading> | ||||
|         Tasks <i class="pull-right glyphicon" | ||||
|                  ng-class="{'glyphicon-chevron-down': isopen.tasks, 'glyphicon-chevron-right': !isopen.tasks}"></i> | ||||
|       </uib-accordion-heading> | ||||
| 
 | ||||
|       <tasks tasks-list="selectedPlay.play.tasks" update-playbook-file-content="updatePlaybookFileContent" selected-play="selectedPlay" save-playbook="savePlaybook" execute-ansible-play-book="executeAnsiblePlayBook"></tasks> | ||||
|     </div> | ||||
| 
 | ||||
|     <div uib-accordion-group class="panel-default" is-open="isopen.roles" ng-show="selectedPlay.play.roles"> | ||||
|       <uib-accordion-heading> | ||||
|         Roles <i class="pull-right glyphicon" | ||||
|                  ng-class="{'glyphicon-chevron-down': isopen.tasks, 'glyphicon-chevron-right': !isopen.tasks}"></i> | ||||
|       </uib-accordion-heading> | ||||
|       <div class="table-responsive"> | ||||
|         <table class="table"> | ||||
|           <thead> | ||||
|           <tr> | ||||
|             <th>Select</th> | ||||
|             <th>Name</th> | ||||
|           </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|           <tr ng-repeat="role in selectedPlay.play.roles"> | ||||
|             <td><input name="playGroup" type="radio" ng-model="selectedRole.role" ng-value="role"> | ||||
|             </td> | ||||
|             <td>{{role}}</td> | ||||
|           </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="panel"> | ||||
|         <div class="panel panel-default"> | ||||
|           <div class="panel-heading">File Browser</div> | ||||
|           <div class="panel-body"> | ||||
| 
 | ||||
|             <treecontrol ng-if="roleData.children" class="tree-classic" | ||||
|                          tree-model="roleData.children" | ||||
|                          options="treeOptions" | ||||
|                          on-selection="showSelected(node)" | ||||
|                          selected-node="node1" | ||||
|                          filter-expression="{name: '!.git'}"> | ||||
|               {{node.name}} | ||||
|             </treecontrol> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
|   </uib-accordion> | ||||
| 
 | ||||
| </div> | ||||
| 
 | ||||
| <div class="col-md-6"> | ||||
|   <div ng-readonly="!editPlaybook.value" ui-ace="{theme:'twilight',document:'YAML',mode:'yaml',onChange:codeChanged,onLoad:aceLoaded}" | ||||
|        ng-model="selectedPlaybook.content"> | ||||
| 
 | ||||
|   </div> | ||||
| 
 | ||||
| </div> | ||||
							
								
								
									
										10
									
								
								client/app/designer/playbook/playbook.routes.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								client/app/designer/playbook/playbook.routes.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export default function($stateProvider) { | ||||
|   'ngInject'; | ||||
|   $stateProvider | ||||
|     .state('designer.playbook', { | ||||
|       url: '/playbook', | ||||
|       template: '<playbook></playbook>' | ||||
|     }); | ||||
| } | ||||
							
								
								
									
										58
									
								
								client/app/designer/roles/new_file/new_file.controller.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								client/app/designer/roles/new_file/new_file.controller.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function newFileController($scope,$uibModalInstance,ansible,selectedDirectory,copyFile,selectedFileName) { | ||||
|   $scope.newFile = {name:null}; | ||||
|   $scope.createFileLoading = false; | ||||
|   $scope.title = 'New File'; | ||||
| 
 | ||||
|   var parentDirectory = selectedDirectory; | ||||
| 
 | ||||
|   // If copyFile use selectedFileName to create new role from
 | ||||
|   // else nullify selectedFileName
 | ||||
|   if(!copyFile){ | ||||
|     selectedFileName = null; | ||||
|   } | ||||
|   else { | ||||
|     $scope.title = 'Copy File'; | ||||
|     $scope.newFile.name = 'Copy of ' + selectedFileName; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Create/Copy File - Either a new role or copy an existing role | ||||
|    */ | ||||
|   $scope.createFile = function(){ | ||||
| 
 | ||||
|     $scope.createFileLoading = true; | ||||
|     ansible.createFile(parentDirectory + '/' + $scope.newFile.name, | ||||
|       function(response){ | ||||
|         $scope.createFileLoading = false; | ||||
|         $scope.ok(); | ||||
|       }, | ||||
|       function(response){ | ||||
|         $scope.createFileLoading = false; | ||||
|         $scope.err_msg = response.data; | ||||
|       }, | ||||
|       selectedFileName | ||||
|     ) | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Close create/copy modal | ||||
|    */ | ||||
|   $scope.ok = function () { | ||||
|     $uibModalInstance.close(null); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Cancel modal | ||||
|    */ | ||||
|   $scope.cancel = function () { | ||||
|     $uibModalInstance.dismiss('cancel'); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.new_file', []) | ||||
|   .controller('NewFileController', newFileController) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: NewFileCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.new_file')); | ||||
| 
 | ||||
|   var NewFileCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     NewFileCtrl = $controller('NewFileCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										19
									
								
								client/app/designer/roles/new_file/new_file.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								client/app/designer/roles/new_file/new_file.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| <!-- Modal content--> | ||||
| <div class="modal-content"> | ||||
|   <div class="modal-header"> | ||||
|     <h4 class="modal-title">{{title}}</h4> | ||||
|   </div> | ||||
|   <div class="modal-body"> | ||||
| 
 | ||||
|     <p class="form-group"> | ||||
|       <label>File Name</label> | ||||
|       <input type="text" ng-model="newFile.name" class="form-control"> | ||||
|     </p> | ||||
| 
 | ||||
|   </div> | ||||
|   <div class="alert alert-danger" ng-if="err_msg">{{err_msg}}</div> | ||||
|   <div class="modal-footer"> | ||||
|     <button class="btn btn-primary" ng-disabled="!newFile.name" ng-click="createFile()">Create <span ng-if="createFileLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
|     <button class="btn btn-default" type="button" ng-click="cancel()">Close</button> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										56
									
								
								client/app/designer/roles/new_role/new_role.controller.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								client/app/designer/roles/new_role/new_role.controller.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function newRoleController($scope,$uibModalInstance,ansible,selectedRoleName,copyRole) { | ||||
| 
 | ||||
|   $scope.newRole = {name:null}; | ||||
|   $scope.createRoleLoading = false; | ||||
|   $scope.title = 'New Role'; | ||||
| 
 | ||||
|   // If copyRole use selectedRoleName to create new role from
 | ||||
|   // else nullify selectedRoleName
 | ||||
|   if(!copyRole){ | ||||
|     selectedRoleName = null; | ||||
|   } | ||||
|   else { | ||||
|     $scope.title = 'Copy Role'; | ||||
|     $scope.newRole.name = 'Copy of ' + selectedRoleName; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Create/Copy Role - Either a new role or copy an existing role | ||||
|    */ | ||||
|   $scope.createRole = function(){ | ||||
|     $scope.createRoleLoading = true; | ||||
|     ansible.createRole($scope.newRole.name, | ||||
|       function(response){ | ||||
|         $scope.createRoleLoading = false; | ||||
|         $scope.ok(); | ||||
|       }, | ||||
|       function(response){ | ||||
|         $scope.createRoleLoading = false; | ||||
|         $scope.err_msg = response.data; | ||||
|       }, | ||||
|       selectedRoleName | ||||
|     ) | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Close create/copy modal | ||||
|    */ | ||||
|   $scope.ok = function () { | ||||
|     $uibModalInstance.close(null); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Cancel modal | ||||
|    */ | ||||
|   $scope.cancel = function () { | ||||
|     $uibModalInstance.dismiss('cancel'); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.new_role', []) | ||||
|   .controller('NewRoleController', newRoleController) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: NewRoleCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.new_role')); | ||||
| 
 | ||||
|   var NewRoleCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     NewRoleCtrl = $controller('NewRoleCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										19
									
								
								client/app/designer/roles/new_role/new_role.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								client/app/designer/roles/new_role/new_role.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| <!-- Modal content--> | ||||
| <div class="modal-content"> | ||||
|   <div class="modal-header"> | ||||
|     <h4 class="modal-title">{{title}}</h4> | ||||
|   </div> | ||||
|   <div class="modal-body"> | ||||
| 
 | ||||
|     <p class="form-group"> | ||||
|       <label>Role Name</label> | ||||
|       <input type="text" ng-model="newRole.name" class="form-control"> | ||||
|     </p> | ||||
| 
 | ||||
|   </div> | ||||
|   <div class="alert alert-danger" ng-if="err_msg">{{err_msg}}</div> | ||||
|   <div class="modal-footer"> | ||||
|     <button class="btn btn-primary" ng-disabled="!newRole.name" ng-click="createRole()">Create <span ng-if="createRoleLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
|     <button class="btn btn-default" type="button" ng-click="cancel()">Close</button> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										463
									
								
								client/app/designer/roles/roles.component.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										463
									
								
								client/app/designer/roles/roles.component.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,463 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| const uiRouter = require('angular-ui-router'); | ||||
| 
 | ||||
| import routes from './roles.routes'; | ||||
| 
 | ||||
| export class RolesComponent { | ||||
|   /*@ngInject*/ | ||||
|   constructor($scope, ansible, $uibModal, yamlFile, Projects, editor) { | ||||
|   'ngInject'; | ||||
| 
 | ||||
|     $scope.treeOptions = { | ||||
|       nodeChildren: "children", | ||||
|       dirSelectable: true, | ||||
|       isLeaf: function (node) { | ||||
|         return !(node.type === 'directory'); | ||||
|       }, | ||||
|       injectClasses: { | ||||
|         ul: "a1", | ||||
|         li: "a2", | ||||
|         liSelected: "a7", | ||||
|         iExpanded: "a3", | ||||
|         iCollapsed: "a4", | ||||
|         iLeaf: "a5", | ||||
|         label: "a6", | ||||
|         labelSelected: "a8" | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     $scope.isopen = {roles: true, filebrowser: false, tasks: false}; | ||||
| 
 | ||||
|     $scope.selectedRole = {role: "", tasks: null}; | ||||
| 
 | ||||
|     $scope.selectedFile = {showSource: true, markdownContent: true, content: ""}; | ||||
| 
 | ||||
|     $scope.editRole = {value: false}; | ||||
|     $scope.showSaveFileButton = false; | ||||
| 
 | ||||
|     $scope.$on('projectLoaded', function () { | ||||
|       $scope.getRoles() | ||||
|     }); | ||||
| 
 | ||||
|     $scope.aceLoaded = function (_editor) { | ||||
|       _editor.$blockScrolling = Infinity; | ||||
|     }; | ||||
| 
 | ||||
|     // --------------------------------------- PLAYBOOKS ----------------
 | ||||
| 
 | ||||
|     $scope.getRoles = function () { | ||||
|       ansible.getRoleList( | ||||
|         function (response) { | ||||
|           $scope.roles = response.data; | ||||
|           if(localStorage.selectedRoleName) | ||||
|             $scope.selectedRole.role = localStorage.selectedRoleName | ||||
|         }, | ||||
|         function (response) { | ||||
|           console.log(response.data) | ||||
|         } | ||||
|       ) | ||||
|     }; | ||||
| 
 | ||||
|     var getRoleByName = function(roleName){ | ||||
|       var result = null; | ||||
|       angular.forEach($scope.roles,function(role){ | ||||
|         if(role.name == roleName){ | ||||
|           result = role | ||||
|         } | ||||
|       }); | ||||
|       return result; | ||||
|     }; | ||||
| 
 | ||||
|     if ($scope.$parent.selectedProject && $scope.$parent.selectedProject.ansibleEngine) { | ||||
|       $scope.getRoles() | ||||
|     } | ||||
| 
 | ||||
|     $scope.loadingModuleCode = false; | ||||
| 
 | ||||
|     $scope.markdownContent = ""; | ||||
| 
 | ||||
|     $scope.showRoleCode = function (role_name) { | ||||
|       $scope.loadingModuleCode = true; | ||||
|       $scope.markdownContent = ''; | ||||
|       $scope.docType = 'text'; | ||||
|       $scope.selectedFile.content = 'Loading Role Files..'; | ||||
|       $scope.selectedRole.tasks = null; | ||||
|       $scope.roleData = null; | ||||
| 
 | ||||
|       if (!role_name) { | ||||
|         $scope.selectedFile.content = "Select a module"; | ||||
|         return; | ||||
|       } | ||||
|       ansible.getRoleFiles(role_name, function (response) { | ||||
|         $scope.loadingModuleCode = false; | ||||
|         $scope.selectedFile.content = JSON.stringify(response.data, null, '\t'); | ||||
|         $scope.docType = 'json'; | ||||
|         $scope.roleData = response.data; | ||||
| 
 | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.$watch('selectedRole.role', function (newValue, oldValue) { | ||||
|       if (newValue && newValue !== oldValue) { | ||||
|         $scope.currentRole = newValue; | ||||
|         $scope.reloadRole(); | ||||
|         //$scope.isopen.roles = false;
 | ||||
|         $scope.isopen.filebrowser = true; | ||||
|         localStorage.selectedRoleName = $scope.selectedRole.role; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     $scope.reloadRole = function () { | ||||
|       $scope.selectedFile.content = "Loading Code..."; | ||||
|       $scope.docType = 'txt'; | ||||
|       $scope.showRoleCode($scope.currentRole); | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     /*var setDocType = function (data, file) { | ||||
|      if (typeof data == 'object') { | ||||
|      $scope.selectedFile.content = JSON.stringify(data, null, '\t'); | ||||
|      } else { | ||||
|      $scope.selectedFile.content = data; | ||||
|      } | ||||
| 
 | ||||
|      $scope.docType = editor.ui_ace_doctype_map[file.extension.replace('.', '')]; | ||||
| 
 | ||||
|      if ($scope.docType == 'markdown') { | ||||
|      $scope.markdownContent = $scope.selectedFile.content; | ||||
|      $scope.selectedFile.showSource = false; | ||||
|      } | ||||
|      };*/ | ||||
| 
 | ||||
|     /** | ||||
|      * Show selected item in the tree | ||||
|      * @param file | ||||
|      * @param parent | ||||
|      */ | ||||
|     $scope.showSelected = function (file, parent, decrypt) { | ||||
| 
 | ||||
|       if($scope.editRole.value){ | ||||
|         return | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|       if (file.children) { | ||||
|         $scope.selectedFile.content = JSON.stringify(file, null, '\t'); | ||||
|         $scope.docType = 'json'; | ||||
|         $scope.selectedRole.tasks = null; | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       $scope.selectedFile.content = 'Loading..'; | ||||
| 
 | ||||
|       var command = 'cat "' + file.path + '"'; | ||||
|       $scope.encryptedFile = false; | ||||
|       if(decrypt){ | ||||
|         command = 'ansible-vault view "' + file.path + '" --vault-password-file ~/.vault_pass.txt' | ||||
|         $scope.encryptedFile = true; | ||||
|         $scope.selectedFile.content = 'Loading Encrypted File..'; | ||||
|       } | ||||
| 
 | ||||
|       $scope.selectedFile.showSource = true; | ||||
|       $scope.markdownContent = ''; | ||||
|       $scope.docType = 'text'; | ||||
| 
 | ||||
|       $scope.selectedRole.tasks = null; | ||||
|       $scope.selectedFileName = file.name; | ||||
|       $scope.selectedFilePath = file.path; | ||||
|       $scope.parentNode = parent; | ||||
| 
 | ||||
|       ansible.executeCommand(command, | ||||
|         function (response) { | ||||
|           $scope.preChangeData = null; | ||||
|           editor.setContentAndType(response.data, file, $scope.selectedFile); | ||||
| 
 | ||||
|           var parentDirectory = file.path.replace(/^(.+)\/(.+)\/([^/]+)$/, "$2"); | ||||
|           if (parentDirectory == 'tasks') { | ||||
|             $scope.selectedRole.tasks = YAML.parse(response.data) || []; | ||||
|             $scope.isopen.tasks = true; | ||||
|             $scope.isopen.roles = false; | ||||
|           } | ||||
| 
 | ||||
|           if(response.data.indexOf('ANSIBLE_VAULT') > -1){ | ||||
|             editor.setContentAndType('Decrypting content...', file, $scope.selectedFile); | ||||
|             $scope.showSelected(file, parent, true); | ||||
|           } | ||||
| 
 | ||||
|         }, function (response) { | ||||
|           $scope.selectedFile.content = response.data; | ||||
| 
 | ||||
|         }) | ||||
|     }; | ||||
| 
 | ||||
|     $scope.showCreateFileModal = function (selectedFile, copyFile) { | ||||
|       var modalInstance = $uibModal.open({ | ||||
|         animation: true, | ||||
|         /*templateUrl: 'createTaskContent.html',*/ | ||||
|         templateUrl: 'app/designer/roles/new_file/new_file.html', | ||||
|         controller: 'NewFileController', | ||||
|         size: 'md', | ||||
|         backdrop: 'static', | ||||
|         keyboard: false, | ||||
|         closeByEscape: false, | ||||
|         closeByDocument: false, | ||||
|         resolve: { | ||||
|           copyFile: function () { | ||||
|             return copyFile | ||||
|           }, | ||||
|           selectedDirectory: function () { | ||||
|             if (selectedFile.type == 'directory') | ||||
|               return selectedFile.path; | ||||
|             else return $scope.parentNode.path | ||||
|           }, | ||||
|           selectedFileName: function () { | ||||
|             return selectedFile | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       modalInstance.result.then(function () { | ||||
|         //$scope.getRoles();
 | ||||
|         $scope.reloadRole(); | ||||
|       }, function () { | ||||
| 
 | ||||
|       }); | ||||
|     }; | ||||
| 
 | ||||
|     $scope.showCreateRoleModal = function (copyRole) { | ||||
|       var modalInstance = $uibModal.open({ | ||||
|         animation: true, | ||||
|         /*templateUrl: 'createTaskContent.html',*/ | ||||
|         templateUrl: 'app/designer/roles/new_role/new_role.html', | ||||
|         controller: 'NewRoleController', | ||||
|         size: 'md', | ||||
|         backdrop: 'static', | ||||
|         keyboard: false, | ||||
|         closeByEscape: false, | ||||
|         closeByDocument: false, | ||||
|         resolve: { | ||||
|           copyRole: function () { | ||||
|             return copyRole | ||||
|           }, | ||||
|           selectedRoleName: function () { | ||||
|             return $scope.selectedRole.role | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       modalInstance.result.then(function () { | ||||
|         $scope.getRoles(); | ||||
|       }, function () { | ||||
| 
 | ||||
|       }); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     $scope.showSearchRoleModal = function () { | ||||
|       var modalInstance = $uibModal.open({ | ||||
|         animation: true, | ||||
|         /*templateUrl: 'createTaskContent.html',*/ | ||||
|         templateUrl: 'app/designer/roles/search_role/search_role.html', | ||||
|         controller: 'SearchRoleController', | ||||
|         size: 'lg', | ||||
|         backdrop: 'static', | ||||
|         keyboard: false, | ||||
|         closeByEscape: false, | ||||
|         closeByDocument: false, | ||||
|         resolve: { | ||||
|           selectedProject: function () { | ||||
|             return $scope.$parent.selectedProject | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
| 
 | ||||
|       modalInstance.result.then(function () { | ||||
|         $scope.getRoles(); | ||||
|       }, function () { | ||||
| 
 | ||||
|       }); | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     $scope.saveRole = function () { | ||||
|       $scope.saveRoleLoading = true; | ||||
|       ansible.createRole($scope.selectedRole.role, $scope.selectedFile.content, | ||||
|         function (response) { | ||||
|           $scope.saveRoleLoading = false; | ||||
|           $scope.editRole.value = false; | ||||
|         }, | ||||
|         function (response) { | ||||
|           $scope.saveRoleLoading = false; | ||||
|           $scope.err_msg = response.data; | ||||
|         }) | ||||
|     }; | ||||
| 
 | ||||
|     $scope.deleteRole = function () { | ||||
|       $scope.deleteRoleLoading = true; | ||||
|       ansible.deleteRole($scope.selectedRole.role, | ||||
|         function (response) { | ||||
|           $scope.deleteRoleLoading = false; | ||||
|           $scope.selectedRole.role = ""; | ||||
|           $scope.selectedFile.content = ""; | ||||
|           $scope.roleData = null; | ||||
|           $scope.getRoles(); | ||||
|         }, | ||||
|         function (response) { | ||||
|           $scope.deleteRoleLoading = false; | ||||
|           $scope.err_msg = response.data; | ||||
|           $scope.selectedFile.content = ""; | ||||
|           $scope.roleData = null; | ||||
|         }) | ||||
|     }; | ||||
| 
 | ||||
|     $scope.loadingButtons = {}; | ||||
|     $scope.showSaveButton = {}; | ||||
| 
 | ||||
|     // ------------- PLAYBOOK ------------------
 | ||||
|     $scope.saveTasksFile = function (buttonStates) { | ||||
| 
 | ||||
|       buttonStates = buttonStates || {}; | ||||
| 
 | ||||
|       buttonStates.loading = true; | ||||
|       var tasksFileContent = $scope.selectedFile.content; | ||||
| 
 | ||||
| 
 | ||||
|       ansible.createPlaybook($scope.selectedFilePath, tasksFileContent, | ||||
|         function (response) { | ||||
|           buttonStates.loading = false; | ||||
|           buttonStates.save = false; | ||||
|         }, | ||||
|         function (response) { | ||||
|           buttonStates.loading = false; | ||||
|           buttonStates.save = false; | ||||
|           buttonStates.err_msg = false; | ||||
|         }) | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     $scope.updatePlaybookFileContent = function (save, buttonStates, preChangedData) { | ||||
|       $scope.selectedRole.tasks = preChangedData || $scope.selectedRole.tasks; | ||||
|       $scope.selectedFile.content = yamlFile.jsonToYamlFile($scope.selectedRole.tasks, 'Tasks File: ' + $scope.selectedFileName); | ||||
|       if (save) | ||||
|         $scope.saveTasksFile(buttonStates); | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     $scope.editFile = function (selectedFile) { | ||||
|       if (selectedFile.type == 'directory')return; | ||||
| 
 | ||||
|       if (!$scope.preChangeData){ | ||||
|         console.log("No prechanged data, setting pre change data"); | ||||
|         $scope.preChangeData = angular.copy($scope.selectedFile.content); | ||||
|       } | ||||
| 
 | ||||
|       $scope.editRole.value = true; | ||||
|       $scope.showSaveFileButton = true; | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     $scope.cancelFileChanges = function (selectedFile) { | ||||
|       if ($scope.preChangeData) { | ||||
|         console.log("Replacing content with pre changed data"); | ||||
|         $scope.selectedFile.content = angular.copy($scope.preChangeData); | ||||
|         $scope.preChangeData = null; | ||||
|         console.log("Clearing pre changed data") | ||||
|       } | ||||
| 
 | ||||
|       $scope.editRole.value = false; | ||||
|       $scope.showSaveFileButton = false; | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     $scope.saveFile = function (selectedFile) { | ||||
|       $scope.showSaveFileButtonLoading = true; | ||||
|       $scope.preChangeData = null; | ||||
|       ansible.updateFile(selectedFile.path, $scope.selectedFile.content, | ||||
|         function (response) { | ||||
|           $scope.showSaveFileButtonLoading = false; | ||||
|           $scope.showSaveFileButton = false; | ||||
|           $scope.editRole.value = false; | ||||
|         }, function (error) { | ||||
|           $scope.showSaveFileButtonLoading = false; | ||||
|           $scope.err_msg = error.data; | ||||
|         }) | ||||
| 
 | ||||
|     }; | ||||
| 
 | ||||
|     $scope.deleteFile = function (selectedFile) { | ||||
|       ansible.deleteFile(selectedFile.path, function (response) { | ||||
|         $scope.reloadRole(); | ||||
|       }, function (error) { | ||||
|         $scope.showSaveFileButtonLoading = false; | ||||
|         $scope.err_msg = error.data; | ||||
|       }) | ||||
|     }; | ||||
|     // ------------------- EXECUTE PLAYBOOK MODAL -------------
 | ||||
| 
 | ||||
|     $scope.executeAnsiblePlayBook = function (tags, executionType, executionName, selectedPlay) { | ||||
|       console.log("Tags type" + typeof tags); | ||||
| 
 | ||||
|       var projectRolesFolder = Projects.selectedProject.ansibleEngine.projectFolder + '/roles'; | ||||
|       var rolesFolder = projectRolesFolder + '/' + $scope.selectedRole.role; | ||||
|       var roleName = $scope.selectedRole.role; | ||||
|       console.log("Projects Roles Folder" + projectRolesFolder); | ||||
| 
 | ||||
|       var modalInstance = $uibModal.open({ | ||||
|         animation: true, | ||||
|         /*templateUrl: 'createTaskContent.html',*/ | ||||
|         templateUrl: 'app/designer/execution/executeModal.html', | ||||
|         controller: 'ExecutionController', | ||||
|         size: 'lg', | ||||
|         backdrop: 'static', | ||||
|         keyboard: false, | ||||
|         closeByEscape: false, | ||||
|         closeByDocument: false, | ||||
|         resolve: { | ||||
|           tags: function () { | ||||
|             return tags | ||||
|           }, | ||||
|           selectedProject: function () { | ||||
|             return Projects.selectedProject | ||||
|           }, | ||||
|           selectedPlaybook: function () { | ||||
|             return {playbook: $scope.selectedRole.role + '/tests/test.yml'}; | ||||
|           }, | ||||
|           selectedPlay: function () { | ||||
|             return selectedPlay | ||||
|           }, | ||||
|           executionType: function () { | ||||
|             return executionType | ||||
|           }, | ||||
|           executionName: function () { | ||||
|             return executionName | ||||
|           }, | ||||
|           readOnly: function () { | ||||
|             return false | ||||
|           }, | ||||
|           runData: function () { | ||||
|             return null | ||||
|           }, | ||||
|           projectFolder: function () { | ||||
|             return projectRolesFolder | ||||
|           }, | ||||
|           roleName: function () { | ||||
|             return roleName | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.roles', [uiRouter]) | ||||
|   .config(routes) | ||||
|   .component('roles', { | ||||
|     template: require('./roles.html'), | ||||
|     controller: RolesComponent, | ||||
|     controllerAs: 'rolesCtrl' | ||||
|   }) | ||||
|   .name; | ||||
							
								
								
									
										17
									
								
								client/app/designer/roles/roles.component.spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								client/app/designer/roles/roles.component.spec.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Component: RolesComponent', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.roles')); | ||||
| 
 | ||||
|   var RolesComponent; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($componentController) { | ||||
|     RolesComponent = $componentController('roles', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										0
									
								
								client/app/designer/roles/roles.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								client/app/designer/roles/roles.css
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										110
									
								
								client/app/designer/roles/roles.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								client/app/designer/roles/roles.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,110 @@ | |||
| <div class="col-md-6"> | ||||
|   <uib-accordion close-others="false"> | ||||
| 
 | ||||
|     <div uib-accordion-group class="panel-default" is-open="isopen.roles"> | ||||
|       <uib-accordion-heading> | ||||
|         Roles <i class="pull-right glyphicon" | ||||
|                  ng-class="{'glyphicon-chevron-down': isopen.roles, 'glyphicon-chevron-right': !isopen.roles}"></i> | ||||
|       </uib-accordion-heading> | ||||
| 
 | ||||
|       <button class="btn btn-default" ng-click="showCreateRoleModal()">Create Role <span class="fa fa-plus"></span> | ||||
|       </button> | ||||
|       <button class="btn btn-default" ng-click="showSearchRoleModal()">Import Role <span | ||||
|         class="fa fa-cloud-download"></span></button> | ||||
|       <button class="btn btn-default" ng-disabled="!selectedRole.role" ng-click="showCreateRoleModal(true)">Copy Role <span class="fa fa-copy"></span> | ||||
|       </button> | ||||
|       <!--<button class="btn btn-default" ng-if="!editRole.value" ng-disabled="!selectedRole.role" ng-click="editRole.value = true">Edit <span | ||||
|         ng-if="!saveRoleLoading" class="fa fa-edit"></span></button> | ||||
|       <button class="btn btn-primary" ng-if="editRole.value" ng-disabled="!selectedRole.role" ng-click="saveRole()">Save | ||||
|         <span ng-if="!saveRoleLoading" class="fa fa-save"></span><span ng-if="saveRoleLoading" | ||||
|                                                                        class="fa fa-spinner fa-spin"></span></button>--> | ||||
|       <button class="btn btn-danger" confirm="Are you sure you want to delete the role and all its contents? " | ||||
|               ng-disabled="!selectedRole.role" ng-click="deleteRole()">Delete <span ng-if="!deleteRoleLoading" | ||||
|                                                                                     class="fa fa-save"></span><span | ||||
|         ng-if="deleteRoleLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
| 
 | ||||
|       <button class="btn btn-success" | ||||
|               ng-disabled="!selectedRole.role" ng-click="executeAnsiblePlayBook(null,'Role',selectedRole.role,null)">Test <span | ||||
|         class="fa fa-play"></span></button> | ||||
| 
 | ||||
|       <div class="table-responsive"> | ||||
|         <table class="table"> | ||||
|           <thead> | ||||
|           <tr> | ||||
|             <th>Select</th> | ||||
|             <th>Name</th> | ||||
|           </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|           <tr ng-repeat="role in roles"> | ||||
|             <td><input name="playGroup" type="radio" ng-model="selectedRole.role" ng-value="role"> | ||||
|             </td> | ||||
|             <td>{{role}}</td> | ||||
|           </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|       </div> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     <div uib-accordion-group class="panel-default" is-open="isopen.filebrowser" ng-show="roleData"> | ||||
|       <uib-accordion-heading> | ||||
|         File Browser {{selectedRole.role ? '-' + selectedRole.role : ''}} <i class="pull-right glyphicon" | ||||
|                                                                              ng-class="{'glyphicon-chevron-down': isopen.filebrowser, 'glyphicon-chevron-right': !isopen.filebrowser}"></i> | ||||
|       </uib-accordion-heading> | ||||
| 
 | ||||
|       <treecontrol ng-show="roleData.children" class="tree-classic" | ||||
|                    tree-model="roleData.children" | ||||
|                    options="treeOptions" | ||||
|                    on-selection="showSelected(node, $parentNode)" | ||||
|                    selected-node="selectedFile" | ||||
|                    filter-expression="{name: '!.git'}"> | ||||
|         {{node.name}} | ||||
|       </treecontrol> | ||||
| 
 | ||||
|       <button class="btn btn-default" ng-click="showCreateFileModal(selectedFile)" ng-disabled="!(selectedFile.type === 'directory')">Create File <span class="fa fa-plus"></span> | ||||
|       </button> | ||||
|       <button class="btn btn-default" ng-show="!editRole.value" ng-disabled="!(selectedFile.type === 'file') || encryptedFile" ng-click="editFile(selectedFile)">Edit File <span class="fa fa-edit"></span> | ||||
|       </button> | ||||
|       <button class="btn btn-primary" ng-show="showSaveFileButton" ng-disabled="encryptedFile" ng-click="saveFile(selectedFile)">Save File <span ng-show="!showSaveFileButtonLoading" class="fa fa-save"></span><span ng-show="showSaveFileButtonLoading" class="fa fa-spin fa-spinner"></span> | ||||
|       </button> | ||||
|       <button class="btn btn-warning" ng-show="showSaveFileButton" confirm="Are you sure you want to cancel and discard the changes?" ng-click="cancelFileChanges(selectedFile)">Cancel <span class="fa fa-times"></span> | ||||
|       </button> | ||||
|       <button class="btn btn-danger" ng-disabled="!selectedFile.type" confirm="Are you sure you want to delete the selected {{selectedFile.type}}?" ng-click="deleteFile(selectedFile)">Delete <span class="fa fa-trash-o"></span> | ||||
|       </button> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     <div uib-accordion-group class="panel-default" is-open="isopen.tasks" ng-show="selectedRole.tasks"> | ||||
|       <uib-accordion-heading> | ||||
|         Tasks {{selectedRole.role ? '-' + selectedRole.role : ''}} <i class="pull-right glyphicon" | ||||
|                                                                       ng-class="{'glyphicon-chevron-down': isopen.tasks, 'glyphicon-chevron-right': !isopen.tasks}"></i> | ||||
|       </uib-accordion-heading> | ||||
| 
 | ||||
|       <tasks tasks-list="selectedRole.tasks" update-playbook-file-content="updatePlaybookFileContent" selected-play="selectedPlay" selected-role="selectedRole" move-up="moveUp" move-down="moveDown" save-playbook="saveTasksFile" files="parentNode.children" execute-ansible-play-book="executeAnsiblePlayBook"></tasks> | ||||
|     </div> | ||||
| 
 | ||||
|   </uib-accordion> | ||||
| 
 | ||||
| 
 | ||||
| </div> | ||||
| </div> | ||||
| 
 | ||||
| <div class="col-md-6"> | ||||
| 
 | ||||
|   <button class="btn btn-default" ng-show="selectedFile.docType == 'markdown' && !selectedFile.showSource" ng-click="selectedFile.showSource = true"> Show | ||||
|     Source | ||||
|   </button> | ||||
|   <button class="btn btn-default" ng-show="selectedFile.docType == 'markdown' && selectedFile.showSource" ng-click="selectedFile.showSource = false"> Hide | ||||
|     Source | ||||
|   </button> | ||||
|   <!--{{docType}}--> | ||||
|   <div ng-show="selectedFile.showSource" ng-readonly="!editRole.value" | ||||
|        ui-ace="{theme:'twilight',document:selectedFile.docType,mode:selectedFile.docType,onChange:codeChanged,onLoad:aceLoaded}" ng-model="selectedFile.content"> | ||||
| 
 | ||||
|   </div> | ||||
| 
 | ||||
|   <div ng-show="!selectedFile.showSource" btf-markdown="selectedFile.markdownContent"> | ||||
|   </div> | ||||
| 
 | ||||
| </div> | ||||
							
								
								
									
										10
									
								
								client/app/designer/roles/roles.routes.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								client/app/designer/roles/roles.routes.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| export default function($stateProvider) { | ||||
|   'ngInject'; | ||||
|   $stateProvider | ||||
|     .state('designer.roles', { | ||||
|       url: '/roles', | ||||
|       template: '<roles></roles>' | ||||
|     }); | ||||
| } | ||||
|  | @ -0,0 +1,66 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function searchRoleController($scope,ansible,selectedProject,$uibModalInstance) { | ||||
| 
 | ||||
|   $scope.searchText = ''; | ||||
|   $scope.searchLoading = false; | ||||
| 
 | ||||
|   $scope.selectedRole = {role:{}}; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   $scope.searchRoles = function(){ | ||||
|     $scope.searchResult = []; | ||||
|     $scope.searchLoading = true; | ||||
|     ansible.searchRolesGalaxy($scope.searchText, | ||||
|       function(response){ | ||||
|         $scope.searchLoading = false; | ||||
|         $scope.searchResult = $scope.searchResult.concat(response.data) | ||||
|       },function(response){ | ||||
|         $scope.searchLoading = false; | ||||
|         $scope.err_msg = response.data | ||||
|       }); | ||||
| 
 | ||||
|     ansible.searchRolesGithub($scope.searchText, | ||||
|       function(response){ | ||||
|         $scope.searchLoading = false; | ||||
|         $scope.searchResult = $scope.searchResult.concat(response.data) | ||||
|       },function(response){ | ||||
|         $scope.searchLoading = false; | ||||
|         $scope.err_msg = response.data | ||||
|       }) | ||||
| 
 | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
| 
 | ||||
|   $scope.importRole = function(role){ | ||||
|     $scope.importLoading = true; | ||||
| 
 | ||||
|     if(role.type === 'galaxy')role.url = role.name; | ||||
| 
 | ||||
|     ansible.importRole(role.type,role.url, | ||||
|       function(response){ | ||||
|         $scope.importLoading = false; | ||||
|         $scope.ok(); | ||||
|       },function(response){ | ||||
|         $scope.importLoading = false; | ||||
|         $scope.err_msg = response.data | ||||
|       }); | ||||
|   }; | ||||
| 
 | ||||
| 
 | ||||
|   $scope.ok = function () { | ||||
|     $uibModalInstance.close(null); | ||||
|   }; | ||||
| 
 | ||||
|   $scope.cancel = function () { | ||||
|     $uibModalInstance.dismiss('cancel'); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.search_role', []) | ||||
|   .controller('SearchRoleController', searchRoleController) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: SearchRoleCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.search_role')); | ||||
| 
 | ||||
|   var SearchRoleCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     SearchRoleCtrl = $controller('SearchRoleCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										70
									
								
								client/app/designer/roles/search_role/search_role.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								client/app/designer/roles/search_role/search_role.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | |||
| <!-- Modal content--> | ||||
| <div class="modal-content"> | ||||
|   <div class="modal-header"> | ||||
|     <h4 class="modal-title">Search Role</h4> | ||||
|   </div> | ||||
|   <div class="modal-body"> | ||||
| 
 | ||||
|     <p class="form-group"> | ||||
|       <label>Search</label> | ||||
|       <input type="text" ng-model="searchText" class="form-control"> | ||||
|     </p> | ||||
| 
 | ||||
|     <div class="row"> | ||||
|       <div ng-repeat="role in searchResult"> | ||||
|         <div class="clearfix" ng-if="($index % 3) == 0"></div> | ||||
|         <div class="col-md-4" > | ||||
| 
 | ||||
|           <div class="panel"> | ||||
|             <div class="panel panel-default"> | ||||
|               <!--<div class="panel-heading"></div>--> | ||||
|               <div class="panel-body"> | ||||
|                 <span class="fa fa-spin fa-spinner fa-4x" ng-show="searchLoading"></span> | ||||
|                 <h4> | ||||
|                   <span class="fa fa-github fa-2x" ng-if="role.type == 'gitrepo'"></span> | ||||
|                   <img src="assets/images/ansible_icon.png" ng-if="role.type == 'galaxy'"> | ||||
|                   {{role.name}} | ||||
|                 </h4> | ||||
|                 {{role.description}} | ||||
|                 <br> | ||||
|                 <button class="btn btn-success btn-sm" ng-click="importRole(role)"> <span class="fa fa-cloud-download"></span> Import</button> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|       </div> | ||||
| 
 | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     <!--<div class="table-responsive"> | ||||
|       <table class="table"> | ||||
|         <thead> | ||||
|         <tr> | ||||
|           <th>Select</th> | ||||
|           <th>Name</th> | ||||
|           <th>Description</th> | ||||
|           <th>Type</th> | ||||
|         </tr> | ||||
|         </thead> | ||||
|         <tbody> | ||||
|         <tr ng-repeat="role in searchResult"> | ||||
|           <td><input name="roleGroup" type="radio" ng-model="selectedRole.role" ng-value="role"> | ||||
|           </td> | ||||
|           <td>{{role.name}}</td> | ||||
|           <td>{{role.description}}</td> | ||||
|           <td>{{role.type}}</td> | ||||
|         </tr> | ||||
|         </tbody> | ||||
|       </table> | ||||
|     </div> | ||||
|     {{selectedRole}}--> | ||||
|   </div> | ||||
|   <div class="alert alert-danger" ng-if="err_msg">{{err_msg}}</div> | ||||
|   <div class="modal-footer"> | ||||
|     <button class="btn btn-primary" ng-disabled="!searchText" ng-click="searchRoles()">Search <span ng-if="!searchLoading" class="fa fa-search"></span> <span ng-if="searchLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
|     <!--<button class="btn btn-primary" ng-disabled="!selectedRole.role.name" ng-click="importRole()">Import <span ng-if="importLoading" class="fa fa-spinner fa-spin"></span></button>--> | ||||
|     <button class="btn btn-default" type="button" ng-click="cancel()">Close</button> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										446
									
								
								client/app/designer/tasks/new_task/new_task.controller.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										446
									
								
								client/app/designer/tasks/new_task/new_task.controller.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,446 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function newTaskController($window, $scope, $sce, $uibModal, ansi2html, ansible, $uibModalInstance, tasksList, selectedTaskIndex, copyTask, files, selectedPlay, selectedRole, $filter, Projects) { | ||||
|   var selectedTask; | ||||
| 
 | ||||
|   /** | ||||
|    * Edit task - in case of edit task , selectedTaskIndex is not null. | ||||
|    * Set selectedTask to a copy of selected task to edit. | ||||
|    */ | ||||
|   if(selectedTaskIndex > -1 && tasksList){ | ||||
|     if(copyTask){ | ||||
|       selectedTask = angular.copy(tasksList[selectedTaskIndex]); | ||||
|       selectedTask.name = "Copy of " + selectedTask.name; | ||||
|       selectedTaskIndex = null; | ||||
|     }else{ | ||||
|       selectedTask = tasksList[selectedTaskIndex] | ||||
|     } | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * List of files for include purpose | ||||
|    */ | ||||
|   if(files){ | ||||
|     $scope.files = files; | ||||
|   } | ||||
| 
 | ||||
|   $scope.getModuleDescriptionLoading = false; | ||||
|   $scope.modulesLoading = false; | ||||
| 
 | ||||
|   $scope.modules = null; | ||||
|   $scope.singeLineModules = ["shell"]; | ||||
|   $scope.showHelp = false; | ||||
| 
 | ||||
|   $scope.newTask = {}; | ||||
|   $scope.title = "New Task"; | ||||
|   $scope.createTaskLoading = false; | ||||
| 
 | ||||
|   /** | ||||
|    * Get Ansible Modules | ||||
|    * If Edit Task, get module description for selected task | ||||
|    */ | ||||
|   $scope.getAnsibleModules = function(){ | ||||
|     $scope.modulesLoading = true; | ||||
|     ansible.getAnsibleModules(function(response){ | ||||
|       $scope.modules = response; | ||||
|       $scope.modulesLoading = false; | ||||
| 
 | ||||
|       if(selectedTask){ | ||||
|         $scope.title = "Edit Task"; | ||||
|         selectedTask = angular.copy(selectedTask); | ||||
|         $scope.newTask = selectedTask; | ||||
|         if(selectedTask.tags)$scope.newTask.tags = $scope.newTask.tags.join(','); | ||||
|         var module = $scope.getModuleFromTask(selectedTask); | ||||
|         $scope.getModuleDescription(module,true) | ||||
|       } | ||||
| 
 | ||||
|     }, function(response){ | ||||
|       $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data).replace(/\n/g, "<br>").replace(/ /g," ")); | ||||
| 
 | ||||
|     }); | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
| 
 | ||||
|   /** | ||||
|    * Get Module Description whenever a module is selected by the user | ||||
|    * @param module - Module Object | ||||
|    * @param override - Override variables in case of edit task | ||||
|    * @param refresh - Refresh module description from server. Don't display from cache | ||||
|    */ | ||||
|   $scope.getModuleDescription = function(module,override,refresh){ | ||||
| 
 | ||||
|     if(!module)return; | ||||
| 
 | ||||
|     var module_copy = angular.copy(module); | ||||
| 
 | ||||
|     $scope.getModuleDescriptionLoading = true; | ||||
|     var moduleName = module.name; | ||||
| 
 | ||||
|     if($scope.singeLineModules.indexOf(moduleName) > -1){ | ||||
|       module.singleLine = true; | ||||
|     } | ||||
| 
 | ||||
|     $scope.detailHelp = ""; | ||||
|     $scope.examples = ""; | ||||
|     module.variables = []; | ||||
| 
 | ||||
|     ansible.getAnsibleModuleDescription(moduleName, | ||||
|       function(response){ | ||||
|         $scope.showHelp = true; | ||||
|         $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response).replace(/\n/g, "<br>").replace(/ /g," ")); | ||||
| 
 | ||||
|         $scope.detailHelp = response; | ||||
|         $scope.examples = response.substr(response.indexOf("#")); | ||||
|         //var re = /(^[-=] .*)/gm;
 | ||||
|         //var re = /(^[-=] .*)[^]*?(?:(\(Choices[^]+?\))?\s*(\[.*\])|(?=^[-=]|^EXAMPLES))/gm;
 | ||||
|         var re = /(^[-=] .*)([^]*?)(?:(\(Choices[^]+?\))?\s*(\[.*\])|(?=^[-=]|^EXAMPLES))/gm; | ||||
|         var m; | ||||
| 
 | ||||
|         while ((m = re.exec($scope.detailHelp.split("EXAMPLES")[0]+"EXAMPLES")) !== null) { | ||||
|           //while ((m = re.exec($scope.detailHelp.split("#")[0])) !== null) {
 | ||||
|           //while ((m = re.exec($scope.detailHelp)) !== null) {
 | ||||
|           if (m.index === re.lastIndex) { | ||||
|             re.lastIndex++; | ||||
|           } | ||||
|           // View your result using the m-variable.
 | ||||
|           // eg m[0] etc.
 | ||||
| 
 | ||||
|           var option_name = m[1]; | ||||
|           var description = m[2]; | ||||
|           var choices = m[3]; | ||||
|           var default_value = m[4]; | ||||
| 
 | ||||
| 
 | ||||
|           var breakup = option_name.split(" "); | ||||
|           var variable_name = breakup[1]; | ||||
|           var mandatory = breakup[0] == "="; | ||||
| 
 | ||||
|           var complex_value = {}; | ||||
| 
 | ||||
|           if(default_value) | ||||
|             default_value = default_value.replace(/\[Default: (.*)\]/,"$1"); | ||||
| 
 | ||||
|           if(default_value == 'None') | ||||
|             default_value = null | ||||
| 
 | ||||
|           var variable_value = default_value || ''; | ||||
| 
 | ||||
|           if(choices) | ||||
|             choices = choices.replace(/\s+/g,"").replace(/\n\s+/g,"").replace(/\(Choices:(.*)\)/,"$1").split(","); | ||||
| 
 | ||||
|           if(override && module_copy.variables){ | ||||
|             var matching_variable = module_copy.variables.filter(function(item){ | ||||
|               if(item.name == variable_name){ | ||||
|                 return true | ||||
|               } | ||||
|             }); | ||||
|             if(matching_variable.length){ | ||||
|               variable_value = matching_variable[0].value; | ||||
|               if(typeof variable_value=='object'){ | ||||
|                 complex_value  = angular.copy(variable_value) | ||||
|               } | ||||
|             } | ||||
| 
 | ||||
|           } | ||||
| 
 | ||||
|           module.variables.push({name:variable_name,description:description,mandatory:mandatory,value:variable_value,complexValue:complex_value,choices:choices,default_value:default_value}); | ||||
|           $scope.getModuleDescriptionLoading = false; | ||||
|         } | ||||
|       }, function(response){ | ||||
|         $scope.result = $sce.trustAsHtml(ansi2html.toHtml(response.data).replace(/\n/g, "<br>")); | ||||
|         console.log(ansi2html.toHtml(response.data)); | ||||
|         $scope.detailHelp = response.data; | ||||
|         $scope.getModuleDescriptionLoading = false; | ||||
|       },refresh) | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Reload Module Description and variables. Ignore displaying from cache. | ||||
|    * Used when a custom module is updated and description and variables info need to be updated. | ||||
|    * @param module - module object | ||||
|    */ | ||||
|   $scope.reloadModuleDetails = function(module){ | ||||
| 
 | ||||
|     if(selectedTask){ | ||||
|       $scope.getModuleDescription(module,true,true) | ||||
|     }else{ | ||||
|       $scope.getModuleDescription(module,false,true) | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
| 
 | ||||
|   /** | ||||
|    * Identify module from a given task object. | ||||
|    * @param task - Single task object containing task properties | ||||
|    * @returns {{}} | ||||
|    */ | ||||
|   $scope.getModuleFromTask = function(task){ | ||||
|     var moduleObject = {}; | ||||
|     $scope.local_action = false; | ||||
|     var task_properties = null; | ||||
| 
 | ||||
|     var module =  ansible.getModuleFromTask(task); | ||||
| 
 | ||||
|     if(module === 'include'){ | ||||
|       module = null; | ||||
|       task.tags = task.include.replace(/.*tags=(.*)/,"$1") | ||||
|       return; | ||||
|     }else if(module === 'local_action'){ | ||||
|       $scope.local_action = true; | ||||
|       module = task.local_action.module; | ||||
|       task_properties = task.local_action; | ||||
|       delete task_properties.module; | ||||
|     }else{ | ||||
|       task_properties = task[module]; | ||||
|     } | ||||
| 
 | ||||
|     angular.forEach($scope.modules, function(item,index) { | ||||
|       if(item.name == module){ | ||||
|         moduleObject = item; | ||||
|         $scope.newTask.module = item; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     if(!(moduleObject && moduleObject.name)){ | ||||
|       $scope.err_msg = "Unable to find module " + module + " in Ansible controller"; | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     //moduleObject.name = module;
 | ||||
|     moduleObject.variables = []; | ||||
|     if(typeof task_properties == "string"){ | ||||
|       moduleObject.variables.push({'name':'free_form','value':task_properties}); | ||||
| 
 | ||||
|       var re = /\b(\w+)=\s*([^=]*\S)\b\s*(?=\w+=|$)/g; | ||||
|       var m; | ||||
| 
 | ||||
|       while ((m = re.exec(task_properties)) !== null) { | ||||
|         if (m.index === re.lastIndex) { | ||||
|           re.lastIndex++; | ||||
|         } | ||||
|         // View your result using the m-variable.
 | ||||
|         // eg m[0] etc.
 | ||||
|         var k=m[1]; | ||||
|         var v=m[2]; | ||||
|         moduleObject.variables.push({'name':k,'value':v}) | ||||
|       } | ||||
| 
 | ||||
|     }else if(typeof task_properties == "object"){ | ||||
| 
 | ||||
|       angular.forEach(task_properties,function(value,key){ | ||||
|         this.push({'name':key,'value':value,'complexValue':value}) | ||||
|       },moduleObject.variables) | ||||
| 
 | ||||
|     } | ||||
|     return moduleObject | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Create Task - Creates new task object and set task variables. | ||||
|    * Push new task object to tasksList | ||||
|    * Close new task modal | ||||
|    */ | ||||
|   $scope.createTask = function(){ | ||||
| 
 | ||||
|     if(!$scope.newTask.module && !$scope.newTask.include){ | ||||
|       $scope.err_msg = "Must select atleast one module or include statement"; | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     $scope.createTaskLoading = true; | ||||
| 
 | ||||
|     if(!tasksList){ | ||||
|       tasksList = [] | ||||
|     } | ||||
| 
 | ||||
|     var taskObject = {name:$scope.newTask.name}; | ||||
| 
 | ||||
|     if($scope.newTask.include) | ||||
|       taskObject['include'] = $scope.newTask.include; | ||||
| 
 | ||||
|     if($scope.newTask.tags) | ||||
|       taskObject['tags'] = $scope.newTask.tags.split(','); | ||||
| 
 | ||||
|     if($scope.newTask.register) | ||||
|       taskObject['register'] = $scope.newTask.register; | ||||
| 
 | ||||
|     if($scope.newTask.async){ | ||||
|       taskObject['async'] = $scope.newTask.async; | ||||
|       if(!$scope.newTask.poll) | ||||
|         $scope.newTask.poll = 10; | ||||
|       taskObject['poll'] = $scope.newTask.poll; | ||||
|     } | ||||
| 
 | ||||
|     var variablesObject = null; | ||||
|     if($scope.newTask.module){ | ||||
|       if($scope.newTask.module.singleLine){ | ||||
|         variablesObject = ""; | ||||
|         //Add all mandatory variables first
 | ||||
|         angular.forEach($scope.newTask.module.variables.filter(function(item){ | ||||
|           return item.mandatory | ||||
|         }),function(item){ | ||||
|           if(item.name == 'free_form'){ | ||||
|             variablesObject += item.value; | ||||
|           }else if(item.value){ | ||||
|             variablesObject += " " + item.name + "=" + item.value; | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|         //Add optional variables
 | ||||
|         angular.forEach($scope.newTask.module.variables.filter(function(item){ | ||||
|           return !item.mandatory | ||||
|         }),function(item){ | ||||
|           if(item.value != item.default_value){ | ||||
|             if(item.name == 'free_form'){ | ||||
|               variablesObject += item.value; | ||||
|             }else if(item.value){ | ||||
|               variablesObject += " " + item.name + "=" + item.value; | ||||
|             } | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|       }else{ | ||||
|         variablesObject = {}; | ||||
|         angular.forEach($scope.newTask.module.variables,function(item){ | ||||
|           if((item.value || (item.isComplexVariable && item.complexValue)) && item.value != item.default_value){ | ||||
|             if(item.isComplexVariable){ | ||||
|               variablesObject[item.name] = item.complexValue; | ||||
|             }else{ | ||||
|               variablesObject[item.name] = item.value; | ||||
|             } | ||||
| 
 | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
| 
 | ||||
|       taskObject[$scope.newTask.module.name] = variablesObject; | ||||
| 
 | ||||
|       if($scope.local_action){ | ||||
|         variablesObject.module = $scope.newTask.module.name; | ||||
|         taskObject['local_action'] = variablesObject; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     if(selectedTaskIndex != null){ | ||||
|       // If Edit Task
 | ||||
| 
 | ||||
|       tasksList[selectedTaskIndex] = taskObject | ||||
| 
 | ||||
|     }else{ | ||||
|       // If New Task
 | ||||
| 
 | ||||
|       tasksList.push(taskObject); | ||||
|     } | ||||
| 
 | ||||
|     $uibModalInstance.close(taskObject); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Close modal | ||||
|    */ | ||||
|   $scope.ok = function () { | ||||
|     $uibModalInstance.close($scope.newTask); | ||||
|   }; | ||||
| 
 | ||||
| 
 | ||||
|   /** | ||||
|    * Cancel modal | ||||
|    */ | ||||
|   $scope.cancel = function () { | ||||
|     $uibModalInstance.dismiss('cancel'); | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Get host variables using Ansible Python API in the backend | ||||
|    */ | ||||
|   $scope.getHostVars = function(){ | ||||
| 
 | ||||
|     if(!(selectedPlay && selectedPlay.play && selectedPlay.play.hosts))return; | ||||
| 
 | ||||
|     ansible.getVars(Projects.selectedInventoryFileName,selectedPlay.play.hosts,function(response){ | ||||
|       console.log(response.data); | ||||
|       if(response.data.length) | ||||
|         $scope.hostvars = $filter('dictToKeyValueArray')(response.data[0]); | ||||
|       else $scope.err_msg = "Getting host variables - No variables returned" ; | ||||
| 
 | ||||
|     },function(error){ | ||||
|       console.log(error.data); | ||||
|       $scope.err_msg = "Getting host variables - " + error.data; | ||||
|     }) | ||||
|   }; | ||||
| 
 | ||||
|   if(selectedPlay) | ||||
|     $scope.getHostVars(); | ||||
| 
 | ||||
| 
 | ||||
|   $scope.getRoleVars = function(){ | ||||
| 
 | ||||
|     if(!(selectedRole && selectedRole.role))return; | ||||
| 
 | ||||
|     ansible.getRoleVars(selectedRole.role,function(response){ | ||||
|       console.log(response.data); | ||||
|       if(response.data) | ||||
|         $scope.hostvars = $filter('dictToKeyValueArray')(response.data); | ||||
|       else $scope.err_msg = "Getting host variables - No variables returned" ; | ||||
| 
 | ||||
|     },function(error){ | ||||
|       console.log(error.data); | ||||
|       $scope.err_msg = "Getting host variables - " + error.data; | ||||
|     }) | ||||
|   }; | ||||
| 
 | ||||
|   if(selectedRole) | ||||
|     $scope.getRoleVars(); | ||||
| 
 | ||||
| 
 | ||||
|   if(!$scope.modules){ | ||||
|     $scope.getAnsibleModules(); | ||||
|   } | ||||
| 
 | ||||
|   $scope.showComplexVariable = function(variable){ | ||||
|     variable.isComplexVariable = true; | ||||
|     var modalInstance = $uibModal.open({ | ||||
|       animation: true, | ||||
|       /*templateUrl: 'createTaskContent.html',*/ | ||||
|       templateUrl: 'app/modals/complex_var_modal/complexVariable.html', | ||||
|       controller: 'ComplexVarModalController', | ||||
|       size: 'sm', | ||||
|       backdrop: 'static', | ||||
|       keyboard: false, | ||||
|       closeByEscape: false, | ||||
|       closeByDocument: false, | ||||
|       resolve: { | ||||
|         path: function () { | ||||
|           return variable.name | ||||
|         }, | ||||
|         hostvars: function(){ | ||||
|           return $scope.hostvars | ||||
|         }, | ||||
|         members: function(){ | ||||
|           return variable.complexValue | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     modalInstance.result.then(function (selectedItem) { | ||||
|       variable.complexValue = selectedItem | ||||
|     }, function () { | ||||
| 
 | ||||
|     }); | ||||
| 
 | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.new_task', []) | ||||
|   .controller('NewTaskController', newTaskController) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: NewTaskCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.new_task')); | ||||
| 
 | ||||
|   var NewTaskCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     NewTaskCtrl = $controller('NewTaskCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										134
									
								
								client/app/designer/tasks/new_task/new_task.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								client/app/designer/tasks/new_task/new_task.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,134 @@ | |||
| <!--<script type="text/ng-template" id="createTaskContent.html">--> | ||||
| <!-- Modal content--> | ||||
| <div class="modal-content" > | ||||
|   <div class="modal-header"> | ||||
|     <button type="button" class="close" ng-click="cancel()" data-dismiss="modal">×</button> | ||||
|     <h4 class="modal-title">{{title}}</h4> | ||||
|   </div> | ||||
|   <div class="modal-body" id="TaskCreationModal"> | ||||
|     <div class="row"> | ||||
|       <div class="col-md-6"> | ||||
|         <div class="input-group"> | ||||
|           <span class="input-group-addon" >Name</span> | ||||
|           <input ng-model="newTask.name" type="text" class="form-control" placeholder="Task Name"> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="input-group"> | ||||
|           <span class="input-group-addon" >Include</span> | ||||
|           <input type="text" ng-model="newTask.include" uib-typeahead="file.name as file.name for file in files | filter: $viewValue"  class="form-control"> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="input-group"> | ||||
|           <span class="input-group-addon" >Module</span> | ||||
|           <input type="text" ng-disabled="!modules || newTask.include" ng-model="newTask.module" typeahead-on-select="getModuleDescription(newTask.module)" uib-typeahead="module as module.name for module in modules | filter: $viewValue"  class="form-control"> | ||||
|         </div> | ||||
| 
 | ||||
|         <i ng-if="modulesLoading" class="fa fa-spinner fa-spin"></i> | ||||
| 
 | ||||
|         <div class="input-group"> | ||||
|           <span class="input-group-addon" >Tags</span> | ||||
|           <input ng-model="newTask.tags" type="text" class="form-control" placeholder="Tags separated by comma"> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="input-group"> | ||||
|           <span class="input-group-addon" >Async (s)</span> | ||||
|           <input ng-model="newTask.async" type="text" class="form-control" placeholder="Max run time in seconds"> | ||||
|         </div> | ||||
| 
 | ||||
|         <div ng-if="newTask.async"> | ||||
|           <div class="hint">{{newTask.async / 60 | number}} minutes</div> | ||||
|           <div class="input-group" > | ||||
|             <span class="input-group-addon" >Poll (s)</span> | ||||
|             <input ng-model="newTask.poll" type="text" class="form-control" placeholder="10" ng-value="10"> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="input-group"> | ||||
|           <span class="input-group-addon" >Register Result</span> | ||||
|           <input ng-model="newTask.register" type="text" class="form-control" placeholder="Variable Name"> | ||||
|         </div> | ||||
|         <div class="hint">Register results of to a variable</div> | ||||
| 
 | ||||
|         <label class="checkbox-inline"><input type="checkbox" ng-model="newTask.module.singleLine">Single Line</label> | ||||
|         <span ng-if="getModuleDescriptionLoading" class="fa fa-spinner fa-spin"></span> | ||||
| 
 | ||||
|         <div ng-if="newTask.module.variables"> | ||||
|           <h4>Arguments</h4> | ||||
|           <div ng-repeat="variable in newTask.module.variables | orderBy : '-mandatory'"> | ||||
|             <!--<div class="input-group" ng-show="!variable.isVariable && !variable.choices.length"> | ||||
|               <span class="input-group-addon" >{{ variable.mandatory ? "*" : "" }} {{variable.name}}</span> | ||||
|               <input ng-model="variable.value" type="text" class="form-control" required="{{variable.mandatory}}"> | ||||
|             </div>--> | ||||
| 
 | ||||
|             <!--Input Type - Default Typeahead - variable or string--> | ||||
|             <div class="input-group" ng-show="!variable.choices.length"> | ||||
|               <span class="input-group-addon" uib-tooltip="{{variable.description}}">{{ variable.mandatory ? "*" : "" }} {{variable.name}}</span> | ||||
|               <!--<select class="form-control" ng-model="variable.value" ng-options="('"\{\{' + var.name + '\}\}"') as (var.name + '-' + var.value) disable when var.disabled for var in hostvars"> | ||||
|               </select>--> | ||||
| 
 | ||||
|               <input type="text" ng-model="variable.value" uib-typeahead="('\{\{' + var.key + '\}\}') as (var.key + ' = ' + var.value) for var in hostvars | filter: $viewValue"  class="form-control" placeholder="{{variable.description}}"> | ||||
| 
 | ||||
|               <span class="input-group-btn"> | ||||
|                 <button class="btn btn-secondary" ng-click="showComplexVariable(variable)" uib-tooltip="Click to configure Complex Variable">{ }</button> | ||||
|               </span> | ||||
| 
 | ||||
| 
 | ||||
|             </div> | ||||
| 
 | ||||
|             <!--Input Type - Choice--> | ||||
|             <div class="input-group" ng-show="variable.choices.length"> | ||||
|               <span class="input-group-addon" uib-tooltip="{{variable.description}}">{{ variable.mandatory ? "*" : "" }} {{variable.name}}</span> | ||||
|               <select class="form-control" ng-model="variable.value" ng-options="choice for choice in variable.choices"> | ||||
|               </select> | ||||
| 
 | ||||
|             </div> | ||||
| 
 | ||||
| 
 | ||||
|             <!--<div class="input-group" ng-show="variable.isComplexVariable"> | ||||
|               <span class="input-group-addon" >{{ variable.mandatory ? "*" : "" }} {{variable.name}}</span> | ||||
|               <!–<json-tree json="variable.complexValue"></json-tree>–> | ||||
|               <button class="btn btn-default" ng-click="showComplexVariable(variable)">Configure</button> | ||||
|             </div>--> | ||||
| 
 | ||||
|             <!--<label class="checkbox-inline"><input type="checkbox" ng-model="variable.isVariable">Variable</label>--> | ||||
|             <label class="checkbox-inline" ng-show="variable.isVariable"><input type="checkbox" ng-model="variable.isComplexVariable">Complex Variable</label> | ||||
| 
 | ||||
|           </div> | ||||
| 
 | ||||
|         </div> | ||||
| 
 | ||||
|       </div> | ||||
|       <div class="col-md-6"> | ||||
|         <div ng-if="showHelp"> | ||||
|           <h4>Help</h4> | ||||
|           <textarea rows="20" cols="60" ng-model="detailHelp" style="font-size: 13px"></textarea> | ||||
| 
 | ||||
|           <h4>Examples</h4> | ||||
|           <textarea rows="20" cols="60" ng-model="examples" style="font-size: 13px"></textarea> | ||||
| 
 | ||||
|         </div> | ||||
|         <!--{{newTask.module.variables}}--> | ||||
| 
 | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|   </div> | ||||
|   <div class="alert alert-danger" ng-if="err_msg">{{err_msg}}</div> | ||||
|   <div class="modal-footer"> | ||||
| 
 | ||||
|     <div class="row"> | ||||
|       <div class="col-md-3" style="text-align: left"> | ||||
|         <button class="btn btn-default" type="button" ng-click="reloadModuleDetails(newTask.module)">Reload Module <span class="fa fa-refresh"></span></button> | ||||
|       </div> | ||||
|       <div class="col-md-9" style="text-align: right"> | ||||
|         <button class="btn btn-primary" ng-click="createTask()">Save <span ng-if="createTaskLoading" class="fa fa-spinner fa-spin"></span></button> | ||||
|         <button class="btn btn-default" type="button" ng-click="cancel()">Close</button> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
| 
 | ||||
|   </div> | ||||
| </div> | ||||
| <!-- | ||||
| </script> | ||||
| --> | ||||
							
								
								
									
										0
									
								
								client/app/designer/tasks/tasks.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								client/app/designer/tasks/tasks.css
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										229
									
								
								client/app/designer/tasks/tasks.directive.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										229
									
								
								client/app/designer/tasks/tasks.directive.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,229 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| export default angular.module('webAppApp.tasks', []) | ||||
|   .directive('tasks', function(ansible, $uibModal) { | ||||
|     return { | ||||
|       templateUrl: 'app/designer/tasks/tasks.html', | ||||
|       restrict: 'EA', | ||||
|       scope: { | ||||
|         tasksList: '=', | ||||
|         selectedPlay: '=', | ||||
|         savePlaybook: '&', | ||||
|         selectedRole: '=', | ||||
|         updatePlaybookFileContent: '&', | ||||
|         executeAnsiblePlayBook: '&', | ||||
|         files: '=' //List of files for include purpose
 | ||||
|       }, | ||||
|       link: function (scope, element, attrs) { | ||||
|         scope.getModuleFromTask = ansible.getModuleFromTask; | ||||
| 
 | ||||
|         scope.buttonStates = {loading:false,save:false,err_msg:false}; | ||||
| 
 | ||||
|         scope.tasksMetaData = []; | ||||
| 
 | ||||
|         scope.$watch('tasksList',function(){ | ||||
|           console.log('tasks list changed'); | ||||
|           scope.tasksMetaData = []; | ||||
| 
 | ||||
|           angular.forEach(scope.tasksList,function(task){ | ||||
|             var taskModule = ansible.getModuleFromTask(task); | ||||
|             var taskName = task.name; | ||||
| 
 | ||||
|             if(taskModule === 'include'){ | ||||
|               taskName = task[taskModule].replace(/(.*yml) .*/,"$1") | ||||
|             } | ||||
| 
 | ||||
|             scope.tasksMetaData.push({taskModule:taskModule,taskName:taskName,selected:false}) | ||||
|           }) | ||||
| 
 | ||||
|         },true); | ||||
| 
 | ||||
| 
 | ||||
|         /** | ||||
|          * Detect when the user selects tasks. | ||||
|          * Enable play button if tasks are selected and has tags assigned | ||||
|          * Enable delete button if tasks are selected | ||||
|          */ | ||||
|         scope.$watch('tasksMetaData',function(newValue,oldValue){ | ||||
|           scope.selectedTasksPlayButton = false; | ||||
|           scope.selectedTasksDeleteButton = false | ||||
| 
 | ||||
|           if(!(scope.tasksMetaData))return; | ||||
| 
 | ||||
|           var selectedTasks = scope.tasksMetaData.filter(item => item.selected); | ||||
|           var includeTasks = scope.tasksMetaData.filter(item => item.taskModule === 'include'); | ||||
|           var selectedTasksWithoutTags = []; | ||||
| 
 | ||||
|           /** | ||||
|            * Find selected tasks without any tags. | ||||
|            * If there are any play button will not be enabled | ||||
|            */ | ||||
|           angular.forEach(scope.tasksMetaData,function(item,index){ | ||||
|             scope.tasksListItem = scope.tasksList[index]; | ||||
|             if(!scope.tasksListItem.tags && item.selected){ | ||||
|               selectedTasksWithoutTags.push(scope.tasksListItem) | ||||
|             } | ||||
|           }); | ||||
| 
 | ||||
|           console.log("selectedTasksWithoutTags=") | ||||
|           console.log(selectedTasksWithoutTags) | ||||
| 
 | ||||
|           if(selectedTasks.length){ | ||||
|             //if(!includeTasks.length && !selectedTasksWithoutTags.length){
 | ||||
|             if(!selectedTasksWithoutTags.length){ | ||||
|               scope.selectedTasksPlayButton = true | ||||
|             } | ||||
|             scope.selectedTasksDeleteButton = true | ||||
| 
 | ||||
|           }else{ | ||||
|             scope.selectedTasksPlayButton = false; | ||||
|             scope.selectedTasksDeleteButton = false | ||||
|           } | ||||
| 
 | ||||
|         },true); | ||||
| 
 | ||||
| 
 | ||||
|         //scope.moveUp = scope.moveUp();
 | ||||
|         //scope.moveDown = scope.moveDown();
 | ||||
|         scope.savePlaybook = scope.savePlaybook(); | ||||
|         scope.updatePlaybookFileContent = scope.updatePlaybookFileContent(); | ||||
|         scope.executeAnsiblePlayBook = scope.executeAnsiblePlayBook(); | ||||
| 
 | ||||
|         scope.showTaskModal = function(selectedTaskIndex, copyTask){ | ||||
|           var modalInstance = $uibModal.open({ | ||||
|             animation: true, | ||||
|             /*templateUrl: 'createTaskContent.html',*/ | ||||
|             templateUrl: 'app/designer/tasks/new_task/new_task.html', | ||||
|             controller: 'NewTaskController', | ||||
|             size: 'lg', | ||||
|             backdrop  : 'static', | ||||
|             keyboard  : false, | ||||
|             closeByEscape : false, | ||||
|             closeByDocument : false, | ||||
|             resolve: { | ||||
|               selectedProject: function () { | ||||
|                 return scope.$parent.selectedProject; | ||||
|               }, | ||||
|               selectedPlay: function(){ | ||||
|                 return scope.selectedPlay | ||||
|               }, | ||||
|               selectedRole: function(){ | ||||
|                 return scope.selectedRole | ||||
|               }, | ||||
|               tasksList: function () { | ||||
|                 return scope.tasksList; | ||||
|               }, | ||||
|               selectedTaskIndex: function(){ | ||||
|                 return selectedTaskIndex | ||||
|               }, | ||||
|               copyTask : function(){ | ||||
|                 return copyTask | ||||
|               }, | ||||
|               //List of files for include purpose
 | ||||
|               files: function(){ | ||||
|                 return scope.files | ||||
|               } | ||||
|             } | ||||
|           }); | ||||
| 
 | ||||
|           modalInstance.result.then( | ||||
|             function (newTask) { | ||||
|               // if(!selectedTaskIndex)
 | ||||
|               //   scope.tasksList.push(newTask);
 | ||||
|               scope.updatePlaybookFileContent(true); | ||||
|               //$scope.selectedPlay = {play: ""};
 | ||||
|             }, function () { | ||||
| 
 | ||||
|             }); | ||||
| 
 | ||||
|         }; | ||||
| 
 | ||||
| 
 | ||||
|         scope.deleteTask = function(index){ | ||||
|           scope.tasksList.splice(index,1); | ||||
|           scope.updatePlaybookFileContent(true); | ||||
|         }; | ||||
| 
 | ||||
|         scope.deleteTasks = function(){ | ||||
| 
 | ||||
|           scope.tasksMetaData.filter(function(item, index){ | ||||
|             if(item.selected){ | ||||
|               scope.tasksList.splice(index,1); | ||||
|             } | ||||
|           }); | ||||
|           scope.updatePlaybookFileContent(true); | ||||
|         }; | ||||
| 
 | ||||
| 
 | ||||
|         scope.moveUp = function(list,index,buttonVariable){ | ||||
|           if(!scope.preChangeData) scope.preChangeData = angular.copy(list); | ||||
|           var temp = angular.copy(list[index]); | ||||
|           list[index] = list[index-1]; | ||||
|           list[index-1] = temp; | ||||
| 
 | ||||
|           scope.updatePlaybookFileContent(false); | ||||
| 
 | ||||
|           scope.buttonStates.save = true | ||||
| 
 | ||||
|         }; | ||||
| 
 | ||||
|         scope.cancelChange = function(buttonVariable){ | ||||
|           if(scope.preChangeData){ | ||||
|             //scope.tasksList = angular.copy(scope.preChangeData);
 | ||||
|             scope.selectedPlay.play.tasks = angular.copy(scope.preChangeData); | ||||
|             scope.preChangeData = null | ||||
| 
 | ||||
|           } | ||||
|           scope.updatePlaybookFileContent(false,null,scope.tasksList); | ||||
| 
 | ||||
|           scope.buttonStates.save = false; | ||||
|         }; | ||||
| 
 | ||||
|         scope.moveDown = function(list,index,buttonVariable){ | ||||
|           if(!scope.preChangeData) scope.preChangeData = angular.copy(list); | ||||
|           var temp = angular.copy(list[index]); | ||||
|           list[index] = list[index+1]; | ||||
|           list[index+1] = temp; | ||||
| 
 | ||||
|           scope.updatePlaybookFileContent(false); | ||||
|           scope.buttonStates.save = true; | ||||
| 
 | ||||
|         }; | ||||
| 
 | ||||
| 
 | ||||
|         scope.executeSelectedTasks = function(){ | ||||
| 
 | ||||
|           /*var selectedTasks = scope.tasksMetaData.map(function(item){return item.selected});*/ | ||||
|           var selectedTags = []; | ||||
|           var selectedTaskNames = []; | ||||
|           /*if(selectedTasks.length){ | ||||
|            selectedTags = selectedTasks.map(function(item){return item.tags}); | ||||
|            selectedTaskNames = selectedTasks.map(function(item){return item.name}) | ||||
|            }*/ | ||||
| 
 | ||||
|           angular.forEach(scope.tasksMetaData, function(item,index){ | ||||
|             if(item.selected){ | ||||
|               if(scope.tasksList[index].tags){ | ||||
|                 // As tags is an array and each task can have multiple tags
 | ||||
|                 var task_tags = scope.tasksList[index].tags | ||||
|                 if(typeof task_tags == 'object') | ||||
|                   task_tags = task_tags[0]  //task_tags.join(',')
 | ||||
|                 selectedTags.push(task_tags); | ||||
|                 selectedTaskNames.push(scope.tasksList[index].name) | ||||
|               } | ||||
|             } | ||||
|           }); | ||||
| 
 | ||||
|           if(selectedTags.length){ | ||||
|             var play = scope.selectedPlay && scope.selectedPlay.play; | ||||
|             scope.executeAnsiblePlayBook(selectedTags,'Tasks',selectedTaskNames.join(","),play) | ||||
|           } | ||||
| 
 | ||||
| 
 | ||||
|         }; | ||||
| 
 | ||||
|       } | ||||
|     }; | ||||
|   }) | ||||
|   .name; | ||||
							
								
								
									
										20
									
								
								client/app/designer/tasks/tasks.directive.spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								client/app/designer/tasks/tasks.directive.spec.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Directive: tasks', function() { | ||||
|   // load the directive's module and view
 | ||||
|   beforeEach(module('webAppApp.tasks')); | ||||
|   beforeEach(module('app/designer/tasks/tasks.html')); | ||||
| 
 | ||||
|   var element, scope; | ||||
| 
 | ||||
|   beforeEach(inject(function($rootScope) { | ||||
|     scope = $rootScope.$new(); | ||||
|   })); | ||||
| 
 | ||||
|   it('should make hidden element visible', inject(function($compile) { | ||||
|     element = angular.element('<tasks></tasks>'); | ||||
|     element = $compile(element)(scope); | ||||
|     scope.$apply(); | ||||
|     expect(element.text()).to.equal('this is the tasks directive'); | ||||
|   })); | ||||
| }); | ||||
							
								
								
									
										53
									
								
								client/app/designer/tasks/tasks.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								client/app/designer/tasks/tasks.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| <div class="table-responsive"> | ||||
|   <table class="table"> | ||||
|     <thead> | ||||
|     <tr> | ||||
|       <th>Select</th> | ||||
|       <th>Name</th> | ||||
|       <th>Module</th> | ||||
|       <th>Actions</th> | ||||
|     </tr> | ||||
|     </thead> | ||||
|     <tbody> | ||||
|     <tr ng-repeat="task in tasksList"> | ||||
|       <td><input type="checkbox" ng-model="tasksMetaData[$index].selected"> | ||||
|       </td> | ||||
|       <td>{{tasksMetaData[$index].taskName}}</td> | ||||
|       <td>{{tasksMetaData[$index].taskModule}}</td> | ||||
|       <td> | ||||
|         <div class="btn-group"> | ||||
|           <label class="btn btn-default btn-sm" ng-click="showTaskModal($index)"><span | ||||
|             class="fa fa-edit"></span></label> | ||||
|           <!--<label class="btn btn-default btn-sm" ng-click="showTaskModal($index,true)"><span | ||||
|             class="fa fa-copy"></span></label>--> | ||||
|           <!--<label class="btn btn-danger btn-sm" ng-click="deleteTask($index)" | ||||
|                  confirm="Are you sure you want to delete?"><span class="fa fa-trash"></span></label>--> | ||||
|           <!--<div style="display: inline-block" tooltip-enable="!task.tags" | ||||
|                uib-tooltip="Tag must be assigned to play individually"><label class="btn btn-success btn-sm" | ||||
|                                                                               ng-disabled="!task.tags" | ||||
|                                                                               ng-click="executeAnsiblePlayBook(task.tags,'Task',task.name, selectedPlay)"><span | ||||
|             class="fa fa-play"></span></label></div>--> | ||||
|           <label class="btn btn-primary btn-sm" ng-disabled="$first" | ||||
|                  ng-click="moveUp(tasksList,$index,'saveTaskListLoading')"><span | ||||
|             class="fa fa-arrow-up"></span></label> | ||||
|           <label class="btn btn-primary btn-sm" ng-disabled="$last" | ||||
|                  ng-click="moveDown(tasksList,$index,'saveTaskListLoading')"><span | ||||
|             class="fa fa-arrow-down"></span></label> | ||||
|         </div> | ||||
|       </td> | ||||
|     </tr> | ||||
|     </tbody> | ||||
|   </table> | ||||
| </div> | ||||
| 
 | ||||
| <button class="btn btn-default" data-toggle="modal" data-target="#createTaskModal" ng-click="showTaskModal()"> | ||||
|   Create Task <span class="fa fa-plus"></span></button> | ||||
| <button class="btn btn-success" data-toggle="modal" ng-disabled="!selectedTasksPlayButton" ng-if="!buttonStates.save" | ||||
|         ng-click="executeSelectedTasks()"> Play <span class="fa fa-play"></span></button> | ||||
| <button class="btn btn-danger" data-toggle="modal" ng-disabled="!selectedTasksDeleteButton" ng-if="!buttonStates.save" confirm="Are you sure you want to delete the selected tasks?" | ||||
|         ng-click="deleteTasks()"> Delete <span class="fa fa-trash-o"></span></button> | ||||
| <button class="btn btn-primary" ng-if="buttonStates.save" | ||||
|         ng-click="savePlaybook(buttonStates)">Save <span ng-if="buttonStates.loading" | ||||
|                                                          class="fa fa-spinner fa-spin"></span></button> | ||||
| <button class="btn btn-warning" ng-if="buttonStates.save" | ||||
|         ng-click="cancelChange(buttonStates)">Cancel <span class="fa fa-times"></span></button> | ||||
							
								
								
									
										60
									
								
								client/app/directives/complexVar/complexVar.controller.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								client/app/directives/complexVar/complexVar.controller.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,60 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function complexVarController($scope,$filter) { | ||||
|   'ngInject'; | ||||
|   var loadMembers = function(){ | ||||
|     $scope.membersCopy = angular.copy($scope.members); | ||||
|     //var membersArray = $filter('addDotInKey')($filter('dictToKeyValueArray')($scope.membersCopy));
 | ||||
|     var membersArray = ($filter('dictToKeyValueArraySimple')($scope.membersCopy)); | ||||
| 
 | ||||
|     $scope.tabgroup = $scope.tabgroup || 0; | ||||
| 
 | ||||
|     if(membersArray.length) | ||||
|       $scope.membersCopy = membersArray; | ||||
|     else | ||||
|       $scope.membersCopy = [{key:"",value:""}]; | ||||
| 
 | ||||
|     $scope.path = $scope.path || ""; | ||||
| 
 | ||||
|     angular.forEach($scope.membersCopy,function(member){ | ||||
|       if(Object.prototype.toString.call(member.value) === '[object Object]'){ | ||||
|         member.type = 'object'; | ||||
|       }else if(Object.prototype.toString.call(member.value) === '[object Array]'){ | ||||
|         member.type = 'array'; | ||||
|       } | ||||
| 
 | ||||
|     }) | ||||
| 
 | ||||
|   }; | ||||
| 
 | ||||
|   loadMembers(); | ||||
| 
 | ||||
|   $scope.setMemberType = function(member,type){ | ||||
|     if(type === 'object'){ | ||||
|       member.value = {}; | ||||
|       member.type = 'object'; | ||||
|     }else if(type === 'array'){ | ||||
|       member.value = {}; | ||||
|       member.type = 'array'; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   $scope.$on('membersUpdated',function(){ | ||||
|     console.log('On Members Updated'); | ||||
|     console.log($scope.members); | ||||
|     loadMembers(); | ||||
|   }); | ||||
| 
 | ||||
|   $scope.$watch('membersCopy',function(){ | ||||
|     if($scope.type === 'object') | ||||
|       $scope.members = $filter('removeDotInKey')($filter('keyValueArrayToDict')($scope.membersCopy)); | ||||
|     else if($scope.type === 'array') | ||||
|       $scope.members = $filter('keyValueArrayToArray')($scope.membersCopy); | ||||
|   },true) | ||||
| } | ||||
| 
 | ||||
| export default angular.module('webAppApp.complexVarCtrl', []) | ||||
|   .controller('ComplexVarController', complexVarController) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Controller: ComplexVarCtrl', function() { | ||||
|   // load the controller's module
 | ||||
|   beforeEach(module('webAppApp.complexVar')); | ||||
| 
 | ||||
|   var ComplexVarCtrl; | ||||
| 
 | ||||
|   // Initialize the controller and a mock scope
 | ||||
|   beforeEach(inject(function($controller) { | ||||
|     ComplexVarCtrl = $controller('ComplexVarCtrl', {}); | ||||
|   })); | ||||
| 
 | ||||
|   it('should ...', function() { | ||||
|     expect(1).to.equal(1); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										8
									
								
								client/app/directives/complexVar/complexVar.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								client/app/directives/complexVar/complexVar.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| .complex-var-input { | ||||
|   line-height: 1; | ||||
|   padding-right: 5px; | ||||
|   padding-left: 5px; | ||||
|   padding-top: 2px; | ||||
|   padding-bottom: 2px; | ||||
|   height: 25px; | ||||
| } | ||||
							
								
								
									
										20
									
								
								client/app/directives/complexVar/complexVar.directive.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								client/app/directives/complexVar/complexVar.directive.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| export default angular.module('webAppApp.complexVar', []) | ||||
|   .directive('complexVar', function() { | ||||
|     return { | ||||
|       template: require('./complexVar.html'), | ||||
|       restrict: 'EA', | ||||
|       scope: { | ||||
|         members: '=', | ||||
|         path: '=', | ||||
|         type: '=', | ||||
|         inputWidth: '=', | ||||
|         tabgroup: '=?bind', | ||||
|         hostVars: '=' | ||||
|       }, | ||||
|       controller: 'ComplexVarController' | ||||
|     }; | ||||
|   }) | ||||
|   .name; | ||||
|  | @ -0,0 +1,20 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Directive: complexVar', function() { | ||||
|   // load the directive's module and view
 | ||||
|   beforeEach(module('webAppApp.complexVar')); | ||||
|   beforeEach(module('app/directives/complexVar/complexVar.html')); | ||||
| 
 | ||||
|   var element, scope; | ||||
| 
 | ||||
|   beforeEach(inject(function($rootScope) { | ||||
|     scope = $rootScope.$new(); | ||||
|   })); | ||||
| 
 | ||||
|   it('should make hidden element visible', inject(function($compile) { | ||||
|     element = angular.element('<complex-var></complex-var>'); | ||||
|     element = $compile(element)(scope); | ||||
|     scope.$apply(); | ||||
|     expect(element.text()).to.equal('this is the complexVar directive'); | ||||
|   })); | ||||
| }); | ||||
							
								
								
									
										38
									
								
								client/app/directives/complexVar/complexVar.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								client/app/directives/complexVar/complexVar.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| <div> | ||||
|   <div class="row"> | ||||
|     <div class="col-md-5" style="text-align: center" ng-if="type != 'array'"> | ||||
|       <h5>Key</h5> | ||||
|     </div> | ||||
|     <div class="col-md-5" style="text-align: center"> | ||||
|       <h5>Value</h5> | ||||
|     </div> | ||||
|   </div> | ||||
| 
 | ||||
|   <div ng-repeat="member in membersCopy"> | ||||
| 
 | ||||
|     <div class="row"> | ||||
|       <div class="col-md-5" style="padding:2px;" ng-if="type != 'array'"> | ||||
|         <input type="text" ng-model="member.key" class="form-control complex-var-input" tabindex="{{tabgroup + ($index * 10) + 1}}" > | ||||
|       </div> | ||||
|       <div class="col-md-5" style="padding:2px;"> | ||||
|         <!--<input type="text" ng-model="member.value">--> | ||||
|         <input type="text" ng-model="member.value" uib-typeahead="('\{\{' + var.key + '\}\}') as (var.key + ' = ' + var.value) for var in hostVars | filter: $viewValue"  class="form-control complex-var-input" tabindex="{{tabgroup +  ($index * 10) + 2}}"> | ||||
|       </div> | ||||
|       <div class="col-md-2" style="padding:2px;"> | ||||
|         <div class="btn-group" style="top: -7px;"> | ||||
|           <label ng-click="membersCopy.splice($index+1,0,{})" style="cursor: pointer;font-weight: bold;font-size: 20px; color:green" tabindex="{{tabgroup + ($index * 10) + 3}}" ng-keydown="($event.which === 13 || $event.which === 32) && membersCopy.splice($index+1,0,{})" >+</label> | ||||
|           <label ng-click="membersCopy.splice($index,1)" style="cursor: pointer;font-weight: bold;font-size: 25px; color:red" tabindex="{{tabgroup + ($index * 10) + 4}}">-</label> | ||||
|           <label ng-click="setMemberType(member,'object')" style="cursor: pointer;font-weight: bold;font-size: 12px;" tabindex="{{tabgroup + ($index * 10) + 5}}">{ }</label> | ||||
|           <label ng-click="setMemberType(member,'array')" style="cursor: pointer;font-weight: bold;font-size: 12px;" tabindex="{{tabgroup + ($index * 10) + 6}}">[ ]</label> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div style="margin-left:30px;"> | ||||
|       <complex-var ng-if="member.type == 'object' || member.type == 'array'" host-vars="hostVars" tabgroup="tabgroup + 100" type="member.type" members="member.value" path="member.key"> | ||||
|       </complex-var> | ||||
|     </div> | ||||
| 
 | ||||
|     <!--<input type="text" ng-model="member.value" uib-typeahead="('"\{\{' + var.name + '\}\}"') as (var.name + ' = ' + var.value) for var in hostvars | filter: $viewValue"  class="form-control">--> | ||||
|   </div> | ||||
| </div> | ||||
| 
 | ||||
							
								
								
									
										14
									
								
								client/app/filters/addDotInKey/addDotInKey.filter.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								client/app/filters/addDotInKey/addDotInKey.filter.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function addDotInKeyFilter() { | ||||
|   return function(input) { | ||||
|     return JSON.parse(JSON.stringify(input).replace(/__dot__/g,'.')); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export default angular.module('webAppApp.addDotInKey', []) | ||||
|   .filter('addDotInKey', addDotInKeyFilter) | ||||
|   .name; | ||||
							
								
								
									
										17
									
								
								client/app/filters/addDotInKey/addDotInKey.filter.spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								client/app/filters/addDotInKey/addDotInKey.filter.spec.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Filter: addDotInKey', function() { | ||||
|   // load the filter's module
 | ||||
|   beforeEach(module('webAppApp.addDotInKey')); | ||||
| 
 | ||||
|   // initialize a new instance of the filter before each test
 | ||||
|   var addDotInKey; | ||||
|   beforeEach(inject(function($filter) { | ||||
|     addDotInKey = $filter('addDotInKey'); | ||||
|   })); | ||||
| 
 | ||||
|   it('should return the input prefixed with "addDotInKey filter:"', function() { | ||||
|     var text = 'angularjs'; | ||||
|     expect(addDotInKey(text)).to.equal('addDotInKey filter: ' + text); | ||||
|   }); | ||||
| }); | ||||
|  | @ -0,0 +1,29 @@ | |||
| 'use strict'; | ||||
| const angular = require('angular'); | ||||
| 
 | ||||
| /*@ngInject*/ | ||||
| export function dictToKeyValueArrayFilter() { | ||||
|   var convert = function (input,prefix) { | ||||
|     var result = []; | ||||
|     angular.forEach(input,function(value,key){ | ||||
|       key = key.replace(/\./g,"__dot__"); | ||||
|       if(prefix){ | ||||
|         key = prefix + '.' + key; | ||||
|       } | ||||
|       if(typeof value != 'object'){ | ||||
|         result.push({"key":key ,"value":value}) | ||||
|       }else{ | ||||
|         result = result.concat(convert(value,key)) | ||||
|       } | ||||
| 
 | ||||
|     }); | ||||
|     return result; | ||||
|   }; | ||||
| 
 | ||||
|   return convert; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export default angular.module('webAppApp.dictToKeyValueArray', []) | ||||
|   .filter('dictToKeyValueArray', dictToKeyValueArrayFilter) | ||||
|   .name; | ||||
|  | @ -0,0 +1,17 @@ | |||
| 'use strict'; | ||||
| 
 | ||||
| describe('Filter: dictToKeyValueArray', function() { | ||||
|   // load the filter's module
 | ||||
|   beforeEach(module('webAppApp.dictToKeyValueArray')); | ||||
| 
 | ||||
|   // initialize a new instance of the filter before each test
 | ||||
|   var dictToKeyValueArray; | ||||
|   beforeEach(inject(function($filter) { | ||||
|     dictToKeyValueArray = $filter('dictToKeyValueArray'); | ||||
|   })); | ||||
| 
 | ||||
|   it('should return the input prefixed with "dictToKeyValueArray filter:"', function() { | ||||
|     var text = 'angularjs'; | ||||
|     expect(dictToKeyValueArray(text)).to.equal('dictToKeyValueArray filter: ' + text); | ||||
|   }); | ||||
| }); | ||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue