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
6
.babelrc
Normal file
6
.babelrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"presets": ["es2015"],
|
||||
"plugins": [
|
||||
"transform-class-properties"
|
||||
]
|
||||
}
|
0
.buildignore
Normal file
0
.buildignore
Normal file
21
.editorconfig
Normal file
21
.editorconfig
Normal file
|
@ -0,0 +1,21 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# Change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
254
.eslintrc
Normal file
254
.eslintrc
Normal file
|
@ -0,0 +1,254 @@
|
|||
{
|
||||
"parser": "babel-eslint",
|
||||
"env": {
|
||||
"es6": true
|
||||
},
|
||||
"globals": {},
|
||||
"plugins": [],
|
||||
"rules": {
|
||||
//Possible Errors
|
||||
"comma-dangle": 0, //disallow or enforce trailing commas
|
||||
"no-cond-assign": 2, //disallow assignment in conditional expressions
|
||||
"no-console": 0, //disallow use of console in the node environment
|
||||
"no-constant-condition": 1, //disallow use of constant expressions in conditions
|
||||
"no-control-regex": 2, //disallow control characters in regular expressions
|
||||
"no-debugger": 2, //disallow use of debugger
|
||||
"no-dupe-args": 2, //disallow duplicate arguments in functions
|
||||
"no-dupe-keys": 2, //disallow duplicate keys when creating object literals
|
||||
"no-duplicate-case": 0, //disallow a duplicate case label.
|
||||
"no-empty-character-class": 2, //disallow the use of empty character classes in regular expressions
|
||||
"no-empty": 2, //disallow empty statements
|
||||
"no-ex-assign": 2, //disallow assigning to the exception in a catch block
|
||||
"no-extra-boolean-cast": 2, //disallow double-negation boolean casts in a boolean context
|
||||
"no-extra-parens": 1, //disallow unnecessary parentheses
|
||||
"no-extra-semi": 2, //disallow unnecessary semicolons
|
||||
"no-func-assign": 2, //disallow overwriting functions written as function declarations
|
||||
"no-inner-declarations": 1, //disallow function or variable declarations in nested blocks
|
||||
"no-invalid-regexp": 2, //disallow invalid regular expression strings in the RegExp constructor
|
||||
"no-irregular-whitespace": 2, //disallow irregular whitespace outside of strings and comments
|
||||
"no-negated-in-lhs": 2, //disallow negation of the left operand of an in expression
|
||||
"no-obj-calls": 2, //disallow the use of object properties of the global object (Math and JSON) as functions
|
||||
"no-prototype-builtins": 0, //Disallow use of Object.prototypes builtins directly
|
||||
"no-regex-spaces": 2, //disallow multiple spaces in a regular expression literal
|
||||
"no-sparse-arrays": 1, //disallow sparse arrays
|
||||
"no-unexpected-multiline": 2, //Avoid code that looks like two expressions but is actually one
|
||||
"no-unreachable": 2, //disallow unreachable statements after a return, throw, continue, or break statement
|
||||
"no-unsafe-finally": 2, //disallow control flow statements in finally blocks
|
||||
"use-isnan": 2, //disallow comparisons with the value NaN
|
||||
"valid-jsdoc": 0, //Ensure JSDoc comments are valid
|
||||
"valid-typeof": 2, //Ensure that the results of typeof are compared against a valid string
|
||||
|
||||
//Best Practices
|
||||
"accessor-pairs": 0, //Enforces getter/setter pairs in objects
|
||||
"array-callback-return": 2, //Enforces return statements in callbacks of array's methods
|
||||
"block-scoped-var": 0, //treat var statements as if they were block scoped
|
||||
"complexity": 0, //specify the maximum cyclomatic complexity allowed in a program
|
||||
"consistent-return": 0, //require return statements to either always or never specify values
|
||||
"curly": [2, "multi-line"], //specify curly brace conventions for all control statements
|
||||
"default-case": 0, //require default case in switch statements
|
||||
"dot-location": [2, "property"], //enforces consistent newlines before or after dots
|
||||
"dot-notation": 2, //encourages use of dot notation whenever possible
|
||||
"eqeqeq": 0, //require the use of === and !==
|
||||
"guard-for-in": 0, //make sure for-in loops have an if statement
|
||||
"no-alert": 2, //disallow the use of alert, confirm, and prompt
|
||||
"no-caller": 0, //disallow use of arguments.caller or arguments.callee
|
||||
"no-case-declarations": 0, //disallow lexical declarations in case clauses
|
||||
"no-div-regex": 2, //disallow division operators explicitly at beginning of regular expression
|
||||
"no-else-return": 0, //disallow else after a return in an if
|
||||
"no-empty-function": 0, //disallow use of empty functions
|
||||
"no-empty-pattern": 2, //disallow use of empty destructuring patterns
|
||||
"no-eq-null": 2, //disallow comparisons to null without a type-checking operator
|
||||
"no-eval": 2, //disallow use of eval()
|
||||
"no-extend-native": 0, //disallow adding to native types
|
||||
"no-extra-bind": 1, //disallow unnecessary function binding
|
||||
"no-extra-label": 2, //disallow unnecessary labels
|
||||
"no-fallthrough": 0, //disallow fallthrough of case statements
|
||||
"no-floating-decimal": 0, //disallow the use of leading or trailing decimal points in numeric literals
|
||||
"no-implicit-coercion": 0, //disallow the type conversions with shorter notations
|
||||
"no-implicit-globals": 0, //disallow var and named functions in global scope
|
||||
"no-implied-eval": 2, //disallow use of eval()-like methods
|
||||
"no-invalid-this": 1, //disallow this keywords outside of classes or class-like objects
|
||||
"no-iterator": 2, //disallow usage of __iterator__ property
|
||||
"no-labels": 2, //disallow use of labeled statements
|
||||
"no-lone-blocks": 2, //disallow unnecessary nested blocks
|
||||
"no-loop-func": 2, //disallow creation of functions within loops
|
||||
"no-magic-numbers": 0, //disallow the use of magic numbers
|
||||
"no-multi-spaces": 2, //disallow use of multiple spaces
|
||||
"no-multi-str": 0, //disallow use of multiline strings
|
||||
"no-native-reassign": 2, //disallow reassignments of native objects
|
||||
"no-new-func": 1, //disallow use of new operator for Function object
|
||||
"no-new-wrappers": 2, //disallows creating new instances of String,Number, and Boolean
|
||||
"no-new": 2, //disallow use of the new operator when not part of an assignment or comparison
|
||||
"no-octal-escape": 0, //disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251";
|
||||
"no-octal": 0, //disallow use of octal literals
|
||||
"no-param-reassign": 0, //disallow reassignment of function parameters
|
||||
"no-process-env": 0, //disallow use of process.env
|
||||
"no-proto": 2, //disallow usage of __proto__ property
|
||||
"no-redeclare": 2, //disallow declaring the same variable more than once
|
||||
"no-return-assign": 2, //disallow use of assignment in return statement
|
||||
"no-script-url": 2, //disallow use of javascript: urls.
|
||||
"no-self-assign": 2, //disallow assignments where both sides are exactly the same
|
||||
"no-self-compare": 2, //disallow comparisons where both sides are exactly the same
|
||||
"no-sequences": 2, //disallow use of the comma operator
|
||||
"no-throw-literal": 2, //restrict what can be thrown as an exception
|
||||
"no-unmodified-loop-condition": 0, //disallow unmodified conditions of loops
|
||||
"no-unused-expressions": 0, //disallow usage of expressions in statement position
|
||||
"no-unused-labels": 2, //disallow unused labels
|
||||
"no-useless-call": 2, //disallow unnecessary .call() and .apply()
|
||||
"no-useless-concat": 2, //disallow unnecessary concatenation of literals or template literals
|
||||
"no-useless-escape": 2, //disallow unnecessary escape characters
|
||||
"no-void": 0, //disallow use of the void operator
|
||||
"no-warning-comments": 1, //disallow usage of configurable warning terms in comments (e.g. TODO or FIXME)
|
||||
"no-with": 2, //disallow use of the with statement
|
||||
"radix": 2, //require use of the second argument for parseInt()
|
||||
"vars-on-top": 0, //require declaration of all vars at the top of their containing scope
|
||||
"wrap-iife": 2, //require immediate function invocation to be wrapped in parentheses
|
||||
"yoda": 2, //require or disallow Yoda conditions
|
||||
|
||||
//Strict Mode
|
||||
"strict": 0, //controls location of Use Strict Directives
|
||||
|
||||
//Variables
|
||||
"init-declarations": 0, //enforce or disallow variable initializations at definition
|
||||
"no-catch-shadow": 2, //disallow the catch clause parameter name being the same as a variable in the outer scope
|
||||
"no-delete-var": 2, //disallow deletion of variables
|
||||
"no-label-var": 2, //disallow labels that share a name with a variable
|
||||
"no-restricted-globals": 0, //restrict usage of specified global variables
|
||||
"no-shadow-restricted-names": 2, //disallow shadowing of names such as arguments
|
||||
"no-shadow": [2, {"allow": ["err"]}], //disallow declaration of variables already declared in the outer scope
|
||||
"no-undef-init": 2, //disallow use of undefined when initializing variables
|
||||
"no-undef": 2, //disallow use of undeclared variables unless mentioned in a /*global */ block
|
||||
"no-undefined": 0, //disallow use of undefined variable
|
||||
"no-unused-vars": 2, //disallow declaration of variables that are not used in the code
|
||||
"no-use-before-define": 0, //disallow use of variables before they are defined
|
||||
|
||||
//Node.js and CommonJS
|
||||
"callback-return": 2, //enforce return after a callback
|
||||
"global-require": 0, //enforce require() on top-level module scope
|
||||
"handle-callback-err": 2, //enforce error handling in callbacks
|
||||
"no-mixed-requires": 2, //disallow mixing regular variable and require declarations
|
||||
"no-new-require": 2, //disallow use of new operator with the require function
|
||||
"no-path-concat": 2, //disallow string concatenation with __dirname and __filename
|
||||
"no-process-exit": 2, //disallow process.exit()
|
||||
"no-restricted-imports": 0, //restrict usage of specified node imports
|
||||
"no-restricted-modules": 0, //restrict usage of specified node modules
|
||||
"no-sync": 1, //disallow use of synchronous methods
|
||||
|
||||
//Stylistic Issues
|
||||
"array-bracket-spacing": [2, "never"], //enforce spacing inside array brackets
|
||||
"block-spacing": 0, //disallow or enforce spaces inside of single line blocks
|
||||
"brace-style": 2, //enforce one true brace style
|
||||
"camelcase": 1, //require camel case names
|
||||
"comma-spacing": [2, {"before": false, "after": true}], //enforce spacing before and after comma
|
||||
"comma-style": 2, //enforce one true comma style
|
||||
"computed-property-spacing": 2, //require or disallow padding inside computed properties
|
||||
"consistent-this": 2, //enforce consistent naming when capturing the current execution context
|
||||
"eol-last": 2, //enforce newline at the end of file, with no multiple empty lines
|
||||
"func-names": 0, //require function expressions to have a name
|
||||
"func-style": 0, //enforce use of function declarations or expressions
|
||||
"id-blacklist": 0, //blacklist certain identifiers to prevent them being used
|
||||
"id-length": 0, //this option enforces minimum and maximum identifier lengths (variable names, property names etc.)
|
||||
"id-match": 0, //require identifiers to match the provided regular expression
|
||||
"indent": ["error", 2], //specify tab or space width for your code
|
||||
"jsx-quotes": 0, //specify whether double or single quotes should be used in JSX attributes
|
||||
"key-spacing": 2, //enforce spacing between keys and values in object literal properties
|
||||
"keyword-spacing": [2, {
|
||||
"before": true,
|
||||
"after": true,
|
||||
"overrides": {
|
||||
"if": {"after": false},
|
||||
"for": {"after": false},
|
||||
"while": {"after": false},
|
||||
"catch": {"after": false}
|
||||
}
|
||||
}], //enforce spacing before and after keywords
|
||||
"linebreak-style": 2, //disallow mixed 'LF' and 'CRLF' as linebreaks
|
||||
"lines-around-comment": 0, //enforce empty lines around comments
|
||||
"max-depth": 1, //specify the maximum depth that blocks can be nested
|
||||
"max-len": [1, 200], //specify the maximum length of a line in your program
|
||||
"max-lines": 0, //enforce a maximum file length
|
||||
"max-nested-callbacks": 2, //specify the maximum depth callbacks can be nested
|
||||
"max-params": 0, //limits the number of parameters that can be used in the function declaration.
|
||||
"max-statements": 0, //specify the maximum number of statement allowed in a function
|
||||
"max-statements-per-line": 0, //enforce a maximum number of statements allowed per line
|
||||
"new-cap": 0, //require a capital letter for constructors
|
||||
"new-parens": 2, //disallow the omission of parentheses when invoking a constructor with no arguments
|
||||
"newline-after-var": 0, //require or disallow an empty newline after variable declarations
|
||||
"newline-before-return": 0, //require newline before return statement
|
||||
"newline-per-chained-call": [
|
||||
"error",
|
||||
{"ignoreChainWithDepth": 2}
|
||||
], //enforce newline after each call when chaining the calls
|
||||
"no-array-constructor": 2, //disallow use of the Array constructor
|
||||
"no-bitwise": 0, //disallow use of bitwise operators
|
||||
"no-continue": 0, //disallow use of the continue statement
|
||||
"no-inline-comments": 0, //disallow comments inline after code
|
||||
"no-lonely-if": 2, //disallow if as the only statement in an else block
|
||||
"no-mixed-operators": 0, //disallow mixes of different operators
|
||||
"no-mixed-spaces-and-tabs": 2, //disallow mixed spaces and tabs for indentation
|
||||
"no-multiple-empty-lines": 2, //disallow multiple empty lines
|
||||
"no-negated-condition": 0, //disallow negated conditions
|
||||
"no-nested-ternary": 0, //disallow nested ternary expressions
|
||||
"no-new-object": 2, //disallow the use of the Object constructor
|
||||
"no-plusplus": 0, //disallow use of unary operators, ++ and --
|
||||
"no-restricted-syntax": 0, //disallow use of certain syntax in code
|
||||
"no-spaced-func": 2, //disallow space between function identifier and application
|
||||
"no-ternary": 0, //disallow the use of ternary operators
|
||||
"no-trailing-spaces": 2, //disallow trailing whitespace at the end of lines
|
||||
"no-underscore-dangle": 0, //disallow dangling underscores in identifiers
|
||||
"no-unneeded-ternary": 2, //disallow the use of ternary operators when a simpler alternative exists
|
||||
"no-whitespace-before-property": 2, //disallow whitespace before properties
|
||||
"object-curly-newline": 0, //enforce consistent line breaks inside braces
|
||||
"object-curly-spacing": 0, //require or disallow padding inside curly braces
|
||||
"object-property-newline": 0, //enforce placing object properties on separate lines
|
||||
"one-var": [2, "never"], //require or disallow one variable declaration per function
|
||||
"one-var-declaration-per-line": 2, //require or disallow an newline around variable declarations
|
||||
"operator-assignment": 0, //require assignment operator shorthand where possible or prohibit it entirely
|
||||
"operator-linebreak": [1, "before"], //enforce operators to be placed before or after line breaks
|
||||
"padded-blocks": [2, "never"], //enforce padding within blocks
|
||||
"quote-props": [2, "as-needed"], //require quotes around object literal property names
|
||||
"quotes": [2, "single"], //specify whether backticks, double or single quotes should be used
|
||||
"require-jsdoc": 0, //Require JSDoc comment
|
||||
"semi-spacing": 2, //enforce spacing before and after semicolons
|
||||
"sort-imports": 0, //sort import declarations within module
|
||||
"semi": 2, //require or disallow use of semicolons instead of ASI
|
||||
"sort-vars": 0, //sort variables within the same declaration block
|
||||
"space-before-blocks": 2, //require or disallow a space before blocks
|
||||
"space-before-function-paren": [2, "never"], //require or disallow a space before function opening parenthesis
|
||||
"space-in-parens": 2, //require or disallow spaces inside parentheses
|
||||
"space-infix-ops": 2, //require spaces around operators
|
||||
"space-unary-ops": 2, //require or disallow spaces before/after unary operators
|
||||
"spaced-comment": 0, //require or disallow a space immediately following the // or /* in a comment
|
||||
"unicode-bom": 0, //require or disallow the Unicode BOM
|
||||
"wrap-regex": 0, //require regex literals to be wrapped in parentheses
|
||||
|
||||
//ECMAScript 6
|
||||
"arrow-body-style": [2, "as-needed"], //require braces in arrow function body
|
||||
"arrow-parens": [2, "as-needed"], //require parens in arrow function arguments
|
||||
"arrow-spacing": 2, //require space before/after arrow function's arrow
|
||||
"constructor-super": 2, //verify calls of super() in constructors
|
||||
"generator-star-spacing": 0, //enforce spacing around the * in generator functions
|
||||
"no-class-assign": 2, //disallow modifying variables of class declarations
|
||||
"no-confusing-arrow": 2, //disallow arrow functions where they could be confused with comparisons
|
||||
"no-const-assign": 2, //disallow modifying variables that are declared using const
|
||||
"no-dupe-class-members": 2, //disallow duplicate name in class members
|
||||
"no-duplicate-imports": 2, //disallow duplicate module imports
|
||||
"no-new-symbol": 2, //disallow use of the new operator with the Symbol object
|
||||
"no-this-before-super": 2, //disallow use of this/super before calling super() in constructors.
|
||||
"no-useless-computed-key": 2, //disallow unnecessary computed property keys in object literals
|
||||
"no-useless-constructor": 2, //disallow unnecessary constructor
|
||||
"no-useless-rename": 2, //disallow renaming import, export, and destructured assignments to the same name
|
||||
"no-var": 0, //require let or const instead of var
|
||||
"object-shorthand": 1, //require method and property shorthand syntax for object literals
|
||||
"prefer-arrow-callback": 0, //suggest using arrow functions as callbacks
|
||||
"prefer-const": 0, //suggest using const declaration for variables that are never modified after declared
|
||||
"prefer-reflect": 1, //suggest using Reflect methods where applicable
|
||||
"prefer-rest-params": 1, //suggest using the rest parameters instead of arguments
|
||||
"prefer-spread": 1, //suggest using the spread operator instead of .apply().
|
||||
"prefer-template": 1, //suggest using template literals instead of strings concatenation
|
||||
"require-yield": 2, //disallow generator functions that do not have yield
|
||||
"rest-spread-spacing": ["error", "never"], //enforce spacing between rest and spread operators and their expressions
|
||||
"template-curly-spacing": 2, //enforce spacing around embedded expressions of template strings
|
||||
"yield-star-spacing": [2, "after"] //enforce spacing around the * in yield* expressions
|
||||
}
|
||||
}
|
43
.gitattributes
vendored
Normal file
43
.gitattributes
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
* text=auto
|
||||
|
||||
# These files are text and should be normalized (Convert crlf => lf)
|
||||
*.php text
|
||||
*.css text
|
||||
*.js text
|
||||
*.htm text
|
||||
*.html text
|
||||
*.xml text
|
||||
*.txt text
|
||||
*.ini text
|
||||
*.inc text
|
||||
.htaccess text
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
# (binary is a macro for -text -diff)
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.mov binary
|
||||
*.mp4 binary
|
||||
*.mp3 binary
|
||||
*.flv binary
|
||||
*.fla binary
|
||||
*.swf binary
|
||||
*.gz binary
|
||||
*.zip binary
|
||||
*.7z binary
|
||||
*.ttf binary
|
||||
|
||||
# Documents
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
node_modules
|
||||
public
|
||||
.tmp
|
||||
.idea
|
||||
client/bower_components
|
||||
client/index.html
|
||||
dist
|
||||
/server/config/local.env.js
|
||||
npm-debug.log
|
||||
coverage
|
21
.travis.yml
Normal file
21
.travis.yml
Normal file
|
@ -0,0 +1,21 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- 6
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- node_js: 5.12.0
|
||||
before_script:
|
||||
- npm install -g gulp-cli node-gyp
|
||||
services: mongodb
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
72
.yo-rc.json
Normal file
72
.yo-rc.json
Normal file
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"generator-angular-fullstack": {
|
||||
"generatorVersion": "4.2.2",
|
||||
"endpointDirectory": "server/api/",
|
||||
"insertRoutes": true,
|
||||
"registerRoutesFile": "server/routes.js",
|
||||
"routesNeedle": "// Insert routes below",
|
||||
"routesBase": "/api/",
|
||||
"pluralizeRoutes": true,
|
||||
"insertSockets": true,
|
||||
"registerSocketsFile": "server/config/socketio.js",
|
||||
"socketsNeedle": "// Insert sockets below",
|
||||
"insertModels": true,
|
||||
"registerModelsFile": "server/sqldb/index.js",
|
||||
"modelsNeedle": "// Insert models below",
|
||||
"filters": {
|
||||
"js": true,
|
||||
"babel": true,
|
||||
"flow": false,
|
||||
"html": true,
|
||||
"css": true,
|
||||
"uirouter": true,
|
||||
"bootstrap": true,
|
||||
"uibootstrap": true,
|
||||
"auth": true,
|
||||
"models": true,
|
||||
"mongooseModels": true,
|
||||
"mongoose": true,
|
||||
"oauth": true,
|
||||
"googleAuth": true,
|
||||
"facebookAuth": true,
|
||||
"mocha": true,
|
||||
"jasmine": false,
|
||||
"should": false,
|
||||
"expect": true
|
||||
}
|
||||
},
|
||||
"generator-ng-component": {
|
||||
"routeDirectory": "client/app/",
|
||||
"directiveDirectory": "client/app/",
|
||||
"componentDirectory": "client/app/components/",
|
||||
"filterDirectory": "client/app/",
|
||||
"serviceDirectory": "client/app/",
|
||||
"basePath": "client",
|
||||
"moduleName": "",
|
||||
"modulePrompt": true,
|
||||
"filters": [
|
||||
"uirouter",
|
||||
"mocha",
|
||||
"expect",
|
||||
"uirouter",
|
||||
"es6",
|
||||
"webpack"
|
||||
],
|
||||
"extensions": [
|
||||
"babel",
|
||||
"js",
|
||||
"html",
|
||||
"css"
|
||||
],
|
||||
"directiveSimpleTemplates": "",
|
||||
"directiveComplexTemplates": "",
|
||||
"filterTemplates": "",
|
||||
"serviceTemplates": "",
|
||||
"factoryTemplates": "",
|
||||
"controllerTemplates": "",
|
||||
"componentTemplates": "",
|
||||
"decoratorTemplates": "",
|
||||
"providerTemplates": "",
|
||||
"routeTemplates": ""
|
||||
}
|
||||
}
|
49
Dockerfile
Normal file
49
Dockerfile
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Pull base image.
|
||||
FROM node:6.2.2
|
||||
|
||||
# Reset Root Password
|
||||
RUN echo "root:P@ssw0rd@123" | chpasswd
|
||||
|
||||
# Install Ansible
|
||||
RUN apt-get update && \
|
||||
apt-get install python-setuptools python-dev build-essential -y && \
|
||||
easy_install pip && \
|
||||
pip install ansible
|
||||
|
||||
# TO fix a bug
|
||||
RUN mkdir -p /root/.config/configstore && chmod g+rwx /root /root/.config /root/.config/configstore
|
||||
RUN useradd -u 1003 -d /home/app_user -m -s /bin/bash -p $(echo P@ssw0rd@123 | openssl passwd -1 -stdin) app_user
|
||||
|
||||
# Create data directory
|
||||
RUN mkdir -p /data
|
||||
|
||||
RUN chown -R app_user /usr/local && chown -R app_user /home/app_user && chown -R app_user /data
|
||||
|
||||
# Install VIM and Openssh-Server
|
||||
RUN apt-get update && apt-get install -y vim openssh-server
|
||||
|
||||
# Permit Root login
|
||||
RUN sed -i '/PermitRootLogin */cPermitRootLogin yes' /etc/ssh/sshd_config
|
||||
|
||||
# Generate SSH Keys
|
||||
RUN /usr/bin/ssh-keygen -A
|
||||
|
||||
# Start Open-ssh server
|
||||
RUN service ssh start
|
||||
|
||||
# Change user to app_user
|
||||
USER app_user
|
||||
|
||||
RUN mkdir -p /data/web-app
|
||||
COPY * /data/web-app
|
||||
|
||||
USER root
|
||||
RUN chown -R app_user /data/web-app
|
||||
|
||||
USER app_user
|
||||
WORKDIR /data/web-app
|
||||
|
||||
RUN npm install -g yo gulp-cli generator-angular-fullstack
|
||||
RUN npm install
|
||||
|
||||
ENTRYPOINT gulp serve
|
28
README.md
Normal file
28
README.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# app2
|
||||
|
||||
This project was generated with the [Angular Full-Stack Generator](https://github.com/DaftMonk/generator-angular-fullstack) version 4.2.2.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Git](https://git-scm.com/)
|
||||
- [Node.js and npm](nodejs.org) Node >= 4.x.x, npm >= 2.x.x
|
||||
- [Gulp](http://gulpjs.com/) (`npm install --global gulp`)
|
||||
- [MongoDB](https://www.mongodb.org/) - Keep a running daemon with `mongod`
|
||||
|
||||
### Developing
|
||||
|
||||
1. Run `npm install` to install server dependencies.
|
||||
|
||||
2. Run `mongod` in a separate shell to keep an instance of the MongoDB Daemon running
|
||||
|
||||
3. Run `gulp serve` to start the development server. It should automatically open the client in your browser when ready.
|
||||
|
||||
## Build & development
|
||||
|
||||
Run `gulp build` for building and `gulp serve` for preview.
|
||||
|
||||
## Testing
|
||||
|
||||
Running `npm test` will run the unit tests with karma.
|
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>
|
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