├── .bowerrc ├── .csslintrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .yo-rc.json ├── Dockerfile ├── LICENSE ├── README.md ├── Vagrantfile ├── application.js ├── bootstrap.sh ├── bower.json ├── config.js ├── config ├── config.js ├── env │ ├── all.js │ ├── development.js │ └── production.js └── init.js ├── dashboard.css ├── gruntfile.js ├── humans.txt ├── index.html ├── karma.conf.js ├── modules ├── apis │ ├── apis.client.module.js │ ├── config │ │ ├── apis.client.config.js │ │ ├── apis.client.constants.js │ │ └── apis.client.routes.js │ ├── controllers │ │ └── apis.client.controller.js │ ├── directives │ │ └── pluginform.client.directive.js │ ├── services │ │ └── apis.client.service.js │ ├── tests │ │ └── apis.client.controller.test.js │ └── views │ │ ├── create-api.client.view.html │ │ ├── edit-api.client.view.html │ │ ├── list-apis.client.view.html │ │ ├── plugin.form.directive.html │ │ ├── view-api-plugin.client.view.html │ │ └── view-api.client.view.html ├── consumers │ ├── config │ │ ├── consumers.client.config.js │ │ └── consumers.client.routes.js │ ├── consumers.client.module.js │ ├── controllers │ │ └── consumers.client.controller.js │ ├── services │ │ └── consumers.client.service.js │ ├── tests │ │ └── consumers.client.controller.test.js │ └── views │ │ ├── create-consumer.client.view.html │ │ ├── crud-plugin-consumer.client.view.html │ │ ├── edit-consumer.client.view.html │ │ ├── list-consumers.client.view.html │ │ ├── view-api-plugin-consumer.client.view.html │ │ └── view-consumer.client.view.html └── core │ ├── config │ ├── core.client.constants.js │ └── core.client.routes.js │ ├── controllers │ ├── header.client.controller.js │ └── home.client.controller.js │ ├── core.client.module.js │ ├── css │ └── core.css │ ├── img │ ├── brand │ │ ├── favicon.ico │ │ └── logo.png │ └── loaders │ │ └── loader.gif │ ├── services │ └── menus.client.service.js │ ├── tests │ ├── header.client.controller.test.js │ └── home.client.controller.test.js │ └── views │ ├── header.client.view.html │ └── home.client.view.html ├── package.json ├── robots.txt └── test ├── .jshintrc ├── karma.conf.js └── spec └── controllers ├── about.js ├── apis.js ├── consumers.js └── main.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "lib" 3 | } 4 | -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "adjoining-classes": false, 3 | "box-model": false, 4 | "box-sizing": false, 5 | "floats": false, 6 | "font-sizes": false, 7 | "important": false, 8 | "known-properties": false, 9 | "overqualified-elements": false, 10 | "qualified-headings": false, 11 | "regex-selectors": false, 12 | "unique-headings": false, 13 | "universal-selector": false, 14 | "unqualified-attributes": false 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | lib/ 6 | .vagrant 7 | npm-debug.log 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, // Enable globals available when code is running inside of the NodeJS runtime environment. 3 | "browser": true, // Standard browser globals e.g. `window`, `document`. 4 | "esnext": true, // Allow ES.next specific features such as `const` and `let`. 5 | "bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.). 6 | "camelcase": false, // Permit only camelcase for `var` and `object indexes`. 7 | "curly": false, // Require {} for every new block or scope. 8 | "eqeqeq": true, // Require triple equals i.e. `===`. 9 | "immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 10 | "latedef": true, // Prohibit variable use before definition. 11 | "newcap": true, // Require capitalization of all constructor functions e.g. `new F()`. 12 | "noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`. 13 | "quotmark": "single", // Define quotes to string values. 14 | "regexp": true, // Prohibit `.` and `[^...]` in regular expressions. 15 | "undef": true, // Require all non-global variables be declared before they are used. 16 | "unused": false, // Warn unused variables. 17 | "strict": true, // Require `use strict` pragma in every file. 18 | "trailing": true, // Prohibit trailing whitespaces. 19 | "smarttabs": false, // Suppresses warnings about mixed tabs and spaces 20 | "globals": { // Globals variables. 21 | "jasmine": true, 22 | "angular": true, 23 | "ApplicationConfiguration": true 24 | }, 25 | "predef": [ // Extra globals. 26 | "define", 27 | "require", 28 | "exports", 29 | "module", 30 | "describe", 31 | "before", 32 | "beforeEach", 33 | "after", 34 | "afterEach", 35 | "it", 36 | "inject", 37 | "expect", 38 | "URI" 39 | ], 40 | "indent": 4, // Specify indentation spacing 41 | "devel": true, // Allow development statements e.g. `console.log();`. 42 | "noempty": true // Prohibit use of empty blocks. 43 | } 44 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'iojs' 5 | - '0.12' 6 | - '0.10' 7 | before_script: 8 | - 'npm install -g bower grunt-cli' 9 | - 'bower install' 10 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:wheezy 2 | 3 | MAINTAINER mike@mikangali.com 4 | 5 | WORKDIR /app 6 | 7 | # Install meanjs tools 8 | RUN npm install -g grunt-cli 9 | RUN npm install -g bower 10 | 11 | # Get mean quick start app 12 | RUN git clone https://github.com/rsdevigo/jungle.git . 13 | 14 | # ADD package.json /app 15 | # ADD .bowerrc /app 16 | 17 | # Install Mean.JS packages 18 | RUN npm install 19 | 20 | # Manually trigger bower. Why doesnt this work via npm install? 21 | RUN bower install --config.interactive=false --allow-root 22 | 23 | # currently only works for development 24 | ENV NODE_ENV development 25 | ENV KONGURL http://localhost:8001 26 | 27 | RUN echo "'use strict'; angular.module('core').constant('KONGURL', ['$KONGURL']);" > modules/core/config/core.client.constants.js 28 | 29 | RUN cat modules/core/config/core.client.constants.js 30 | 31 | # Expose ports: server (3000), livereload (35729) 32 | EXPOSE 3000 35729 33 | CMD ["grunt", "serve"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JUNGLE 2 | 3 | [![Join the chat at https://gitter.im/rsdevigo/jungle](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/rsdevigo/jungle?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | [![Build Status](https://travis-ci.org/rsdevigo/jungle.svg?branch=master)](https://travis-ci.org/rsdevigo/jungle) 5 | 6 | ![](https://i.imgur.com/MuFFweF.png) 7 | 8 | GUI to [KONG](http://getkong.org). 9 | 10 | ## Prerequisites 11 | - Nodejs 12 | - Npm 13 | - Grunt 14 | - Bower 15 | 16 | ## Install dependencies 17 | ``` 18 | $ [sudo] npm install 19 | $ bower install 20 | ``` 21 | 22 | ## Configure the Kong url 23 | In ./modules/core/config/core.client.constants.js change the KONGURL. 24 | 25 | ## Build & development 26 | 27 | - Run `grunt` for building and `grunt serve` for preview. 28 | 29 | - Open the address http://localhost:3000 30 | 31 | ## Testing 32 | 33 | Running `grunt test` will run the unit tests with karma. 34 | 35 | ## Gitter chat & Community 36 | 37 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/rsdevigo/jungle?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 38 | 39 | ## Roadmap for version 0.0.1 40 | 41 | - Error treatment for messages from server. 42 | - Paginate results using the *next* attribute of response from server. 43 | - Add the all plugins in PLUGINSAVAILABLE. ![](http://cmsresources.windowsphone.com/windowsphone/lv-LV/How-to/wp7/inline/office-icon-done.png) 44 | - Create a active plugin per consumer in API section. 45 | - Add favicon and Jungle logo ![](http://cmsresources.windowsphone.com/windowsphone/lv-LV/How-to/wp7/inline/office-icon-done.png) 46 | - Dockerfile ![](http://cmsresources.windowsphone.com/windowsphone/lv-LV/How-to/wp7/inline/office-icon-done.png) 47 | - Write the tests ![](http://cmsresources.windowsphone.com/windowsphone/lv-LV/How-to/wp7/inline/office-icon-done.png) 48 | - Add travis webhook ![](http://cmsresources.windowsphone.com/windowsphone/lv-LV/How-to/wp7/inline/office-icon-done.png) 49 | 50 | 51 | ## License 52 | 53 | ``` 54 | Copyright 2015 Jungle Contributors 55 | 56 | Licensed under the Apache License, Version 2.0 (the "License"); 57 | you may not use this file except in compliance with the License. 58 | You may obtain a copy of the License at 59 | 60 | http://www.apache.org/licenses/LICENSE-2.0 61 | 62 | Unless required by applicable law or agreed to in writing, software 63 | distributed under the License is distributed on an "AS IS" BASIS, 64 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 65 | See the License for the specific language governing permissions and 66 | limitations under the License. 67 | ``` 68 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = "2" 6 | 7 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 8 | # All Vagrant configuration is done here. The most common configuration 9 | # options are documented and commented below. For a complete reference, 10 | # please see the online documentation at vagrantup.com. 11 | 12 | # Every Vagrant virtual environment requires a box to build off of. 13 | config.vm.box = "ubuntu/trusty64" 14 | config.vm.provision :shell, path: "bootstrap.sh" 15 | 16 | # Disable automatic box update checking. If you disable this, then 17 | # boxes will only be checked for updates when the user runs 18 | # `vagrant box outdated`. This is not recommended. 19 | # config.vm.box_check_update = false 20 | 21 | # Create a forwarded port mapping which allows access to a specific port 22 | # within the machine from a port on the host machine. In the example below, 23 | # accessing "localhost:8080" will access port 80 on the guest machine. 24 | # config.vm.network "forwarded_port", guest: 8000, host: 8000 25 | # config.vm.network "forwarded_port", guest: 8001, host: 8001 26 | config.vm.network "forwarded_port", guest: 3000, host: 3000 27 | 28 | # Create a private network, which allows host-only access to the machine 29 | # using a specific IP. 30 | # config.vm.network "private_network", ip: "192.168.33.10" 31 | 32 | # Create a public network, which generally matched to bridged network. 33 | # Bridged networks make the machine appear as another physical device on 34 | # your network. 35 | # config.vm.network "public_network" 36 | 37 | # If true, then any SSH connections made will enable agent forwarding. 38 | # Default value: false 39 | # config.ssh.forward_agent = true 40 | 41 | # Share an additional folder to the guest VM. The first argument is 42 | # the path on the host to the actual folder. The second argument is 43 | # the path on the guest to mount the folder. And the optional third 44 | # argument is a set of non-required options. 45 | # config.vm.synced_folder "workspace", "/home/vagrant/workspace" 46 | 47 | # Provider-specific configuration so you can fine-tune various 48 | # backing providers for Vagrant. These expose provider-specific options. 49 | # Example for VirtualBox: 50 | # 51 | config.vm.provider "virtualbox" do |vb| 52 | # # Don't boot with headless mode 53 | # vb.gui = true 54 | # 55 | # # Use VBoxManage to customize the VM. For example to change memory: 56 | vb.name = "jungle" 57 | vb.memory = 1024 58 | # vb.customize ["modifyvm", :id, "--memory", "1024"] 59 | end 60 | # 61 | # View the documentation for the provider you're using for more 62 | # information on available options. 63 | 64 | # Enable provisioning with CFEngine. CFEngine Community packages are 65 | # automatically installed. For example, configure the host as a 66 | # policy server and optionally a policy file to run: 67 | # 68 | # config.vm.provision "cfengine" do |cf| 69 | # cf.am_policy_hub = true 70 | # # cf.run_file = "motd.cf" 71 | # end 72 | # 73 | # You can also configure and bootstrap a client to an existing 74 | # policy server: 75 | # 76 | # config.vm.provision "cfengine" do |cf| 77 | # cf.policy_server_address = "10.0.2.15" 78 | # end 79 | 80 | # Enable provisioning with Puppet stand alone. Puppet manifests 81 | # are contained in a directory path relative to this Vagrantfile. 82 | # You will need to create the manifests directory and a manifest in 83 | # the file default.pp in the manifests_path directory. 84 | # 85 | # config.vm.provision "puppet" do |puppet| 86 | # puppet.manifests_path = "manifests" 87 | # puppet.manifest_file = "site.pp" 88 | # end 89 | 90 | # Enable provisioning with chef solo, specifying a cookbooks path, roles 91 | # path, and data_bags path (all relative to this Vagrantfile), and adding 92 | # some recipes and/or roles. 93 | # 94 | # config.vm.provision "chef_solo" do |chef| 95 | # chef.cookbooks_path = "../my-recipes/cookbooks" 96 | # chef.roles_path = "../my-recipes/roles" 97 | # chef.data_bags_path = "../my-recipes/data_bags" 98 | # chef.add_recipe "mysql" 99 | # chef.add_role "web" 100 | # 101 | # # You may also specify custom JSON attributes: 102 | # chef.json = { mysql_password: "foo" } 103 | # end 104 | 105 | # Enable provisioning with chef server, specifying the chef server URL, 106 | # and the path to the validation key (relative to this Vagrantfile). 107 | # 108 | # The Opscode Platform uses HTTPS. Substitute your organization for 109 | # ORGNAME in the URL and validation key. 110 | # 111 | # If you have your own Chef Server, use the appropriate URL, which may be 112 | # HTTP instead of HTTPS depending on your configuration. Also change the 113 | # validation key to validation.pem. 114 | # 115 | # config.vm.provision "chef_client" do |chef| 116 | # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME" 117 | # chef.validation_key_path = "ORGNAME-validator.pem" 118 | # end 119 | # 120 | # If you're using the Opscode platform, your validator client is 121 | # ORGNAME-validator, replacing ORGNAME with your organization name. 122 | # 123 | # If you have your own Chef Server, the default validation client name is 124 | # chef-validator, unless you changed the configuration. 125 | # 126 | # chef.validation_client_name = "ORGNAME-validator" 127 | end 128 | -------------------------------------------------------------------------------- /application.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Start by defining the main module and adding the module dependencies 4 | angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies); 5 | 6 | // Setting HTML5 Location Mode 7 | angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider', 8 | function($locationProvider) { 9 | $locationProvider.hashPrefix('!'); 10 | } 11 | ]); 12 | 13 | //Then define the init function for starting up the application 14 | angular.element(document).ready(function() { 15 | //Fixing facebook bug with redirect 16 | if (window.location.hash === '#_=_') window.location.hash = '#!'; 17 | 18 | //Then init the app 19 | angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]); 20 | }); -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | echo "=========================" 2 | echo "Provisioning VM..." 3 | echo "=========================" 4 | 5 | cd /home/vagrant 6 | 7 | echo "Installing mongodb..." 8 | sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 9 | echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list 10 | sudo apt-get update 11 | sudo apt-get install -y mongodb-org 12 | sudo service mongod start 13 | 14 | echo "Adding node.js repository..." 15 | curl -sL https://deb.nodesource.com/setup | sudo bash - 16 | 17 | echo "Installing apps..." 18 | sudo apt-get install -y emacs24-nox unzip bash nodejs git 19 | sudo npm install -g npm grunt-cli 20 | 21 | echo "Installing liquid prompt..." 22 | git clone https://github.com/nojhan/liquidprompt.git 23 | echo "source ~/liquidprompt/liquidprompt" >> .bashrc 24 | 25 | echo "Installing Yeoman & generator..." 26 | sudo npm install -g yo generator-meanjs 27 | 28 | echo "=========================" 29 | echo "Provisioning finished" 30 | echo "=========================" 31 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jungle", 3 | "version": "0.0.1", 4 | "description": "GUI of Kong API Gateway", 5 | "dependencies": { 6 | "bootstrap": "~3", 7 | "angular": "~1.2", 8 | "angular-resource": "~1.2", 9 | "angular-mocks": "~1.2", 10 | "angular-cookies": "~1.2", 11 | "angular-animate": "~1.2", 12 | "angular-touch": "~1.2", 13 | "angular-sanitize": "~1.2", 14 | "angular-bootstrap": "~0.11.2", 15 | "angular-ui-utils": "~0.1.1", 16 | "angular-ui-router": "*", 17 | "font-awesome": "*", 18 | "angular-breadcrumb": "*", 19 | "uri.js":"*", 20 | "angular-xeditable": "*", 21 | "ngstorage":"*", 22 | "ngInfiniteScroll":"*" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Init the application configuration module for AngularJS application 4 | var ApplicationConfiguration = (function() { 5 | // Init module configuration options 6 | var applicationModuleName = 'jungle'; 7 | var applicationModuleVendorDependencies = ['ngResource', 'ngCookies', 'ngAnimate', 'ngTouch', 'ngSanitize', 'ui.router', 'ui.bootstrap', 'ui.utils', 'ncy-angular-breadcrumb', 'xeditable', 'ngStorage', 'infinite-scroll']; 8 | 9 | // Add a new vertical module 10 | var registerModule = function(moduleName, dependencies) { 11 | // Create angular module 12 | angular.module(moduleName, dependencies || []); 13 | 14 | // Add the module to the AngularJS configuration file 15 | angular.module(applicationModuleName).requires.push(moduleName); 16 | }; 17 | 18 | return { 19 | applicationModuleName: applicationModuleName, 20 | applicationModuleVendorDependencies: applicationModuleVendorDependencies, 21 | registerModule: registerModule 22 | }; 23 | })(); -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'), 7 | glob = require('glob'); 8 | 9 | /** 10 | * Load app configurations 11 | */ 12 | module.exports = _.extend( 13 | require('./env/all'), 14 | require('./env/' + process.env.NODE_ENV) || {} 15 | ); 16 | 17 | /** 18 | * Get files by glob patterns 19 | */ 20 | module.exports.getGlobbedFiles = function(globPatterns, removeRoot) { 21 | // For context switching 22 | var _this = this; 23 | 24 | // URL paths regex 25 | var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i'); 26 | 27 | // The output array 28 | var output = []; 29 | 30 | // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob 31 | if (_.isArray(globPatterns)) { 32 | globPatterns.forEach(function(globPattern) { 33 | output = _.union(output, _this.getGlobbedFiles(globPattern, removeRoot)); 34 | }); 35 | } else if (_.isString(globPatterns)) { 36 | if (urlRegex.test(globPatterns)) { 37 | output.push(globPatterns); 38 | } else { 39 | glob(globPatterns, { 40 | sync: true 41 | }, function(err, files) { 42 | if (removeRoot) { 43 | files = files.map(function(file) { 44 | return file.replace(removeRoot, ''); 45 | }); 46 | } 47 | 48 | output = _.union(output, files); 49 | }); 50 | } 51 | } 52 | 53 | return output; 54 | }; 55 | 56 | /** 57 | * Get the modules JavaScript files 58 | */ 59 | module.exports.getJavaScriptAssets = function(includeTests) { 60 | var output = this.getGlobbedFiles(this.assets.lib.js.concat(this.assets.js)); 61 | 62 | // To include tests 63 | if (includeTests) { 64 | output = _.union(output, this.getGlobbedFiles(this.assets.tests)); 65 | } 66 | 67 | return output; 68 | }; 69 | 70 | /** 71 | * Get the modules CSS files 72 | */ 73 | module.exports.getCSSAssets = function() { 74 | var output = this.getGlobbedFiles(this.assets.lib.css.concat(this.assets.css)); 75 | return output; 76 | }; -------------------------------------------------------------------------------- /config/env/all.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | assets: { 5 | lib: { 6 | css: [ 7 | 'lib/bootstrap/dist/css/bootstrap.css', 8 | 'lib/bootstrap/dist/css/bootstrap-theme.css', 9 | 'lib/font-awesome/css/font-awesome.css' 10 | ], 11 | js: [ 12 | 'lib/angular/angular.js', 13 | 'lib/angular-resource/angular-resource.js', 14 | 'lib/angular-cookies/angular-cookies.js', 15 | 'lib/angular-animate/angular-animate.js', 16 | 'lib/angular-touch/angular-touch.js', 17 | 'lib/angular-sanitize/angular-sanitize.js', 18 | 'lib/angular-ui-router/release/angular-ui-router.js', 19 | 'lib/angular-ui-utils/ui-utils.js', 20 | 'lib/angular-bootstrap/ui-bootstrap-tpls.js', 21 | 'lib/angular-breadcrumb/release/angular-breadcrumb.js', 22 | 'lib/angular-xeditable/dist/js/xeditable.js', 23 | 'lib/ngstorage/ngStorage.js', 24 | 'lib/ngInfiniteScroll/build/ng-infinite-scroll.js' 25 | ] 26 | }, 27 | css: [ 28 | 'modules/**/css/*.css', 29 | 'dashboard.css' 30 | ], 31 | js: [ 32 | 'config.js', 33 | 'application.js', 34 | 'modules/*/*.js', 35 | 'modules/*/*[!tests]*/*.js' 36 | ], 37 | tests: [ 38 | 'lib/angular-mocks/angular-mocks.js', 39 | 'modules/*/tests/*.js' 40 | ] 41 | } 42 | }; -------------------------------------------------------------------------------- /config/env/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /config/env/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | assets: { 5 | lib: { 6 | css: [ 7 | 'lib/bootstrap/dist/css/bootstrap.min.css', 8 | 'lib/bootstrap/dist/css/bootstrap-theme.min.css', 9 | ], 10 | js: [ 11 | 'lib/angular/angular.min.js', 12 | 'lib/angular-resource/angular-resource.js', 13 | 'lib/angular-cookies/angular-cookies.js', 14 | 'lib/angular-animate/angular-animate.js', 15 | 'lib/angular-touch/angular-touch.js', 16 | 'lib/angular-sanitize/angular-sanitize.js', 17 | 'lib/angular-ui-router/release/angular-ui-router.min.js', 18 | 'lib/angular-ui-utils/ui-utils.min.js', 19 | 'lib/angular-bootstrap/ui-bootstrap-tpls.min.js' 20 | ] 21 | }, 22 | css: 'dist/application.min.css', 23 | js: 'dist/application.min.js' 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /config/init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var glob = require('glob'), 7 | chalk = require('chalk'); 8 | 9 | /** 10 | * Module init function. 11 | */ 12 | module.exports = function() { 13 | /** 14 | * Before we begin, lets set the environment variable 15 | * We'll Look for a valid NODE_ENV variable and if one cannot be found load the development NODE_ENV 16 | */ 17 | glob('./config/env/' + process.env.NODE_ENV + '.js', { 18 | sync: true 19 | }, function(err, environmentFiles) { 20 | if (!environmentFiles.length) { 21 | if (process.env.NODE_ENV) { 22 | console.error(chalk.red('No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead')); 23 | } else { 24 | console.error(chalk.red('NODE_ENV is not defined! Using default development environment')); 25 | } 26 | 27 | process.env.NODE_ENV = 'development'; 28 | } else { 29 | console.log(chalk.black.bgWhite('Application loaded using the "' + process.env.NODE_ENV + '" environment configuration')); 30 | } 31 | }); 32 | 33 | }; -------------------------------------------------------------------------------- /dashboard.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Base structure 3 | */ 4 | 5 | /* Move down content because we have a fixed navbar that is 50px tall */ 6 | body { 7 | padding-top: 50px; 8 | } 9 | 10 | 11 | /* 12 | * Global add-ons 13 | */ 14 | 15 | .sub-header { 16 | padding-bottom: 10px; 17 | border-bottom: 1px solid #eee; 18 | } 19 | 20 | /* 21 | * Top navigation 22 | * Hide default border to remove 1px line. 23 | */ 24 | .navbar-fixed-top { 25 | border: 0; 26 | } 27 | 28 | /* 29 | * Sidebar 30 | */ 31 | 32 | /* Hide for mobile, show later */ 33 | .sidebar { 34 | display: none; 35 | } 36 | @media (min-width: 768px) { 37 | .sidebar { 38 | position: fixed; 39 | top: 51px; 40 | bottom: 0; 41 | left: 0; 42 | z-index: 1000; 43 | display: block; 44 | padding: 20px; 45 | overflow-x: hidden; 46 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 47 | background-color: #f5f5f5; 48 | border-right: 1px solid #eee; 49 | } 50 | } 51 | 52 | /* Sidebar navigation */ 53 | .nav-sidebar { 54 | margin-right: -21px; /* 20px padding + 1px border */ 55 | margin-bottom: 20px; 56 | margin-left: -20px; 57 | } 58 | .nav-sidebar > li > a { 59 | padding-right: 20px; 60 | padding-left: 20px; 61 | } 62 | .nav-sidebar > .active > a, 63 | .nav-sidebar > .active > a:hover, 64 | .nav-sidebar > .active > a:focus { 65 | color: #fff; 66 | background-color: #428bca; 67 | } 68 | 69 | 70 | /* 71 | * Main content 72 | */ 73 | 74 | .main { 75 | padding: 20px; 76 | } 77 | @media (min-width: 768px) { 78 | .main { 79 | padding-right: 40px; 80 | padding-left: 40px; 81 | } 82 | } 83 | .main .page-header { 84 | margin-top: 0; 85 | } 86 | 87 | 88 | /* 89 | * Placeholder dashboard ideas 90 | */ 91 | 92 | .placeholders { 93 | margin-bottom: 30px; 94 | text-align: center; 95 | } 96 | .placeholders h4 { 97 | margin-bottom: 0; 98 | } 99 | .placeholder { 100 | margin-bottom: 20px; 101 | } 102 | .placeholder img { 103 | display: inline-block; 104 | border-radius: 50%; 105 | } 106 | 107 | 108 | /* 109 | * Table 110 | */ 111 | 112 | th.sortable { 113 | cursor: pointer; 114 | } 115 | .table-2-column th, .table-2-column td { 116 | width: 50%; 117 | } -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2015-06-10 using generator-angular 0.11.1 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | 10 | module.exports = function (grunt) { 11 | 12 | // Load grunt tasks automatically 13 | require('load-grunt-tasks')(grunt); 14 | 15 | // Time how long tasks take. Can help when optimizing build times 16 | require('time-grunt')(grunt); 17 | 18 | // Configurable paths for the application 19 | var appConfig = { 20 | app: '.', 21 | dist: './dist' 22 | }; 23 | 24 | // Define the configuration for all the tasks 25 | grunt.initConfig({ 26 | 27 | // Project settings 28 | yeoman: appConfig, 29 | 30 | // Watches files for changes and runs tasks based on the changed files 31 | watch: { 32 | bower: { 33 | files: ['bower.json'], 34 | tasks: ['wiredep'] 35 | }, 36 | js: { 37 | files: ['<%= yeoman.app %>/{,*/}*.js'], 38 | tasks: ['newer:jshint:all'], 39 | options: { 40 | livereload: '<%= connect.options.livereload %>' 41 | } 42 | }, 43 | jsTest: { 44 | files: ['test/spec/{,*/}*.js'], 45 | tasks: ['newer:jshint:test', 'karma'] 46 | }, 47 | styles: { 48 | files: ['<%= yeoman.app %>/modules/{,*/}*.css'], 49 | tasks: ['newer:copy:styles', 'autoprefixer'] 50 | }, 51 | gruntfile: { 52 | files: ['Gruntfile.js'] 53 | }, 54 | livereload: { 55 | options: { 56 | livereload: '<%= connect.options.livereload %>' 57 | }, 58 | files: [ 59 | '<%= yeoman.app %>/{,*/}*.html', 60 | '.tmp/styles/{,*/}*.css', 61 | '<%= yeoman.app %>/modules/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 62 | ] 63 | } 64 | }, 65 | 66 | // The actual grunt server settings 67 | connect: { 68 | options: { 69 | port: 3000, 70 | // Change this to '0.0.0.0' to access the server from outside. 71 | hostname: '0.0.0.0', 72 | livereload: 35729 73 | }, 74 | livereload: { 75 | options: { 76 | open: true, 77 | middleware: function (connect) { 78 | return [ 79 | connect.static('.tmp'), 80 | connect.static(appConfig.app) 81 | ]; 82 | } 83 | } 84 | }, 85 | test: { 86 | options: { 87 | port: 9001, 88 | middleware: function (connect) { 89 | return [ 90 | connect.static('.tmp'), 91 | connect.static(appConfig.app) 92 | ]; 93 | } 94 | } 95 | }, 96 | dist: { 97 | options: { 98 | open: true, 99 | base: '<%= yeoman.dist %>' 100 | } 101 | } 102 | }, 103 | 104 | // Make sure code styles are up to par and there are no obvious mistakes 105 | jshint: { 106 | options: { 107 | jshintrc: '.jshintrc', 108 | reporter: require('jshint-stylish') 109 | }, 110 | all: { 111 | src: [ 112 | 'Gruntfile.js', 113 | '<%= yeoman.app %>/modules/**/*.js', 114 | 'application.js', 115 | 'config.js', 116 | ] 117 | }, 118 | test: { 119 | options: { 120 | jshintrc: 'test/.jshintrc' 121 | }, 122 | src: ['test/spec/{,*/}*.js'] 123 | } 124 | }, 125 | 126 | csslint: { 127 | options: { 128 | csslintrc: '.csslintrc', 129 | }, 130 | all : { 131 | src:'<%= applicationCSSFiles %>' 132 | } 133 | 134 | }, 135 | 136 | // Empties folders to start fresh 137 | clean: { 138 | dist: { 139 | files: [{ 140 | dot: true, 141 | src: [ 142 | '.tmp', 143 | '<%= yeoman.dist %>/{,*/}*', 144 | '!<%= yeoman.dist %>/.git{,*/}*' 145 | ] 146 | }] 147 | }, 148 | server: '.tmp' 149 | }, 150 | 151 | // Add vendor prefixed styles 152 | autoprefixer: { 153 | options: { 154 | browsers: ['last 1 version'] 155 | }, 156 | server: { 157 | options: { 158 | map: true, 159 | }, 160 | files: [{ 161 | expand: true, 162 | cwd: '.tmp/styles/', 163 | src: '{,*/}*.css', 164 | dest: '.tmp/styles/' 165 | }] 166 | }, 167 | dist: { 168 | files: [{ 169 | expand: true, 170 | cwd: '.tmp/styles/', 171 | src: '{,*/}*.css', 172 | dest: '.tmp/styles/' 173 | }] 174 | } 175 | }, 176 | 177 | // Automatically inject Bower components into the app 178 | wiredep: { 179 | app: { 180 | src: ['<%= yeoman.app %>/index.html'], 181 | ignorePath: /\.\.\// 182 | }, 183 | test: { 184 | devDependencies: true, 185 | src: '<%= karma.unit.configFile %>', 186 | ignorePath: /\.\.\//, 187 | fileTypes:{ 188 | js: { 189 | block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi, 190 | detect: { 191 | js: /'(.*\.js)'/gi 192 | }, 193 | replace: { 194 | js: '\'{{filePath}}\',' 195 | } 196 | } 197 | } 198 | } 199 | }, 200 | 201 | // Renames files for browser caching purposes 202 | filerev: { 203 | dist: { 204 | src: [ 205 | '<%= yeoman.dist %>/{,*/}*.js', 206 | '<%= yeoman.dist %>/modules/{,*/}*.css', 207 | '<%= yeoman.dist %>/modules/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 208 | '<%= yeoman.dist %>/styles/fonts/*' 209 | ] 210 | } 211 | }, 212 | 213 | env: { 214 | dev: { 215 | NODE_ENV: 'development' 216 | }, 217 | prod: { 218 | NODE_ENV: 'production' 219 | } 220 | }, 221 | 222 | 223 | cssmin: { 224 | dist: { 225 | files: { 226 | '<%= yeoman.dist %>/application.min.css': '<%= applicationCSSFiles %>' 227 | } 228 | } 229 | }, 230 | uglify: { 231 | dist: { 232 | options: { 233 | mangle: false 234 | }, 235 | files: { 236 | '<%= yeoman.dist %>/application.min.js': 'dist/application.js' 237 | } 238 | } 239 | }, 240 | 241 | htmlmin: { 242 | dist: { 243 | options: { 244 | collapseWhitespace: true, 245 | conservativeCollapse: true, 246 | collapseBooleanAttributes: true, 247 | removeCommentsFromCDATA: true, 248 | removeOptionalTags: true 249 | }, 250 | files: [{ 251 | expand: true, 252 | cwd: '<%= yeoman.app %>', 253 | src: ['*.html', '<%= yeoman.app %>/modules/*/views/{,*/}*.html'], 254 | dest: '<%= yeoman.dist %>' 255 | }] 256 | } 257 | }, 258 | 259 | // ng-annotate tries to make the code safe for minification automatically 260 | // by using the Angular long form for dependency injection. 261 | ngAnnotate: { 262 | dist: { 263 | files: { 264 | '<%= yeoman.dist %>/application.js': '<%= applicationJavaScriptFiles %>' 265 | } 266 | } 267 | }, 268 | 269 | // Copies remaining files to places other tasks can use 270 | copy: { 271 | dist: { 272 | files: [{ 273 | expand: true, 274 | dot: true, 275 | cwd: '<%= yeoman.app %>', 276 | dest: '<%= yeoman.dist %>', 277 | src: [ 278 | 'modules/*/img/{,*/}*.{png,jpg,jpeg,gif,webp,svg,ico}' 279 | ] 280 | }, 281 | { 282 | expand: true, 283 | dot: true, 284 | cwd: '<%= yeoman.app %>', 285 | dest: '<%= yeoman.dist %>/fonts/', 286 | flatten: true, 287 | src: [ 288 | '<%= yeoman.app %>/lib/*/fonts/{,*/}*.*' 289 | ] 290 | }] 291 | }, 292 | styles: { 293 | expand: true, 294 | cwd: '<%= yeoman.app %>/styles', 295 | dest: '.tmp/styles/', 296 | src: '{,*/}*.css' 297 | } 298 | }, 299 | 300 | // Run some tasks in parallel to speed up the build process 301 | concurrent: { 302 | server: [ 303 | 'copy:styles' 304 | ], 305 | test: [ 306 | 'copy:styles' 307 | ], 308 | dist: [ 309 | 'copy:styles', 310 | 'imagemin', 311 | 'svgmin' 312 | ] 313 | }, 314 | 315 | // Test settings 316 | karma: { 317 | unit: { 318 | configFile: 'karma.conf.js', 319 | singleRun: true 320 | } 321 | }, 322 | 323 | htmlrefs: { 324 | dist: { 325 | /** @required - string including grunt glob variables */ 326 | src: '<%= yeoman.app %>/index.html', 327 | /** @optional - string directory name*/ 328 | dest: '<%= yeoman.dist %>/index.html', 329 | } 330 | } 331 | }); 332 | 333 | 334 | grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() { 335 | var init = require('./config/init')(); 336 | var config = require('./config/config'); 337 | 338 | grunt.config.set('applicationJavaScriptFiles', config.assets.lib.js.concat(config.assets.js)); 339 | grunt.config.set('applicationCSSFiles', config.assets.lib.css.concat(config.assets.css)); 340 | grunt.log.warn(config.assets.lib.css.concat(config.assets.css)); 341 | }); 342 | 343 | grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { 344 | if (target === 'dist') { 345 | return grunt.task.run(['connect:dist:keepalive']); 346 | } 347 | 348 | grunt.task.run([ 349 | 'clean:server', 350 | 'wiredep', 351 | 'concurrent:server', 352 | 'autoprefixer:server', 353 | 'connect:livereload', 354 | 'watch' 355 | ]); 356 | }); 357 | 358 | grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) { 359 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 360 | grunt.task.run(['serve:' + target]); 361 | }); 362 | 363 | grunt.registerTask('test', [ 364 | 'env:dev', 365 | 'clean:server', 366 | 'connect:test', 367 | 'karma:unit' 368 | ]); 369 | 370 | grunt.registerTask('build', ['clean:dist','env:dev', 'jshint','wiredep','loadConfig', 'ngAnnotate', 'uglify', 'cssmin', 'htmlmin', 'copy:dist','htmlrefs']); 371 | 372 | grunt.registerTask('default', [ 373 | 'build', 374 | ]); 375 | }; 376 | -------------------------------------------------------------------------------- /humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | HTML5, CSS3 15 | jQuery, Modernizr 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JUNGLE 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 |
49 | 50 | 51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var applicationConfiguration = require('./config/config'); 7 | 8 | // Karma configuration 9 | module.exports = function(config) { 10 | config.set({ 11 | // Frameworks to use 12 | frameworks: ['jasmine'], 13 | 14 | // List of files / patterns to load in the browser 15 | files: applicationConfiguration.assets.lib.js.concat(applicationConfiguration.assets.js, applicationConfiguration.assets.tests), 16 | 17 | // Test results reporter to use 18 | // Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 19 | //reporters: ['progress'], 20 | reporters: ['progress'], 21 | 22 | // Web server port 23 | port: 8080, 24 | 25 | // Enable / disable colors in the output (reporters and logs) 26 | colors: true, 27 | 28 | // Level of logging 29 | // Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 30 | logLevel: config.LOG_WARN, 31 | 32 | // Enable / disable watching file and executing tests whenever any file changes 33 | autoWatch: true, 34 | 35 | // Start these browsers, currently available: 36 | // - Chrome 37 | // - ChromeCanary 38 | // - Firefox 39 | // - Opera 40 | // - Safari (only Mac) 41 | // - PhantomJS 42 | // - IE (only Windows) 43 | browsers: ['PhantomJS'], 44 | 45 | plugins: [ 46 | "karma-phantomjs-launcher", 47 | "karma-jasmine" 48 | ], 49 | 50 | // If browser does not capture in given timeout [ms], kill it 51 | captureTimeout: 60000, 52 | 53 | // Continuous Integration mode 54 | // If true, it capture browsers, run tests and exit 55 | singleRun: true 56 | }); 57 | }; -------------------------------------------------------------------------------- /modules/apis/apis.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use applicaion configuration module to register a new module 4 | ApplicationConfiguration.registerModule('apis'); -------------------------------------------------------------------------------- /modules/apis/config/apis.client.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Configuring the Articles module 4 | angular.module('apis').run(['Menus', 'editableOptions', 5 | function(Menus, editableOptions) { 6 | // Set top bar menu items 7 | Menus.addMenuItem('topbar', 'Apis', 'apis', 'dropdown', '/apis(/create)?'); 8 | Menus.addSubMenuItem('topbar', 'apis', 'List Apis', 'apis'); 9 | Menus.addSubMenuItem('topbar', 'apis', 'New Api', 'apis/create'); 10 | editableOptions.theme = 'bs3'; 11 | } 12 | ]); -------------------------------------------------------------------------------- /modules/apis/config/apis.client.constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Setting up route 4 | angular.module('apis').constant('PLUGINSAVAILABLE', [ 5 | 6 | { 7 | name: 'http-log', 8 | label: 'Http Log', 9 | docUrl: 'http://getkong.org/plugins/http-log/', 10 | schema: [ 11 | { 12 | 'name':'config.http_endpoint', 13 | 'type' : 'string', 14 | 'label': 'Http endpoint' 15 | }, 16 | { 17 | 'name':'config.timeout', 18 | 'type' : 'integer', 19 | 'label': 'Timeout' 20 | }, 21 | { 22 | 'name':'config.keepalive', 23 | 'type' : 'integer', 24 | 'label': 'Keepalive' 25 | }, 26 | { 27 | 'name':'config.method', 28 | 'type' : 'enum', 29 | 'label': 'Method', 30 | 'values': [ 31 | { 'label' : 'POST', 'value' : 'POST'}, 32 | { 'label' : 'PATCH', 'value' : 'PATCH'}, 33 | { 'label' : 'PUT', 'value' : 'PUT'} 34 | ] 35 | } 36 | ] 37 | }, 38 | { 39 | name: 'udp-log', 40 | label: 'UDP Log', 41 | docUrl: 'http://getkong.org/plugins/udp-log/', 42 | schema: [ 43 | { 44 | 'name':'config.host', 45 | 'type' : 'string', 46 | 'label': 'Host' 47 | }, 48 | { 49 | 'name':'config.timeout', 50 | 'type' : 'integer', 51 | 'label': 'Timeout' 52 | }, 53 | { 54 | 'name':'config.port', 55 | 'type' : 'integer', 56 | 'label': 'Port' 57 | } 58 | ] 59 | }, 60 | { 61 | name: 'tcp-log', 62 | label: 'TCP Log', 63 | docUrl: 'http://getkong.org/plugins/tcp-log/', 64 | schema: [ 65 | { 66 | 'name':'config.host', 67 | 'type' : 'string', 68 | 'label': 'Host' 69 | }, 70 | { 71 | 'name':'config.timeout', 72 | 'type' : 'integer', 73 | 'label': 'Timeout' 74 | }, 75 | { 76 | 'name':'config.port', 77 | 'type' : 'integer', 78 | 'label': 'Port' 79 | }, 80 | { 81 | 'name':'config.keepalive', 82 | 'type' : 'integer', 83 | 'label': 'Keepalive' 84 | } 85 | ] 86 | }, 87 | { 88 | name: 'file-log', 89 | label: 'File Log', 90 | docUrl: 'http://getkong.org/plugins/file-log/', 91 | schema: [ 92 | { 93 | 'name':'config.path', 94 | 'type' : 'string', 95 | 'label': 'The output path file' 96 | } 97 | ] 98 | }, 99 | { 100 | name: 'basic-auth', 101 | label: 'Basic Authentication', 102 | docUrl: 'http://getkong.org/plugins/basic-authentication/', 103 | schema: [ 104 | { 105 | 'name':'config.hide_credentials', 106 | 'type' : 'boolean', 107 | 'label': 'Hide Credentials' 108 | } 109 | ], 110 | api : { 111 | routes : [ 112 | { 113 | 'action': 'list', 114 | 'route': 'consumers/:username/basic-auth', 115 | 'method': 'GET', 116 | 'params': ['username'] 117 | }, 118 | { 119 | 'action': 'create', 120 | 'route': 'consumers/:username/basic-auth', 121 | 'method': 'POST', 122 | 'params': ['username'] 123 | }, 124 | { 125 | 'action': 'view', 126 | 'route': 'consumers/:username/basic-auth/:id', 127 | 'method': 'GET', 128 | 'params': ['username', 'id'] 129 | }, 130 | { 131 | 'action': 'update', 132 | 'route': 'consumers/:username/basic-auth/:id', 133 | 'method': 'PATCH', 134 | 'params': ['username', 'id'] 135 | }, 136 | { 137 | 'action': 'delete', 138 | 'route': 'consumers/:username/basic-auth/:id', 139 | 'method': 'DELETE', 140 | 'params': ['username', 'id'] 141 | } 142 | ], 143 | dao : [ 144 | { 145 | 'name':'username', 146 | 'type' : 'string', 147 | 'label': 'Username' 148 | }, 149 | { 150 | 'name':'password', 151 | 'type' : 'string', 152 | 'label': 'Password' 153 | }, 154 | ] 155 | } 156 | 157 | }, 158 | { 159 | name: 'key-auth', 160 | label: 'Key Authentication', 161 | docUrl: 'http://getkong.org/plugins/key-authentication/', 162 | schema: [ 163 | { 164 | 'name':'config.hide_credentials', 165 | 'type' : 'boolean', 166 | 'label': 'Hide Credentials' 167 | }, 168 | { 169 | 'name':'config.key_names', 170 | 'type' : 'string', 171 | 'label': 'Key Names' 172 | } 173 | ], 174 | api : 175 | { 176 | routes : [ 177 | { 178 | 'action': 'list', 179 | 'route': 'consumers/:username/key-auth', 180 | 'method': 'GET', 181 | 'params': ['username'] 182 | }, 183 | { 184 | 'action': 'create', 185 | 'route': 'consumers/:username/key-auth', 186 | 'method': 'POST', 187 | 'params': ['username'] 188 | }, 189 | { 190 | 'action': 'view', 191 | 'route': 'consumers/:username/key-auth/:id', 192 | 'method': 'GET', 193 | 'params': ['username', 'id'] 194 | }, 195 | { 196 | 'action': 'update', 197 | 'route': 'consumers/:username/key-auth/:id', 198 | 'method': 'PATCH', 199 | 'params': ['username', 'id'] 200 | }, 201 | { 202 | 'action': 'delete', 203 | 'route': 'consumers/:username/key-auth/:id', 204 | 'method': 'DELETE', 205 | 'params': ['username', 'id'] 206 | } 207 | ], 208 | dao : [ 209 | { 210 | 'name':'key', 211 | 'type' : 'string', 212 | 'label': 'Key' 213 | } 214 | ] 215 | } 216 | 217 | }, 218 | { 219 | name: 'cors', 220 | label: 'CORS', 221 | docUrl: 'http://getkong.org/plugins/cors/', 222 | schema: [ 223 | { 224 | 'name':'config.origin', 225 | 'type' : 'string', 226 | 'label': 'Origin' 227 | }, 228 | { 229 | 'name':'config.methods', 230 | 'type' : 'string', 231 | 'label': 'Method' 232 | }, 233 | { 234 | 'name':'config.headers', 235 | 'type' : 'string', 236 | 'label': 'Headers' 237 | }, 238 | { 239 | 'name':'config.exposed_headers', 240 | 'type' : 'string', 241 | 'label': 'Exposed Headers' 242 | }, 243 | { 244 | 'name':'config.credentials', 245 | 'type' : 'boolean', 246 | 'label': 'Credentials' 247 | }, 248 | { 249 | 'name':'config.max_age', 250 | 'type' : 'integer', 251 | 'label': 'Max age' 252 | } 253 | ] 254 | }, 255 | { 256 | name: 'ssl', 257 | label: 'SSL', 258 | docUrl: 'http://getkong.org/plugins/ssl/', 259 | schema: [ 260 | { 261 | 'name':'config.cert', 262 | 'type' : 'string', 263 | 'label': 'Certificate file path' 264 | }, 265 | { 266 | 'name':'config.key', 267 | 'type' : 'string', 268 | 'label': 'Certificate key path' 269 | }, 270 | { 271 | 'name':'config.only_https', 272 | 'type' : 'boolean', 273 | 'label': 'Only HTTPS' 274 | } 275 | ] 276 | }, 277 | { 278 | name: 'request-transformer', 279 | label: 'Request Transformer', 280 | docUrl: 'http://getkong.org/plugins/request-transformer/', 281 | schema: [ 282 | { 283 | 'name':'config.add.headers', 284 | 'type' : 'string', 285 | 'label': 'Headers to add' 286 | }, 287 | { 288 | 'name':'config.add.querystring', 289 | 'type' : 'string', 290 | 'label': 'Parameters to add in request querystring' 291 | }, 292 | { 293 | 'name':'config.add.form', 294 | 'type' : 'string', 295 | 'label': 'Values to add in request body' 296 | }, 297 | { 298 | 'name':'config.remove.headers', 299 | 'type' : 'string', 300 | 'label': 'Headers to remove' 301 | }, 302 | { 303 | 'name':'config.remove.querystring', 304 | 'type' : 'string', 305 | 'label': 'Parameters to remove in request querystring' 306 | }, 307 | { 308 | 'name':'config.remove.form', 309 | 'type' : 'string', 310 | 'label': 'Values to remove in request body' 311 | } 312 | ] 313 | }, 314 | { 315 | name: 'response-transformer', 316 | label: 'Response Transformer', 317 | docUrl: 'http://getkong.org/plugins/response-transformer/', 318 | schema: [ 319 | { 320 | 'name':'config.add.headers', 321 | 'type' : 'string', 322 | 'label': 'Headers to add' 323 | }, 324 | { 325 | 'name':'config.add.json', 326 | 'type' : 'string', 327 | 'label': 'Values to add to a JSON response body' 328 | }, 329 | { 330 | 'name':'config.remove.headers', 331 | 'type' : 'string', 332 | 'label': 'Headers to remove' 333 | }, 334 | { 335 | 'name':'config.remove.json', 336 | 'type' : 'string', 337 | 'label': 'Values to remove to a JSON response body' 338 | } 339 | ] 340 | }, 341 | { 342 | name: 'ratelimiting', 343 | label: 'Rate Limiting', 344 | docUrl: 'http://getkong.org/plugins/rate-limiting/', 345 | schema: [ 346 | { 347 | 'name':'limit', 348 | 'type' : 'integer', 349 | 'label': 'Limit' 350 | }, 351 | { 352 | 'name':'period', 353 | 'type' : 'enum', 354 | 'label': 'Period', 355 | 'values': [ 356 | { 'label' : 'Second', 'value' : 'second'}, 357 | { 'label' : 'Minute', 'value' : 'minute'}, 358 | { 'label' : 'Hour', 'value' : 'hour'}, 359 | { 'label' : 'Day', 'value' : 'day'}, 360 | { 'label' : 'Month', 'value' : 'month'}, 361 | { 'label' : 'Year', 'value' : 'year'} 362 | ] 363 | } 364 | ] 365 | }, 366 | { 367 | name: 'request-size-limiting', 368 | label: 'Request Size Limiting', 369 | docUrl: 'http://getkong.org/plugins/request-size-limiting/', 370 | schema: [ 371 | { 372 | 'name':'config.allowed_payload_size', 373 | 'type' : 'integer', 374 | 'label': 'Allowed request payload size in megabytes' 375 | } 376 | ] 377 | } 378 | ]); -------------------------------------------------------------------------------- /modules/apis/config/apis.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Setting up route 4 | angular.module('apis').config(['$stateProvider', 5 | function($stateProvider) { 6 | // Apis state routing 7 | $stateProvider. 8 | state('listApis', { 9 | url: '/apis', 10 | templateUrl: 'modules/apis/views/list-apis.client.view.html', 11 | ncyBreadcrumb: { 12 | label: 'List Apis', 13 | parent: 'home' 14 | }, 15 | reloadOnSearch: true 16 | }). 17 | state('createApi', { 18 | url: '/apis/create', 19 | templateUrl: 'modules/apis/views/create-api.client.view.html', 20 | ncyBreadcrumb: { 21 | label: 'Create API', 22 | parent: 'home' 23 | }, 24 | reloadOnSearch: true 25 | }). 26 | state('viewApi', { 27 | url: '/apis/:apiId', 28 | templateUrl: 'modules/apis/views/view-api.client.view.html', 29 | ncyBreadcrumb: { 30 | label: 'View API', 31 | parent: 'listApis' 32 | }, 33 | reloadOnSearch: true 34 | }). 35 | state('editApi', { 36 | url: '/apis/:apiId/edit', 37 | templateUrl: 'modules/apis/views/edit-api.client.view.html', 38 | ncyBreadcrumb: { 39 | label: 'Edit API', 40 | parent: 'viewApi' 41 | }, 42 | reloadOnSearch: true 43 | }). 44 | state('viewPluginApi', { 45 | url: '/apis/:apiId/plugins', 46 | templateUrl: 'modules/apis/views/view-api-plugin.client.view.html', 47 | ncyBreadcrumb: { 48 | label: 'List Plugins for API', 49 | parent: 'viewApi' 50 | }, 51 | reloadOnSearch: true 52 | }); 53 | } 54 | ]); -------------------------------------------------------------------------------- /modules/apis/controllers/apis.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Apis controller 4 | angular.module('apis').controller('ApisController', ['$scope', '$stateParams', '$location', '$state', 'Apis', 'Plugins', 'PLUGINSAVAILABLE', 5 | function($scope, $stateParams, $location, $state, Apis, Plugins, PLUGINSAVAILABLE) { 6 | 7 | // Create new Api 8 | $scope.create = function() { 9 | // Create new Api object 10 | var api = new Apis ({ 11 | name: this.name, 12 | request_host: this.request_host, 13 | request_path: this.request_path, 14 | strip_request_path: this.strip_request_path, 15 | preserve_host: this.preserve_host, 16 | upstream_url : this.upstream_url 17 | }); 18 | 19 | // Redirect after save 20 | api.$save(function(response) { 21 | $location.path('apis/' + response.id); 22 | 23 | // Clear form fields 24 | $scope.name = ''; 25 | $scope.request_host = ''; 26 | $scope.request_path = ''; 27 | $scope.strip_request_path = ''; 28 | $scope.preserve_host = ''; 29 | $scope.upstream_url = ''; 30 | }, function(errorResponse) { 31 | $scope.error = errorResponse.data; 32 | }); 33 | }; 34 | 35 | // Remove existing Api 36 | $scope.remove = function(api) { 37 | if ( api ) { 38 | api.$remove(); 39 | 40 | for (var i in $scope.apis) { 41 | if ($scope.apis [i] === api) { 42 | $scope.apis.splice(i, 1); 43 | } 44 | } 45 | } else { 46 | 47 | $scope.api.$remove(function() { 48 | $location.path('apis'); 49 | }, function(errorResponse) { 50 | $location.path('apis'); 51 | }); 52 | } 53 | }; 54 | 55 | // Update existing Api 56 | $scope.update = function() { 57 | var api = $scope.api; 58 | api.$update(function() { 59 | $location.path('apis/' + api.id); 60 | }, function(errorResponse) { 61 | $scope.error = errorResponse.data; 62 | }); 63 | }; 64 | 65 | // Find a list of Apis 66 | $scope.find = function() { 67 | $scope.apis = Apis.query($location.search()); 68 | $scope.scroll= {busy : false}; 69 | }; 70 | 71 | // Find existing Api 72 | $scope.findOne = function() { 73 | $scope.api = Apis.get({ 74 | apiId: $stateParams.apiId 75 | }); 76 | }; 77 | 78 | $scope.listPluginByApi = function() { 79 | $scope.pluginAvailable = PLUGINSAVAILABLE; 80 | $scope.findOne(); 81 | $scope.plugins = Plugins.query({ 82 | apiId: $stateParams.apiId 83 | }); 84 | }; 85 | 86 | 87 | $scope.initPluginForm = function() { 88 | $scope.pluginAvailable = PLUGINSAVAILABLE; 89 | $scope.currentPlugin = null; 90 | $scope.value = {}; 91 | }; 92 | 93 | $scope.removePlugin = function(plugin) { 94 | if ( plugin ) { 95 | var pluginResource = new Plugins(plugin); 96 | pluginResource.$remove(); 97 | 98 | for (var i in $scope.plugins.data) { 99 | if ($scope.plugins.data [i] === plugin) { 100 | $scope.plugins.data.splice(i, 1); 101 | } 102 | } 103 | } 104 | }; 105 | 106 | $scope.createPlugin = function() { 107 | if ($scope.currentPlugin !== null) { 108 | var opt = $scope.value; 109 | opt.name = $scope.currentPlugin.name; 110 | opt.api_id = $stateParams.apiId; 111 | var plugin = new Plugins(opt); 112 | plugin.$save(function(response) { 113 | $scope.initPluginForm(); 114 | $state.go($state.current, {}, {reload: true}); 115 | }, function(errorResponse) { 116 | $scope.error = errorResponse.data; 117 | }); 118 | } 119 | }; 120 | 121 | $scope.saveApi = function(data, id) { 122 | var api = new Apis (data); 123 | api.id = id; 124 | api.$update(function() { 125 | for (var i in $scope.apis.data) { 126 | if ($scope.apis.data [i] . id === id) { 127 | angular.extend($scope.apis.data [i], data); 128 | } 129 | } 130 | }, function(errorResponse) { 131 | $scope.error = errorResponse.data; 132 | }); 133 | }; 134 | 135 | $scope.nextPage = function() { 136 | if ($scope.scroll.busy){ 137 | $scope.scroll.busy = false; 138 | return; 139 | } 140 | 141 | $scope.scroll.busy = true; 142 | 143 | 144 | if (undefined === $scope.apis.next){ 145 | $scope.scroll.busy = false; 146 | return; 147 | } 148 | 149 | var offset = new URI($scope.apis.next).search(true).offset; 150 | Apis.query({offset: offset}, function(apis){ 151 | $scope.apis.next = apis.next; 152 | $scope.apis.data = $scope.apis.data.concat(apis.data); 153 | $scope.scroll.busy = false; 154 | }); 155 | }; 156 | 157 | // Set sorting default configuration 158 | $scope.sortType = 'name'; 159 | $scope.sortReverse = false; 160 | } 161 | ]); 162 | -------------------------------------------------------------------------------- /modules/apis/directives/pluginform.client.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('apis').directive('pluginForm', [ 4 | function() { 5 | return { 6 | templateUrl: 'modules/apis/views/plugin.form.directive.html' 7 | }; 8 | } 9 | ]); -------------------------------------------------------------------------------- /modules/apis/services/apis.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Apis service used to communicate Apis REST endpoints 4 | angular.module('apis').factory('Apis', ['$resource', '$localStorage', 5 | function($resource, $localStorage) { 6 | return $resource($localStorage.kongurl+'/apis/:apiId', { apiId: '@id' 7 | }, { 8 | query: { 9 | method: 'GET', 10 | isArray: false 11 | }, 12 | update: { 13 | method: 'PATCH' 14 | } 15 | }); 16 | } 17 | ]); 18 | 19 | //Plugin service used to communicate Apis REST endpoints 20 | angular.module('apis').factory('Plugins', ['$resource', '$localStorage', 21 | function($resource, $localStorage) { 22 | return $resource($localStorage.kongurl+'/apis/:apiId/plugins/:pluginId', { apiId: '@api_id', pluginId: '@id' 23 | }, { 24 | query: { 25 | method: 'GET', 26 | isArray: false 27 | }, 28 | update: { 29 | method: 'PATCH' 30 | } 31 | }); 32 | } 33 | ]); 34 | 35 | angular.module('apis').factory('PluginsConfigurations', ['$resource', 'KONGURL', 36 | function($resource, KONGURL) { 37 | return $resource(KONGURL+'/plugins_configurations', { pluginId: '@id' 38 | }, { 39 | query: { 40 | method: 'GET', 41 | isArray: false 42 | } 43 | }); 44 | } 45 | ]); -------------------------------------------------------------------------------- /modules/apis/tests/apis.client.controller.test.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | // Apis Controller Spec 4 | describe('Apis Controller Tests', function() { 5 | // Initialize global variables 6 | var ApisController, 7 | scope, 8 | $httpBackend, 9 | $stateParams, 10 | $location, 11 | $KONGURL, 12 | $localStorage; 13 | 14 | // The $resource service augments the response object with methods for updating and deleting the resource. 15 | // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match 16 | // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. 17 | // When the toEqualData matcher compares two objects, it takes only object properties into 18 | // account and ignores methods. 19 | beforeEach(function() { 20 | jasmine.addMatchers({ 21 | toEqualData: function(util, customEqualityTesters) { 22 | return { 23 | compare: function(actual, expected) { 24 | return { 25 | pass: angular.equals(actual, expected) 26 | }; 27 | } 28 | }; 29 | } 30 | }); 31 | }); 32 | 33 | // Then we can start by loading the main application module 34 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 35 | 36 | // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). 37 | // This allows us to inject a service but then attach it to a variable 38 | // with the same name as the service. 39 | beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_, _KONGURL_, _$localStorage_) { 40 | // Set a new global scope 41 | scope = $rootScope.$new(); 42 | 43 | // Point global variables to injected services 44 | $stateParams = _$stateParams_; 45 | $httpBackend = _$httpBackend_; 46 | $location = _$location_; 47 | $KONGURL = _KONGURL_; 48 | $localStorage = _$localStorage_; 49 | $localStorage.kongurl = _KONGURL_; 50 | $httpBackend.when('GET', 'modules/core/views/home.client.view.html').respond(200, {}); 51 | // Initialize the Apis controller. 52 | ApisController = $controller('ApisController', { 53 | $scope: scope 54 | }); 55 | })); 56 | 57 | it('$scope.find() should create an array with at least one Api object fetched from XHR', inject(function(Apis) { 58 | // Create sample Api using the Apis service 59 | var sampleApi = new Apis({ 60 | name: 'New Api', 61 | request_host: 'api', 62 | path: '/path', 63 | strip_request_path: true, 64 | preserve_host: true, 65 | upstream_url: 'http://api.com' 66 | }); 67 | 68 | // Create a sample Apis array that includes the new Api 69 | var sampleApis = { data: [sampleApi] }; 70 | 71 | // Set GET response 72 | $httpBackend.expectGET($KONGURL+'/apis').respond(sampleApis); 73 | 74 | // Run controller functionality 75 | scope.find(); 76 | $httpBackend.flush(); 77 | 78 | // Test scope value 79 | expect(scope.apis).toEqualData(sampleApis); 80 | })); 81 | 82 | it('$scope.findOne() should create an array with one Api object fetched from XHR using a apiId URL parameter', inject(function(Apis) { 83 | // Define a sample Api object 84 | var sampleApi = new Apis({ 85 | name: 'New Api', 86 | request_host: 'api', 87 | request_path: '/path', 88 | strip_request_path: true, 89 | preserve_host: true, 90 | upstream_url: 'http://api.com' 91 | }); 92 | 93 | // Set the URL parameter 94 | $stateParams.apiId = '525a8422f6d0f87f0e407a33'; 95 | 96 | // Set GET response 97 | $httpBackend.expectGET(/apis\/([0-9a-fA-F]{24})$/).respond(sampleApi); 98 | 99 | // Run controller functionality 100 | scope.findOne(); 101 | $httpBackend.flush(); 102 | 103 | // Test scope value 104 | expect(scope.api).toEqualData(sampleApi); 105 | })); 106 | 107 | it('$scope.create() with valid form data should send a POST request with the form input values and then locate to new object URL', inject(function(Apis) { 108 | // Create a sample Api object 109 | var sampleApiPostData = new Apis({ 110 | name: 'New Api', 111 | request_host: 'api', 112 | request_path: '/path', 113 | strip_request_path: true, 114 | preserve_host: true, 115 | upstream_url: 'http://api.com' 116 | }); 117 | 118 | // Create a sample Api response 119 | var sampleApiResponse = new Apis({ 120 | id: '525cf20451979dea2c000001', 121 | name: 'New Api', 122 | request_host: 'api', 123 | request_path: '/path', 124 | strip_request_path: true, 125 | preserve_host: true, 126 | upstream_url: 'http://api.com' 127 | }); 128 | 129 | // Fixture mock form input values 130 | scope.name = 'New Api'; 131 | scope.request_host = 'api'; 132 | scope.request_path = '/path'; 133 | scope.strip_request_path = true; 134 | scope.preserve_host = true; 135 | scope.upstream_url = 'http://api.com'; 136 | 137 | // Set POST response 138 | $httpBackend.expectPOST($KONGURL+'/apis', sampleApiPostData).respond(sampleApiResponse); 139 | $httpBackend.when('GET', 'modules/apis/views/view-api.client.view.html').respond(200, {}); 140 | // Run controller functionality 141 | scope.create(); 142 | $httpBackend.flush(); 143 | 144 | // Test form inputs are reset 145 | expect(scope.name).toEqual(''); 146 | 147 | // Test URL redirection after the Api was created 148 | expect($location.path()).toBe('/apis/' + sampleApiResponse.id); 149 | })); 150 | 151 | it('$scope.update() should update a valid Api', inject(function(Apis) { 152 | // Define a sample Api put data 153 | var sampleApiPutData = new Apis({ 154 | id: '525cf20451979dea2c000001', 155 | name: 'New Api', 156 | request_host: 'api', 157 | request_path: '/path', 158 | strip_request_path: true, 159 | upstream_url: 'http://api.com' 160 | }); 161 | 162 | // Mock Api in scope 163 | scope.api = sampleApiPutData; 164 | 165 | // Set PUT response 166 | $httpBackend.expectPATCH(/apis\/([0-9a-fA-F]{24})$/).respond(); 167 | $httpBackend.when('GET', 'modules/apis/views/view-api.client.view.html').respond(200, {}); 168 | 169 | // Run controller functionality 170 | scope.update(); 171 | $httpBackend.flush(); 172 | 173 | // Test URL location to new object 174 | expect($location.path()).toBe('/apis/' + sampleApiPutData.id); 175 | })); 176 | 177 | it('$scope.remove() should send a DELETE request with a valid apiId and remove the Api from the scope', inject(function(Apis) { 178 | // Create new Api object 179 | var sampleApi = new Apis({ 180 | id: '525a8422f6d0f87f0e407a33' 181 | }); 182 | 183 | // Create new Apis array and include the Api 184 | scope.apis = [sampleApi]; 185 | 186 | // Set expected DELETE response 187 | $httpBackend.expectDELETE(/apis\/([0-9a-fA-F]{24})$/).respond(204); 188 | 189 | // Run controller functionality 190 | scope.remove(sampleApi); 191 | $httpBackend.flush(); 192 | 193 | // Test array after successful delete 194 | expect(scope.apis.length).toBe(0); 195 | })); 196 | }); 197 | }()); 198 | -------------------------------------------------------------------------------- /modules/apis/views/create-api.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 |
8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 | 16 |
17 | 18 |
19 |
20 |
21 | 22 |
23 | 24 |
25 |
26 |
27 | 28 |
29 | 30 |
31 |
32 |
33 |
34 | 38 |
39 |
40 |
41 |
42 | 46 |
47 |
48 |
49 | 50 |
51 |
52 | 53 |
54 |
55 |
56 |
57 |
-------------------------------------------------------------------------------- /modules/apis/views/edit-api.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 |
8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 | 16 |
17 | 18 |
19 |
20 |
21 | 22 |
23 | 24 |
25 |
26 |
27 | 28 |
29 | 30 |
31 |
32 |
33 |
34 | 38 |
39 |
40 |
41 |
42 | 46 |
47 |
48 |
49 | 50 |
51 |
52 | 53 |
54 |
55 |
56 |
57 |
-------------------------------------------------------------------------------- /modules/apis/views/list-apis.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 9 |
10 |
11 |
12 | 13 |
14 |
15 | 16 | 17 | 22 | 27 | 32 | 37 | 42 | 47 | 52 | 53 | 54 |
55 |
56 | 59 | 64 | 69 | 74 | 79 | 84 | 89 | 105 | 106 | 107 |
18 | ID 19 | 20 | 21 | 23 | Name 24 | 25 | 26 | 28 | Request Host 29 | 30 | 31 | 33 | Request Path 34 | 35 | 36 | 38 | Upstream Url 39 | 40 | 41 | 43 | Strip Request Path 44 | 45 | 46 | 48 | Preserve Host 49 | 50 | 51 | Action
57 | {{api.id}} 58 | 60 | 61 | {{api.name}} 62 | 63 | 65 | 66 | {{api.request_host}} 67 | 68 | 70 | 71 | {{api.request_path}} 72 | 73 | 75 | 76 | {{api.upstream_url}} 77 | 78 | 80 | 81 | {{ api.strip_request_path && "ON" || "OFF" }} 82 | 83 | 85 | 86 | {{ api.preserve_host && "ON" || "OFF" }} 87 | 88 | 90 |
91 | 94 | 97 |
98 |
99 | 100 | 101 | View API Details 102 | 103 |
104 |
108 |
109 | No Apis yet, why don't you create one? 110 |
111 |
112 | -------------------------------------------------------------------------------- /modules/apis/views/plugin.form.directive.html: -------------------------------------------------------------------------------- 1 | Check out the Documents for {{currentPlugin.name}} in {{currentPlugin.docUrl}} 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 |
28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 |
-------------------------------------------------------------------------------- /modules/apis/views/view-api-plugin.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
  • 7 |

    8 | 13 |

    14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
    AttributeValue
    Consumer ID{{plugin.consumer_id}}
    {{key}}{{value}}
    28 |

    29 |
  • 30 |
    31 |
    32 | No active plugins found in the API, why don't you add one ? 33 |
    34 |
    35 |
    36 | 39 |
    40 |
    41 |
    42 |
    43 | 44 | 47 |
    48 | 49 |
    50 |
    51 |
    52 |
    -------------------------------------------------------------------------------- /modules/apis/views/view-api.client.view.html: -------------------------------------------------------------------------------- 1 |
    2 | 5 | 16 |

    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
    AttributeValue
    request_host{{api.request_host}}
    request_path{{api.request_path}}
    upstream_url{{api.upstream_url}}
    strip_request_path{{api.strip_request_path}}
    preserve_host{{api.preserve_host}}
    43 |

    44 |
    45 | -------------------------------------------------------------------------------- /modules/consumers/config/consumers.client.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Configuring the Articles module 4 | angular.module('consumers').run(['Menus', 'editableOptions', 5 | function(Menus, editableOptions) { 6 | // Set top bar menu items 7 | Menus.addMenuItem('topbar', 'Consumers', 'consumers', 'dropdown', '/consumers(/create)?'); 8 | Menus.addSubMenuItem('topbar', 'consumers', 'List Consumers', 'consumers'); 9 | Menus.addSubMenuItem('topbar', 'consumers', 'New Consumer', 'consumers/create'); 10 | editableOptions.theme = 'bs3'; 11 | } 12 | ]); -------------------------------------------------------------------------------- /modules/consumers/config/consumers.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Setting up route 4 | angular.module('consumers').config(['$stateProvider', 5 | function($stateProvider) { 6 | // Consumers state routing 7 | $stateProvider. 8 | state('listConsumers', { 9 | url: '/consumers', 10 | templateUrl: 'modules/consumers/views/list-consumers.client.view.html', 11 | ncyBreadcrumb: { 12 | label: 'List Consumers', 13 | parent: 'home' 14 | } 15 | }). 16 | state('createConsumer', { 17 | url: '/consumers/create', 18 | templateUrl: 'modules/consumers/views/create-consumer.client.view.html', 19 | ncyBreadcrumb: { 20 | label: 'Create Consumer', 21 | parent: 'home' 22 | } 23 | }). 24 | state('viewConsumer', { 25 | url: '/consumers/:consumerId', 26 | templateUrl: 'modules/consumers/views/view-consumer.client.view.html', 27 | ncyBreadcrumb: { 28 | label: 'View Consumer', 29 | parent: 'listConsumers' 30 | } 31 | }). 32 | state('editConsumer', { 33 | url: '/consumers/:consumerId/edit', 34 | templateUrl: 'modules/consumers/views/edit-consumer.client.view.html', 35 | ncyBreadcrumb: { 36 | label: 'Edit Consumer', 37 | parent: 'viewConsumer' 38 | } 39 | }). 40 | state('viewPluginConsumer', { 41 | url: '/consumers/:consumerId/plugins', 42 | templateUrl: 'modules/consumers/views/view-api-plugin-consumer.client.view.html', 43 | ncyBreadcrumb: { 44 | label: 'List Plugins for Consumer', 45 | parent: 'viewConsumer' 46 | } 47 | }). 48 | state('crudPluginConsumer', { 49 | url: '/consumers/:consumerId/plugins/:pluginName', 50 | templateUrl: 'modules/consumers/views/crud-plugin-consumer.client.view.html', 51 | ncyBreadcrumb: { 52 | label: 'Manage Plugin API', 53 | parent: 'viewConsumer' 54 | } 55 | }); 56 | } 57 | ]); -------------------------------------------------------------------------------- /modules/consumers/consumers.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use applicaion configuration module to register a new module 4 | ApplicationConfiguration.registerModule('consumers'); -------------------------------------------------------------------------------- /modules/consumers/controllers/consumers.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Consumers controller 4 | angular.module('consumers').controller('ConsumersController', ['$scope', '$stateParams', '$location', '$state', '$filter', '$http', 'Consumers', 'PLUGINSAVAILABLE', 'PluginsConfigurations', 'Apis', 'Plugins', '$localStorage', 5 | function($scope, $stateParams, $location, $state, $filter, $http, Consumers, PLUGINSAVAILABLE, PluginsConfigurations, Apis, Plugins, $localStorage) { 6 | 7 | // Create new Consumer 8 | $scope.create = function() { 9 | // Create new Consumer object 10 | var consumer = new Consumers ({ 11 | username: this.username, 12 | custom_id: this.custom_id 13 | }); 14 | 15 | // Redirect after save 16 | consumer.$save(function(response) { 17 | $location.path('consumers/' + response.id); 18 | 19 | // Clear form fields 20 | $scope.username = ''; 21 | $scope.custom_id = ''; 22 | }, function(errorResponse) { 23 | $scope.error = errorResponse.data; 24 | }); 25 | }; 26 | 27 | // Remove existing Consumer 28 | $scope.remove = function(consumer) { 29 | if ( consumer ) { 30 | consumer.$remove(); 31 | 32 | for (var i in $scope.consumers) { 33 | if ($scope.consumers [i] === consumer) { 34 | $scope.consumers.splice(i, 1); 35 | } 36 | } 37 | } else { 38 | $scope.consumer.$remove(function() { 39 | $location.path('consumers'); 40 | }); 41 | } 42 | }; 43 | 44 | // Update existing Consumer 45 | $scope.update = function() { 46 | var consumer = $scope.consumer; 47 | 48 | consumer.$update(function() { 49 | $location.path('consumers/' + consumer.id); 50 | }, function(errorResponse) { 51 | $scope.error = errorResponse.data; 52 | }); 53 | }; 54 | 55 | // Find a list of Consumers 56 | $scope.find = function() { 57 | $scope.consumers = Consumers.query($location.search()); 58 | $scope.scroll= {busy : false}; 59 | }; 60 | 61 | // Find existing Consumer 62 | $scope.findOne = function() { 63 | $scope.pluginAvailable = PLUGINSAVAILABLE; 64 | $scope.consumer = Consumers.get({ 65 | consumerId: $stateParams.consumerId 66 | }); 67 | }; 68 | 69 | $scope.listPluginByConsumer = function() { 70 | $scope.pluginAvailable = PLUGINSAVAILABLE; 71 | $scope.findOne(); 72 | $scope.apis = Apis.query({size: 100000}); 73 | $scope.plugins = PluginsConfigurations.query({ 74 | consumer_id : $stateParams.consumerId 75 | }); 76 | }; 77 | 78 | 79 | $scope.initPluginForm = function() { 80 | $scope.pluginAvailable = PLUGINSAVAILABLE; 81 | $scope.currentPlugin = null; 82 | $scope.apis = Apis.query({size: 100000}); 83 | $scope.api_id = null; 84 | $scope.value = {}; 85 | }; 86 | 87 | $scope.removePlugin = function(plugin) { 88 | if ( plugin ) { 89 | var pluginResource = new Plugins(plugin); 90 | pluginResource.$remove(); 91 | 92 | for (var i in $scope.plugins.data) { 93 | if ($scope.plugins.data [i] === plugin) { 94 | $scope.plugins.data.splice(i, 1); 95 | } 96 | } 97 | } 98 | }; 99 | 100 | $scope.createPlugin = function() { 101 | if ($scope.currentPlugin !== null) { 102 | var plugin = new Plugins({ 103 | value: $scope.value, 104 | name: $scope.currentPlugin.name, 105 | consumer_id: $stateParams.consumerId, 106 | api_id: $scope.api_id.id 107 | }); 108 | plugin.$save(function(response) { 109 | $scope.initPluginForm(); 110 | $state.go($state.current, {}, {reload: true}); 111 | }, function(errorResponse) { 112 | $scope.error = errorResponse.data; 113 | }); 114 | } 115 | }; 116 | 117 | $scope.saveConsumer = function(data, id) { 118 | var consumer = new Consumers (data); 119 | consumer.id = id; 120 | consumer.$update(function() { 121 | for (var i in $scope.consumers.data) { 122 | if ($scope.consumers.data [i] . id === id) { 123 | angular.extend($scope.consumers.data [i], data); 124 | } 125 | } 126 | }, function(errorResponse) { 127 | $scope.error = errorResponse.data; 128 | }); 129 | }; 130 | 131 | $scope.hasApi = function(element) { 132 | return undefined !== element.api; 133 | }; 134 | 135 | $scope.crudPlugin = function() { 136 | 137 | $scope.plugin = $filter('filter')(PLUGINSAVAILABLE, 138 | function(element){ 139 | return $stateParams.pluginName === element.name; 140 | } 141 | ); 142 | 143 | $scope.plugin = $scope.plugin[0]; 144 | 145 | if ($scope.plugin.api === undefined) { 146 | $location.path('consumers/' + $stateParams.consumerId); 147 | } 148 | 149 | var routeList = $filter('filter')($scope.plugin.api.routes, {action: 'list'})[0]; 150 | 151 | var route = routeList.route.replace(':username', $stateParams.consumerId); 152 | 153 | $http.get($localStorage.kongurl+'/'+route). 154 | success(function(data, status){ 155 | $scope.items = data; 156 | }); 157 | 158 | }; 159 | 160 | $scope.addRow = function() { 161 | $scope.inserted = {id: null}; 162 | angular.forEach($scope.plugin.api.dao, function(field){ 163 | $scope.inserted[field.name] = ''; 164 | }); 165 | 166 | $scope.items.data.push($scope.inserted); 167 | }; 168 | 169 | $scope.saveItem = function(data, id, index) { 170 | var routeList, route; 171 | if (id === null) { 172 | routeList = $filter('filter')($scope.plugin.api.routes, {action: 'create'})[0]; 173 | 174 | route = routeList.route.replace(':username', $stateParams.consumerId); 175 | 176 | $http.post($localStorage.kongurl+'/'+route, data). 177 | success(function(data, status){ 178 | $scope.items.data[index].id = data.id; 179 | }); 180 | 181 | } else { 182 | routeList = $filter('filter')($scope.plugin.api.routes, {action: 'update'})[0]; 183 | 184 | route = routeList.route.replace(':username', $stateParams.consumerId); 185 | 186 | route = route.replace(':id', id); 187 | 188 | $http.patch($localStorage.kongurl+'/'+route, data); 189 | } 190 | }; 191 | 192 | $scope.removeItem = function(index) { 193 | 194 | var id = $scope.items.data[index].id; 195 | 196 | var routeList = $filter('filter')($scope.plugin.api.routes, {action: 'delete'})[0]; 197 | 198 | var route = routeList.route.replace(':username', $stateParams.consumerId); 199 | 200 | route = route.replace(':id', id); 201 | 202 | $http.delete($localStorage.kongurl+'/'+route). 203 | success(function(data, status){ 204 | $scope.items.data.splice(index, 1); 205 | }); 206 | }; 207 | 208 | $scope.nextPage = function() { 209 | if ($scope.scroll.busy){ 210 | $scope.scroll.busy = false; 211 | return; 212 | } 213 | 214 | $scope.scroll.busy = true; 215 | 216 | 217 | if (undefined === $scope.consumers.next){ 218 | $scope.scroll.busy = false; 219 | return; 220 | } 221 | 222 | var offset = new URI($scope.consumers.next).search(true).offset; 223 | Consumers.query({offset: offset}, function(consumers){ 224 | $scope.consumers.next = consumers.next; 225 | $scope.consumers.data = $scope.consumers.data.concat(consumers.data); 226 | $scope.scroll.busy = false; 227 | }); 228 | }; 229 | 230 | // Set sorting default configuration 231 | $scope.sortType = 'username'; 232 | $scope.sortReverse = false; 233 | } 234 | ]); 235 | -------------------------------------------------------------------------------- /modules/consumers/services/consumers.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Consumers service used to communicate Consumers REST endpoints 4 | angular.module('consumers').factory('Consumers', ['$resource', '$localStorage', 5 | function($resource, $localStorage) { 6 | return $resource($localStorage.kongurl+'/consumers/:consumerId', { consumerId: '@id' 7 | }, { 8 | query: { 9 | method: 'GET', 10 | isArray: false 11 | }, 12 | update: { 13 | method: 'PATCH' 14 | } 15 | }); 16 | } 17 | ]); -------------------------------------------------------------------------------- /modules/consumers/tests/consumers.client.controller.test.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | // Consumers Controller Spec 4 | describe('Consumers Controller Tests', function() { 5 | // Initialize global variables 6 | var ConsumersController, 7 | scope, 8 | $httpBackend, 9 | $stateParams, 10 | $location, 11 | $KONGURL, 12 | $localStorage; 13 | 14 | // The $resource service augments the response object with methods for updating and deleting the resource. 15 | // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match 16 | // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. 17 | // When the toEqualData matcher compares two objects, it takes only object properties into 18 | // account and ignores methods. 19 | beforeEach(function() { 20 | jasmine.addMatchers({ 21 | toEqualData: function(util, customEqualityTesters) { 22 | return { 23 | compare: function(actual, expected) { 24 | return { 25 | pass: angular.equals(actual, expected) 26 | }; 27 | } 28 | }; 29 | } 30 | }); 31 | }); 32 | 33 | // Then we can start by loading the main application module 34 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 35 | 36 | // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). 37 | // This allows us to inject a service but then attach it to a variable 38 | // with the same name as the service. 39 | beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_, _KONGURL_, _$localStorage_) { 40 | // Set a new global scope 41 | scope = $rootScope.$new(); 42 | 43 | // Point global variables to injected services 44 | $stateParams = _$stateParams_; 45 | $httpBackend = _$httpBackend_; 46 | $location = _$location_; 47 | $KONGURL = _KONGURL_; 48 | $localStorage = _$localStorage_; 49 | $localStorage.kongurl = _KONGURL_; 50 | 51 | 52 | $httpBackend.when('GET', 'modules/core/views/home.client.view.html').respond(200, {}); 53 | // Initialize the Consumers controller. 54 | ConsumersController = $controller('ConsumersController', { 55 | $scope: scope 56 | }); 57 | })); 58 | 59 | it('$scope.find() should create an array with at least findOne Consumer object fetched from XHR', inject(function(Consumers) { 60 | // Create sample Consumer using the Consumers service 61 | var sampleConsumer = new Consumers({ 62 | username: 'New Consumer', 63 | custom_id: '4356' 64 | }); 65 | 66 | // Create a sample Consumers array that includes the new Consumer 67 | var sampleConsumers = {data: [sampleConsumer]}; 68 | 69 | // Set GET response 70 | $httpBackend.expectGET($KONGURL+'/consumers').respond(sampleConsumers); 71 | 72 | // Run controller functionality 73 | scope.find(); 74 | $httpBackend.flush(); 75 | 76 | // Test scope value 77 | expect(scope.consumers).toEqualData(sampleConsumers); 78 | })); 79 | 80 | it('$scope.findOne() should create an array with one Consumer object fetched from XHR using a consumerId URL parameter', inject(function(Consumers) { 81 | // Define a sample Consumer object 82 | var sampleConsumer = new Consumers({ 83 | username: 'New Consumer', 84 | custom_id: '4356' 85 | }); 86 | 87 | // Set the URL parameter 88 | $stateParams.consumerId = '525a8422f6d0f87f0e407a33'; 89 | 90 | // Set GET response 91 | $httpBackend.expectGET(/http:\/\/localhost:8001\/consumers\/([0-9a-fA-F]{24})$/).respond(sampleConsumer); 92 | 93 | // Run controller functionality 94 | scope.findOne(); 95 | $httpBackend.flush(); 96 | 97 | // Test scope value 98 | expect(scope.consumer).toEqualData(sampleConsumer); 99 | })); 100 | 101 | it('$scope.create() with valid form data should send a POST request with the form input values and then locate to new object URL', inject(function(Consumers) { 102 | // Create a sample Consumer object 103 | var sampleConsumerPostData = new Consumers({ 104 | username: 'New Consumer', 105 | custom_id: '4356' 106 | }); 107 | 108 | // Create a sample Consumer response 109 | var sampleConsumerResponse = new Consumers({ 110 | id: '525cf20451979dea2c000001', 111 | username: 'New Consumer', 112 | custom_id: '4356' 113 | }); 114 | 115 | // Fixture mock form input values 116 | scope.username = 'New Consumer'; 117 | scope.custom_id = '4356'; 118 | 119 | // Set POST response 120 | $httpBackend.expectPOST($KONGURL+'/consumers', sampleConsumerPostData).respond(sampleConsumerResponse); 121 | $httpBackend.when('GET', 'modules/consumers/views/view-consumer.client.view.html').respond(200, {}); 122 | 123 | // Run controller functionality 124 | scope.create(); 125 | $httpBackend.flush(); 126 | 127 | // Test form inputs are reset 128 | expect(scope.username).toEqual(''); 129 | 130 | // Test URL redirection after the Consumer was created 131 | expect($location.path()).toBe('/consumers/' + sampleConsumerResponse.id); 132 | })); 133 | 134 | it('$scope.update() should update a valid Consumer', inject(function(Consumers) { 135 | // Define a sample Consumer put data 136 | var sampleConsumerPutData = new Consumers({ 137 | id: '525cf20451979dea2c000001', 138 | username: 'New Consumer', 139 | custom_id: '4356' 140 | }); 141 | 142 | // Mock Consumer in scope 143 | scope.consumer = sampleConsumerPutData; 144 | 145 | // Set PUT response 146 | $httpBackend.expectPATCH(/http:\/\/localhost:8001\/consumers\/([0-9a-fA-F]{24})$/).respond(); 147 | $httpBackend.when('GET', 'modules/consumers/views/view-consumer.client.view.html').respond(200, {}); 148 | // Run controller functionality 149 | scope.update(); 150 | $httpBackend.flush(); 151 | 152 | // Test URL location to new object 153 | expect($location.path()).toBe('/consumers/' + sampleConsumerPutData.id); 154 | })); 155 | 156 | it('$scope.remove() should send a DELETE request with a valid consumerId and remove the Consumer from the scope', inject(function(Consumers) { 157 | // Create new Consumer object 158 | var sampleConsumer = new Consumers({ 159 | id: '525a8422f6d0f87f0e407a33' 160 | }); 161 | 162 | // Create new Consumers array and include the Consumer 163 | scope.consumers = [sampleConsumer]; 164 | 165 | // Set expected DELETE response 166 | $httpBackend.expectDELETE(/consumers\/([0-9a-fA-F]{24})$/).respond(204); 167 | 168 | // Run controller functionality 169 | scope.remove(sampleConsumer); 170 | $httpBackend.flush(); 171 | 172 | // Test array after successful delete 173 | expect(scope.consumers.length).toBe(0); 174 | })); 175 | }); 176 | }()); 177 | -------------------------------------------------------------------------------- /modules/consumers/views/create-consumer.client.view.html: -------------------------------------------------------------------------------- 1 |
    2 | 5 |
    6 |
    7 |
    8 |
    9 | 10 |
    11 | 12 |
    13 |
    14 |
    15 | 16 |
    17 | 18 |
    19 |
    20 |
    21 | 22 |
    23 |
    24 | 25 |
    26 |
    27 |
    28 |
    29 |
    -------------------------------------------------------------------------------- /modules/consumers/views/crud-plugin-consumer.client.view.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 26 | 41 | 42 |
    {{field.label}}Actions
    9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {{ item[field.name] && "True" || "False" }} 23 | 24 | 25 | 27 | 28 |
    29 | 32 | 35 |
    36 |
    37 | 38 | 39 |
    40 |
    43 | 44 |
    -------------------------------------------------------------------------------- /modules/consumers/views/edit-consumer.client.view.html: -------------------------------------------------------------------------------- 1 |
    2 | 5 |
    6 |
    7 |
    8 |
    9 | 10 |
    11 | 12 |
    13 |
    14 |
    15 | 16 |
    17 | 18 |
    19 |
    20 |
    21 | 22 |
    23 |
    24 | 25 |
    26 |
    27 |
    28 |
    29 |
    -------------------------------------------------------------------------------- /modules/consumers/views/list-consumers.client.view.html: -------------------------------------------------------------------------------- 1 |
    2 | 5 |
    6 |
    7 |
    8 | 9 |
    10 |
    11 | 12 | 13 | 18 | 23 | 28 | 29 | 30 |
    31 |
    32 | 35 | 40 | 45 | 61 | 62 | 63 |
    14 | ID 15 | 16 | 17 | 19 | Username 20 | 21 | 22 | 24 | Custom ID 25 | 26 | 27 | Action
    33 | {{consumer.id}} 34 | 36 | 37 | {{consumer.username}} 38 | 39 | 41 | 42 | {{consumer.custom_id}} 43 | 44 | 46 |
    47 | 50 | 53 |
    54 |
    55 | 56 | 57 | View Consumer Details 58 | 59 |
    60 |
    64 |
    65 | No Consumers yet, why don't you create one? 66 |
    67 |
    68 | -------------------------------------------------------------------------------- /modules/consumers/views/view-api-plugin-consumer.client.view.html: -------------------------------------------------------------------------------- 1 |
    2 | 5 |
    6 |
  • 7 |

    8 | 13 |

    14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 |
    AttributeValue
    Api name 22 | {{api.name}} 23 |
    {{key}}{{value}}
    30 |

    31 |
  • 32 |
    33 |
    34 | No active plugins found in the API, why don't you add one ? 35 |
    36 |
    37 |
    38 | 41 |
    42 |
    43 |
    44 |
    45 | 46 | 49 |
    50 |
    51 | 52 | 55 |
    56 | 57 |
    58 |
    59 |
    60 |
    -------------------------------------------------------------------------------- /modules/consumers/views/view-consumer.client.view.html: -------------------------------------------------------------------------------- 1 |
    2 | 5 | 26 |

    27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
    AttributeValue
    username{{consumer.username}}
    custom_id{{consumer.custom_id}}
    41 |

    42 |
    43 | -------------------------------------------------------------------------------- /modules/core/config/core.client.constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Setting up route 4 | angular.module('core').value('KONGURL', 'http://localhost:8001'); 5 | -------------------------------------------------------------------------------- /modules/core/config/core.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Setting up route 4 | angular.module('core').config(['$stateProvider', '$urlRouterProvider', 5 | function($stateProvider, $urlRouterProvider) { 6 | // Redirect to home view when route not found 7 | $urlRouterProvider.otherwise('/'); 8 | 9 | // Home state routing 10 | $stateProvider. 11 | state('home', { 12 | url: '/', 13 | templateUrl: 'modules/core/views/home.client.view.html', 14 | ncyBreadcrumb: { 15 | label: 'Home page' 16 | } 17 | }); 18 | } 19 | ]); -------------------------------------------------------------------------------- /modules/core/controllers/header.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core').controller('HeaderController', ['$scope', '$location', '$state', '$window', '$localStorage', '$http', 'Menus', 'KONGURL', 4 | function($scope, $location, $state, $window, $localStorage, $http, Menus, KONGURL) { 5 | $scope.isCollapsed = false; 6 | $scope.menu = Menus.getMenu('topbar'); 7 | $scope.storage = $localStorage; 8 | 9 | $scope.toggleCollapsibleMenu = function() { 10 | $scope.isCollapsed = !$scope.isCollapsed; 11 | }; 12 | 13 | // Collapsing the menu after navigation 14 | $scope.$on('$stateChangeSuccess', function() { 15 | $scope.isCollapsed = false; 16 | }); 17 | 18 | $scope.setKongUrl = function() { 19 | var url = this.url = this.url.replace(/\/$/, ''); 20 | $http.get(this.url).success(function(data, status){ 21 | if (status === 200) { 22 | $localStorage.kongurl = url; 23 | setTimeout(function () { 24 | $window.location.reload(); 25 | }, 1000); 26 | } 27 | }). 28 | error(function(data, status){ 29 | 30 | }); 31 | 32 | }; 33 | 34 | $scope.removeKongUrl = function() { 35 | delete $localStorage.kongurl; 36 | $localStorage.hasDisconnected = true; 37 | setTimeout(function () { 38 | $window.location.reload(); 39 | }, 1000); 40 | }; 41 | } 42 | ]); 43 | -------------------------------------------------------------------------------- /modules/core/controllers/home.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | angular.module('core').controller('HomeController', ['$scope', '$http', '$localStorage', 'PLUGINSAVAILABLE', 'KONGURL', 5 | function($scope, $http, $localStorage, PLUGINSAVAILABLE, KONGURL) { 6 | $scope.storage = $localStorage; 7 | $scope.isArray = angular.isArray; 8 | $scope.pluginsAvailable = PLUGINSAVAILABLE; 9 | if ($localStorage.hasDisconnected !== true && typeof KONGURL !== 'undefined'){ 10 | $localStorage.kongurl = KONGURL; 11 | } 12 | if ($localStorage.kongurl !== undefined) { 13 | $http.get($localStorage.kongurl). 14 | success(function(data, status){ 15 | $scope.serverInfo = data; 16 | }); 17 | } 18 | } 19 | ]); 20 | -------------------------------------------------------------------------------- /modules/core/core.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use Applicaion configuration module to register a new module 4 | ApplicationConfiguration.registerModule('core'); -------------------------------------------------------------------------------- /modules/core/css/core.css: -------------------------------------------------------------------------------- 1 | .content { 2 | margin-top: 50px; 3 | } 4 | .undecorated-link:hover { 5 | text-decoration: none; 6 | } 7 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 8 | display: none !important; 9 | } 10 | .ng-invalid.ng-dirty { 11 | border-color: #FA787E; 12 | } 13 | .ng-valid.ng-dirty { 14 | border-color: #78FA89; 15 | } -------------------------------------------------------------------------------- /modules/core/img/brand/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsdevigo/jungle/4c4886955d9ab4feb08fb3abe4979f1eb12bbe62/modules/core/img/brand/favicon.ico -------------------------------------------------------------------------------- /modules/core/img/brand/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsdevigo/jungle/4c4886955d9ab4feb08fb3abe4979f1eb12bbe62/modules/core/img/brand/logo.png -------------------------------------------------------------------------------- /modules/core/img/loaders/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsdevigo/jungle/4c4886955d9ab4feb08fb3abe4979f1eb12bbe62/modules/core/img/loaders/loader.gif -------------------------------------------------------------------------------- /modules/core/services/menus.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Menu service used for managing menus 4 | angular.module('core').service('Menus', [ 5 | 6 | function() { 7 | // Define a set of default roles 8 | this.defaultRoles = ['*']; 9 | 10 | // Define the menus object 11 | this.menus = {}; 12 | 13 | // A private function for rendering decision 14 | var shouldRender = function(user) { 15 | if (user) { 16 | if (!!~this.roles.indexOf('*')) { 17 | return true; 18 | } else { 19 | for (var userRoleIndex in user.roles) { 20 | for (var roleIndex in this.roles) { 21 | if (this.roles[roleIndex] === user.roles[userRoleIndex]) { 22 | return true; 23 | } 24 | } 25 | } 26 | } 27 | } else { 28 | return this.isPublic; 29 | } 30 | 31 | return false; 32 | }; 33 | 34 | // Validate menu existance 35 | this.validateMenuExistance = function(menuId) { 36 | if (menuId && menuId.length) { 37 | if (this.menus[menuId]) { 38 | return true; 39 | } else { 40 | throw new Error('Menu does not exists'); 41 | } 42 | } else { 43 | throw new Error('MenuId was not provided'); 44 | } 45 | 46 | return false; 47 | }; 48 | 49 | // Get the menu object by menu id 50 | this.getMenu = function(menuId) { 51 | // Validate that the menu exists 52 | this.validateMenuExistance(menuId); 53 | 54 | // Return the menu object 55 | return this.menus[menuId]; 56 | }; 57 | 58 | // Add new menu object by menu id 59 | this.addMenu = function(menuId, isPublic, roles) { 60 | // Create the new menu 61 | this.menus[menuId] = { 62 | isPublic: isPublic || false, 63 | roles: roles || this.defaultRoles, 64 | items: [], 65 | shouldRender: shouldRender 66 | }; 67 | 68 | // Return the menu object 69 | return this.menus[menuId]; 70 | }; 71 | 72 | // Remove existing menu object by menu id 73 | this.removeMenu = function(menuId) { 74 | // Validate that the menu exists 75 | this.validateMenuExistance(menuId); 76 | 77 | // Return the menu object 78 | delete this.menus[menuId]; 79 | }; 80 | 81 | // Add menu item object 82 | this.addMenuItem = function(menuId, menuItemTitle, menuItemURL, menuItemType, menuItemUIRoute, isPublic, roles, position) { 83 | // Validate that the menu exists 84 | this.validateMenuExistance(menuId); 85 | 86 | // Push new menu item 87 | this.menus[menuId].items.push({ 88 | title: menuItemTitle, 89 | link: menuItemURL, 90 | menuItemType: menuItemType || 'item', 91 | menuItemClass: menuItemType, 92 | uiRoute: menuItemUIRoute || ('/' + menuItemURL), 93 | isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].isPublic : isPublic), 94 | roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].roles : roles), 95 | position: position || 0, 96 | items: [], 97 | shouldRender: shouldRender 98 | }); 99 | 100 | // Return the menu object 101 | return this.menus[menuId]; 102 | }; 103 | 104 | // Add submenu item object 105 | this.addSubMenuItem = function(menuId, rootMenuItemURL, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles, position) { 106 | // Validate that the menu exists 107 | this.validateMenuExistance(menuId); 108 | 109 | // Search for menu item 110 | for (var itemIndex in this.menus[menuId].items) { 111 | if (this.menus[menuId].items[itemIndex].link === rootMenuItemURL) { 112 | // Push new submenu item 113 | this.menus[menuId].items[itemIndex].items.push({ 114 | title: menuItemTitle, 115 | link: menuItemURL, 116 | uiRoute: menuItemUIRoute || ('/' + menuItemURL), 117 | isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : isPublic), 118 | roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : roles), 119 | position: position || 0, 120 | shouldRender: shouldRender 121 | }); 122 | } 123 | } 124 | 125 | // Return the menu object 126 | return this.menus[menuId]; 127 | }; 128 | 129 | // Remove existing menu object by menu id 130 | this.removeMenuItem = function(menuId, menuItemURL) { 131 | // Validate that the menu exists 132 | this.validateMenuExistance(menuId); 133 | 134 | // Search for menu item to remove 135 | for (var itemIndex in this.menus[menuId].items) { 136 | if (this.menus[menuId].items[itemIndex].link === menuItemURL) { 137 | this.menus[menuId].items.splice(itemIndex, 1); 138 | } 139 | } 140 | 141 | // Return the menu object 142 | return this.menus[menuId]; 143 | }; 144 | 145 | // Remove existing menu object by menu id 146 | this.removeSubMenuItem = function(menuId, submenuItemURL) { 147 | // Validate that the menu exists 148 | this.validateMenuExistance(menuId); 149 | 150 | // Search for menu item to remove 151 | for (var itemIndex in this.menus[menuId].items) { 152 | for (var subitemIndex in this.menus[menuId].items[itemIndex].items) { 153 | if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) { 154 | this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1); 155 | } 156 | } 157 | } 158 | 159 | // Return the menu object 160 | return this.menus[menuId]; 161 | }; 162 | 163 | //Adding the topbar menu 164 | this.addMenu('topbar'); 165 | } 166 | ]); -------------------------------------------------------------------------------- /modules/core/tests/header.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | describe('HeaderController', function() { 5 | //Initialize global variables 6 | var scope, 7 | HeaderController; 8 | 9 | // Load the main application module 10 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 11 | 12 | beforeEach(inject(function($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | 15 | HeaderController = $controller('HeaderController', { 16 | $scope: scope 17 | }); 18 | })); 19 | 20 | 21 | }); 22 | })(); -------------------------------------------------------------------------------- /modules/core/tests/home.client.controller.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | describe('HomeController', function() { 5 | //Initialize global variables 6 | var scope, 7 | HomeController; 8 | 9 | // Load the main application module 10 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 11 | 12 | beforeEach(inject(function($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | 15 | HomeController = $controller('HomeController', { 16 | $scope: scope 17 | }); 18 | })); 19 | 20 | 21 | }); 22 | })(); -------------------------------------------------------------------------------- /modules/core/views/header.client.view.html: -------------------------------------------------------------------------------- 1 | 20 |
    21 |
    22 | 34 | -------------------------------------------------------------------------------- /modules/core/views/home.client.view.html: -------------------------------------------------------------------------------- 1 |
    2 | 6 |
    7 |
    8 |
    9 |
    How to connect your KONG instance
    10 |
    11 |
      12 |
    1. Add your Kong Admin API in the your Kong apis list
    2. 13 |
    3. 14 |
    4. First add your Kong Admin API in the Kong
    5. 15 |
    16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 |
    23 |
    Server info
    24 |
    25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 53 | 54 |
    Host{{storage.kongurl}}
    Hostname{{serverInfo.hostname}}
    Kong version{{serverInfo.version}}
    Lua version{{serverInfo.lua_version}}
    Plugins 45 | 46 | 47 | {{plugin}} 48 | {{plugin}} 49 | 50 | 51 | 52 |
    55 |
    56 |
    57 |
    58 |
    59 |
    60 |
    61 |
    62 | JUNGLE 63 |
    64 |
    65 |
    66 |
    67 |

    68 | Open-Source User Interface for KONG 69 |

    70 |
    71 |
    72 |

    73 | Learn more 74 |

    75 |
    76 |
    77 | 78 |
    79 |

    KONG Documentation

    80 |

    81 |

    91 |

    92 |
    93 |
    94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jungle", 3 | "version": "0.0.1", 4 | "dependencies": {}, 5 | "repository": {}, 6 | "devDependencies": { 7 | "grunt": "^0.4.5", 8 | "grunt-autoprefixer": "^2.0.0", 9 | "grunt-concurrent": "^1.0.0", 10 | "grunt-contrib-clean": "^0.6.0", 11 | "grunt-contrib-concat": "^0.5.0", 12 | "grunt-contrib-connect": "^0.9.0", 13 | "grunt-contrib-copy": "^0.7.0", 14 | "grunt-contrib-cssmin": "^0.12.0", 15 | "grunt-contrib-htmlmin": "^0.4.0", 16 | "grunt-contrib-imagemin": "^0.9.2", 17 | "grunt-contrib-jshint": "~0.10.0", 18 | "grunt-contrib-csslint": "^0.3.1", 19 | "grunt-contrib-uglify": "^0.7.0", 20 | "grunt-contrib-watch": "^0.6.1", 21 | "grunt-filerev": "^2.1.2", 22 | "grunt-google-cdn": "^0.4.3", 23 | "grunt-karma": "*", 24 | "grunt-newer": "^1.1.0", 25 | "grunt-ng-annotate": "^0.9.2", 26 | "grunt-svgmin": "^2.0.0", 27 | "grunt-usemin": "^3.0.0", 28 | "grunt-wiredep": "^2.0.0", 29 | "jshint-stylish": "^1.0.0", 30 | "karma-jasmine": "*", 31 | "karma-phantomjs-launcher": "*", 32 | "karma-chrome-launcher": "*", 33 | "phantomjs": "*", 34 | "karma": "*", 35 | "load-grunt-tasks": "^3.1.0", 36 | "time-grunt": "^1.0.0", 37 | "glob": "~4.0.5", 38 | "lodash": "~2.4.1", 39 | "async": "~0.9.0", 40 | "grunt-env": "~0.4.1", 41 | "chalk": "~0.5", 42 | "grunt-htmlrefs": "*" 43 | }, 44 | "engines": { 45 | "node": ">=0.10.0" 46 | }, 47 | "scripts": { 48 | "test": "grunt test" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "jasmine": true, 22 | "globals": { 23 | "angular": false, 24 | "browser": false, 25 | "inject": false 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.12/config/configuration-file.html 3 | // Generated on 2015-06-10 using 4 | // generator-karma 1.0.0 5 | 6 | module.exports = function(config) { 7 | 'use strict'; 8 | 9 | config.set({ 10 | // enable / disable watching file and executing tests whenever any file changes 11 | autoWatch: true, 12 | 13 | // base path, that will be used to resolve files and exclude 14 | basePath: '../', 15 | 16 | // testing framework to use (jasmine/mocha/qunit/...) 17 | // as well as any additional frameworks (requirejs/chai/sinon/...) 18 | frameworks: [ 19 | "jasmine" 20 | ], 21 | 22 | // list of files / patterns to load in the browser 23 | files: [ 24 | // bower:js 25 | 'lib/jquery/dist/jquery.js', 26 | 'lib/bootstrap/dist/js/bootstrap.js', 27 | 'lib/angular/angular.js', 28 | 'lib/angular-resource/angular-resource.js', 29 | 'lib/angular-mocks/angular-mocks.js', 30 | 'lib/angular-cookies/angular-cookies.js', 31 | 'lib/angular-animate/angular-animate.js', 32 | 'lib/angular-touch/angular-touch.js', 33 | 'lib/angular-sanitize/angular-sanitize.js', 34 | 'lib/angular-bootstrap/ui-bootstrap-tpls.js', 35 | 'lib/angular-ui-utils/ui-utils.js', 36 | 'lib/angular-ui-router/release/angular-ui-router.js', 37 | 'lib/angular-breadcrumb/release/angular-breadcrumb.js', 38 | // endbower 39 | "app/scripts/**/*.js", 40 | "test/mock/**/*.js", 41 | "test/spec/**/*.js" 42 | ], 43 | 44 | // list of files / patterns to exclude 45 | exclude: [ 46 | ], 47 | 48 | // web server port 49 | port: 8080, 50 | 51 | // Start these browsers, currently available: 52 | // - Chrome 53 | // - ChromeCanary 54 | // - Firefox 55 | // - Opera 56 | // - Safari (only Mac) 57 | // - PhantomJS 58 | // - IE (only Windows) 59 | browsers: [ 60 | "PhantomJS" 61 | ], 62 | 63 | // Which plugins to enable 64 | plugins: [ 65 | "karma-phantomjs-launcher", 66 | "karma-jasmine" 67 | ], 68 | 69 | // Continuous Integration mode 70 | // if true, it capture browsers, run tests and exit 71 | singleRun: false, 72 | 73 | colors: true, 74 | 75 | // level of logging 76 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 77 | logLevel: config.LOG_INFO, 78 | 79 | // Uncomment the following lines if you are using grunt's server to run the tests 80 | // proxies: { 81 | // '/': 'http://localhost:9000/' 82 | // }, 83 | // URL root prevent conflicts with the site root 84 | // urlRoot: '_karma_' 85 | }); 86 | }; 87 | -------------------------------------------------------------------------------- /test/spec/controllers/about.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AboutCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('jungleApp')); 7 | 8 | var AboutCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | AboutCtrl = $controller('AboutCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/apis.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: ApisCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('jungleApp')); 7 | 8 | var ApisCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | ApisCtrl = $controller('ApisCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/consumers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: ConsumersCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('jungleApp')); 7 | 8 | var ConsumersCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | ConsumersCtrl = $controller('ConsumersCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('jungleApp')); 7 | 8 | var MainCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | MainCtrl = $controller('MainCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | --------------------------------------------------------------------------------