├── .eslintignore ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── backend ├── .env.template ├── app.js ├── bin │ └── www ├── package-lock.json ├── package.json └── routes │ ├── index.js │ └── users.js ├── frontend ├── README.md ├── app.js ├── babel.config.js ├── index.html ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── .eslintrc.js │ ├── App.vue │ ├── assets │ │ ├── logo.png │ │ └── logo.svg │ ├── components │ │ ├── DetailedInferenceView.vue │ │ ├── Drag.vue │ │ ├── HelloWorld.vue │ │ └── Modal.vue │ ├── dist │ │ ├── json-tree.css │ │ ├── vfc.css │ │ ├── vuetable-2.css │ │ └── vuetable-2.js │ ├── main.js │ └── plugins │ │ └── vuetify.js └── vue.config.js ├── images ├── GettingStartedWComposer-arch-diagram.png ├── addtowallet.png ├── admintab.png ├── archi.png ├── checkcompleted.png ├── composerplayground.png ├── createparticipantbtn.png ├── developer-analytical-dashboard-ai-powerai-flow-11.png ├── developer-analytical-dashboards-ai-powerai-flow.png ├── generateNewId.png ├── idstowallet.png ├── importbtn.png ├── productListing.png ├── retailer.png ├── retailerPL.png └── selectid.png ├── lib ├── foodSupply.js └── foodSupplyFabric.js ├── models ├── base.cto └── foodSupply.cto ├── package.json └── permissions.acl /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | go 4 | lib 5 | node_modules 6 | out 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Emacs backup files 2 | *~ 3 | *# 4 | .#* 5 | # Vim file artifacts 6 | .*.sw* 7 | # installed platform-specific binaries 8 | .DS_Store 9 | .env 10 | local/crypto-config/ 11 | backend/hfc-key-store/ 12 | backend/.env 13 | frontend/.env 14 | */.env 15 | 16 | .project 17 | 18 | # Logs 19 | logs 20 | *.log 21 | npm-debug.log* 22 | 23 | # Runtime data 24 | pids 25 | *.pid 26 | *.seed 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | lib-cov 30 | 31 | # Coverage directory used by tools like istanbul 32 | coverage 33 | 34 | # nyc test coverage 35 | .nyc_output 36 | 37 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 38 | .grunt 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (http://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules 48 | jspm_packages 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # JSDoc 57 | out 58 | 59 | # Mac files. 60 | **/.DS_Store 61 | 62 | *.swp 63 | 64 | # Build generated files should be ignored by git, but not by npm. 65 | #dist 66 | 67 | node_modules 68 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # JSDoc 40 | out 41 | 42 | # Mac files. 43 | **/.DS_Store 44 | 45 | *.swp 46 | 47 | # Build generated files should be ignored by git, but not by npm. 48 | # dist 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This is an open source project, and we appreciate your help! 4 | 5 | We use the GitHub issue tracker to discuss new features and non-trivial bugs. 6 | 7 | In addition to the issue tracker, [#journeys on 8 | Slack](https://dwopen.slack.com) is the best way to get into contact with the 9 | project's maintainers. 10 | 11 | To contribute code, documentation, or tests, please submit a pull request to 12 | the GitHub repository. Generally, we expect two maintainers to review your pull 13 | request before it is approved for merging. For more details, see the 14 | [MAINTAINERS](MAINTAINERS.md) page. 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers Guide 2 | 3 | This guide is intended for maintainers - anybody with commit access to one or 4 | more Code Pattern repositories. 5 | 6 | ## Methodology 7 | 8 | This repository does not have a traditional release management cycle, but 9 | should instead be maintained as as a useful, working, and polished reference at 10 | all times. While all work can therefore be focused on the master branch, the 11 | quality of this branch should never be compromised. 12 | 13 | The remainder of this document details how to merge pull requests to the 14 | repositories. 15 | 16 | ## Merge approval 17 | 18 | The project maintainers use LGTM (Looks Good To Me) in comments on the pull 19 | request to indicate acceptance prior to merging. A change requires LGTMs from 20 | two project maintainers. If the code is written by a maintainer, the change 21 | only requires one additional LGTM. 22 | 23 | ## Reviewing Pull Requests 24 | 25 | We recommend reviewing pull requests directly within GitHub. This allows a 26 | public commentary on changes, providing transparency for all users. When 27 | providing feedback be civil, courteous, and kind. Disagreement is fine, so long 28 | as the discourse is carried out politely. If we see a record of uncivil or 29 | abusive comments, we will revoke your commit privileges and invite you to leave 30 | the project. 31 | 32 | During your review, consider the following points: 33 | 34 | ### Does the change have positive impact? 35 | 36 | Some proposed changes may not represent a positive impact to the project. Ask 37 | whether or not the change will make understanding the code easier, or if it 38 | could simply be a personal preference on the part of the author (see 39 | [bikeshedding](https://en.wiktionary.org/wiki/bikeshedding)). 40 | 41 | Pull requests that do not have a clear positive impact should be closed without 42 | merging. 43 | 44 | ### Do the changes make sense? 45 | 46 | If you do not understand what the changes are or what they accomplish, ask the 47 | author for clarification. Ask the author to add comments and/or clarify test 48 | case names to make the intentions clear. 49 | 50 | At times, such clarification will reveal that the author may not be using the 51 | code correctly, or is unaware of features that accommodate their needs. If you 52 | feel this is the case, work up a code sample that would address the pull 53 | request for them, and feel free to close the pull request once they confirm. 54 | 55 | ### Does the change introduce a new feature? 56 | 57 | For any given pull request, ask yourself "is this a new feature?" If so, does 58 | the pull request (or associated issue) contain narrative indicating the need 59 | for the feature? If not, ask them to provide that information. 60 | 61 | Are new unit tests in place that test all new behaviors introduced? If not, do 62 | not merge the feature until they are! Is documentation in place for the new 63 | feature? (See the documentation guidelines). If not do not merge the feature 64 | until it is! Is the feature necessary for general use cases? Try and keep the 65 | scope of any given component narrow. If a proposed feature does not fit that 66 | scope, recommend to the user that they maintain the feature on their own, and 67 | close the request. You may also recommend that they see if the feature gains 68 | traction among other users, and suggest they re-submit when they can show such 69 | support. 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING: This repository is no longer maintained 2 | 3 | > This repository will not be updated. The repository will be kept available in read-only mode. 4 | 5 | # Generate and visualize video analytics using IBM Maximo Visual Inspection 6 | 7 | In this Code Pattern we will show how to deploy a customizable dashboard to visualize video/image analytics. This dashboard enables users to upload images to be processed by IBM Maximo Visual Inspection (object recognition, image classification), download the analyzed results, and view analytics via interactive graphs. 8 | 9 | When the reader has completed this Code Pattern, they will understand how to build a dashboard using Vue.js and Maximo Visual Inspection APIs to generate and visualize image analytics. 10 | 11 | 12 | 13 | 14 | 15 | ![Architecture](images/developer-analytical-dashboard-ai-powerai-flow-11.png) 16 | 17 | 18 | # Components 19 | 20 | * [IBM Maximo Visual Inspection](https://www.ibm.com/us-en/marketplace/ibm-powerai-vision). This is an image analysis platform that allows you to build and manage computer vision models, upload and annotate images, and deploy apis to analyze images and videos. 21 | 22 | Sign up for a trial account of IBM Maximo Visual Inspection [here](https://developer.ibm.com/linuxonpower/deep-learning-powerai/try-powerai/). This link includes options to provision a IBM Maximo Visual Inspection instance either locally on in the cloud. 23 | 24 | 25 | 26 | # Flow 27 | 28 | 1. Upload images to IBM Maximo Visual Inspection 29 | 2. Label uploaded images to train model 30 | 3. Deploy model 31 | 4. Upload image via dashboard 32 | 5. View processed image and graphs in dashboard 33 | 34 | # Prerequisites 35 | 36 | * An account on IBM Marketplace that has access to IBM Maximo Visual Inspection. This service can be provisioned [here](https://developer.ibm.com/linuxonpower/deep-learning-powerai/vision/access-registration-form/) 37 | 38 | # Steps 39 | 40 | Follow these steps to setup and run this Code Pattern. 41 | 42 | 1. [Upload training images to IBM Maximo Visual Inspection ](#1-upload-training-images-to-IBM-Visual-Insights) 43 | 2. [Train and deploy model in IBM Maximo Visual Inspection](#2-Train-and-deploy-model-in-IBM-Visual-Insights) 44 | 3. [Clone repository](#3-clone-repository) 45 | 4. [Deploy dashboard](#4-Deploy-dashboard) 46 | 5. [Upload images to be processed via dashboard](#5-Upload-images-to-be-processed-via-dashboard) 47 | 6. [View processed images and graphs in dashboard](#6-View-images-and-graphs-in-dashboard) 48 | 49 | 50 | 51 | ## 1. Upload training images to IBM Maximo Visual Inspection 52 | 53 | Login to IBM Maximo Visual Inspection Dashboard 54 | 55 | 56 | 57 | 58 | To build a model, we'll first need to upload a set of images. Click "Datasets" in the upper menu. Then, click "Create New Data Set", and enter a name. We'll use "traffic_long" here 59 | 60 | 61 | 62 | 63 | 64 | Drag and drop one or more images to build your dataset. 65 | 66 | 67 | 68 | 69 | 70 | ## 2. Train and deploy model in IBM Maximo Visual Inspection 71 | 72 | In this example, we'll build an object recognition model to identify specific objects in each frame of a video. After the images have completed uploading to the dataset, select one or more images in the dataset, and then select "Label Objects". 73 | 74 | 75 | 76 | 77 | 78 | 79 | Next, we'll split the training video into multiple frames. We'll label objects in a few of the frames manually. After generating a model, we can automatically label the rest of the frames to increase accuracy. 80 | 81 | 82 | 83 | 84 | 85 | Identify what kinds of objects will need to be recognized. Click "Add Label", and type the name of each object. In this case, we're detecting traffic on a freeway, so we'll set our objects as "car", "truck", and "bus". 86 | 87 | 88 | 89 | 90 | 91 | We can then manually annotate objects by 92 | 1. Selecting a video frame 93 | 2. Selecting an object type 94 | 3. Drawing a rectangle (or custom shape) around object in frame 95 | 96 | 97 | 98 | 99 | 100 | After annotating a few frames, we can then build a model. Do so by going back to the "Datasets" view, selecting your dataset, and then selecting "Train Model" 101 | 102 | 103 | 104 | 105 | Select type of Model you'd like to build. In this case, we'll use "Object Detection" as our model type, and "Detectron" as our model optimizer. Then, click the "Train Model" button. 106 | 107 | 108 | 109 | After the model completes training, click the "Models" button in the upper menu. Then, select the model and then click the "Deploy Model" button. 110 | 111 | 112 | 113 | 114 | Deploying the custom will establish an endpoint where images and videos can be uploaded, either through the UI or through a REST API endpoint. 115 | 116 | 117 | 118 | 119 | 120 | ## 3. Clone repository 121 | 122 | Clone repository using the git cli 123 | 124 | ``` 125 | git clone https://github.com/IBM/power-ai-dashboard 126 | ``` 127 | 128 | ### Install Node.js packages 129 | 130 | If expecting to run this application locally, please install [Node.js](https://nodejs.org/en/) and NPM. Windows users can use the installer at the link [here](https://nodejs.org/en/download/) 131 | 132 | If you're using Mac OS X or Linux, and your system requires additional versions of node for other projects, we'd suggest using [nvm](https://github.com/creationix/nvm) to easily switch between node versions. Install nvm with the following commands 133 | 134 | ```bash 135 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash 136 | # Place next three lines in ~/.bash_profile 137 | export NVM_DIR="$HOME/.nvm" 138 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm 139 | [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion 140 | ``` 141 | 142 | 143 | ``` 144 | nvm install v8.9.0 145 | nvm use 8.9.0 146 | ``` 147 | 148 | Also install [ffmpeg](https://www.ffmpeg.org/) using on of the following command, depending on your operating system. ffmpeg enables the app to receive metadata describing the analyzed videos. 149 | 150 | This may take a while (10-15 minutes). 151 | 152 | ``` 153 | # OS X 154 | brew install ffmpeg 155 | 156 | # Linux 157 | sudo apt install ffmpeg -y 158 | ``` 159 | 160 | To run the dashboard locally, we'll need to install a few node libraries which are listed in our `package.json` file. 161 | - [Vue.js](https://vuejs.org/): Used to simplify the generation of front-end components 162 | - [Express.js](https://expressjs.org/): Used to provide custom api endpoints 163 | 164 | These libraries can be installed by entering the following commands in a terminal. 165 | 166 | ``` 167 | cd visual_insights_dashboard 168 | ``` 169 | 170 | ``` 171 | cd backend 172 | npm install 173 | cd .. 174 | cd frontend 175 | npm install 176 | ``` 177 | 178 | 179 | ## 4. Deploy dashboard 180 | 181 | After installing the prerequisites, we can start the dashboard application. 182 | 183 | 184 | 185 | Run the following to start the backend 186 | ``` 187 | cd backend 188 | npm start 189 | ``` 190 | 191 | In a separate terminal, run the following to start the frontend UI 192 | ``` 193 | cd frontend 194 | npm run serve 195 | ``` 196 | 197 | 198 | Confirm you can access the Dashboard UI at [http://localhost:8080](http://localhost:8080). 199 | 200 | 201 | 202 | 203 | 204 | Click the Login button at the top and enter your IBM Maximo Visual Inspection credentials. These credentials should be included in the welcome letter when your PowerAI instance provisioned. This input form requires a username, password, and url where the instance can be accessed. 205 | 206 | 207 | 208 | ## 5. Upload images to be processed via dashboard 209 | 210 | After providing our IBM Maximo Visual Inspection credentials, we can then use the dashboard to 211 | Let's upload a video or image to be processed by our custom model. We'll do this by clicking the "Upload Image(s)" button in the upper menu. Then, drag and drop images that need to be analyzed. Select a model from the selection dropbox, and then click the "Upload" button. 212 | 213 | 214 | 215 | 216 | ## 6. View processed images and graphs in dashboard 217 | 218 | As images are uploaded to the IBM Maximo Visual Inspection service, they'll be shown in a grid in the main dashboard view. We can use the "Search" input to filter the image analysis results by time, model id, object type, etc. Also, the annotated images can be downloaded as a zip file by clicking the "Download Images" button. 219 | 220 | Select any of the images to open a detailed view for a video/image. This detailed view will show the original image/video, as well as a few graphs showing basic video analytics, such as a breakdown of objects detected per second (line graph), and a comparison of total detected objects by type (circle graph). 221 | 222 | 223 | 224 | 225 | # Learn more 226 | 227 | 228 | 229 | 230 | 231 | # License 232 | 233 | This code pattern is licensed under the Apache Software License, Version 2. Separate third party code objects invoked within this code pattern are licensed by their respective providers pursuant to their own separate licenses. Contributions are subject to the [Developer Certificate of Origin, Version 1.1 (DCO)](https://developercertificate.org/) and the [Apache Software License, Version 2](https://www.apache.org/licenses/LICENSE-2.0.txt). 234 | 235 | [Apache Software License (ASL) FAQ](https://www.apache.org/foundation/license-faq.html#WhatDoesItMEAN) 236 | -------------------------------------------------------------------------------- /backend/.env.template: -------------------------------------------------------------------------------- 1 | url="" 2 | user="" 3 | password="" 4 | -------------------------------------------------------------------------------- /backend/app.js: -------------------------------------------------------------------------------- 1 | var createError = require('http-errors'); 2 | var express = require('express'); 3 | var path = require('path'); 4 | var cookieParser = require('cookie-parser'); 5 | var logger = require('morgan'); 6 | var fs = require('fs') 7 | var indexRouter = require('./routes/index'); 8 | var usersRouter = require('./routes/users'); 9 | 10 | var app = express(); 11 | var cors = require('cors') 12 | var multer = require('multer') 13 | const proxy = require('express-http-proxy'); 14 | require('dotenv').config() 15 | 16 | console.log("Backend Server Started.") 17 | // app.use(cors({credentials: false, origin: true})) 18 | app.use(cors()) 19 | app.options('*', cors()) 20 | app.use(function(req, res, next) { 21 | if (req.method === 'OPTIONS') { 22 | console.log('!OPTIONS'); 23 | res.header("Access-Control-Allow-Origin", "*"); 24 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 25 | res.header("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") 26 | res.header("Access-Control-Allow-Credentials", "true") 27 | res.writeHead(200, headers); 28 | res.end(); 29 | } 30 | next(); 31 | }); 32 | 33 | const bodyParser = require('body-parser') 34 | app.use(bodyParser.json()); 35 | // bodyParser.raw({ type: 'application/vnd.custom-type' }) 36 | 37 | app.use('/proxyget', function(req, res) { 38 | console.log(req) 39 | const headers = req.headers 40 | console.log("headers") 41 | console.log(headers) 42 | var url = headers['x-proxy-url'] 43 | var paiv_url = url + req.url; 44 | console.log("sending proxy get request to " + paiv_url) 45 | const options = { 46 | url: paiv_url, 47 | headers: headers 48 | } 49 | console.log("options") 50 | console.log(options) 51 | require('request').get(options).on('error', function(err) { 52 | console.error(err) 53 | }).pipe(res) 54 | }); 55 | 56 | // app.use(bodyParser.raw({ type: 'application/vnd.custom-type' })); 57 | 58 | 59 | 60 | app.set('views', path.join(__dirname, '')); 61 | app.use(require("express-form-data").parse()) 62 | // app.use(formData.stream()) 63 | app.use('/proxypost', function(req, res) { 64 | // console.log(upload) 65 | console.log(req) 66 | console.log("received post request") 67 | console.log("req.headers") 68 | console.log(req.headers) 69 | var url = req.headers['x-proxy-url'] 70 | console.log(`url: ${url}`) 71 | // console.log(`req.url: ${req.url}`) 72 | var paiv_url = url + req.url; 73 | console.log(`posting to ${paiv_url}`) 74 | // console.log(fs.createReadStream(req.files['files']['path'])) 75 | // console.log(req.files) 76 | var filePath = req.files['blob']['path'] 77 | // console.log("filePath") 78 | // console.log(filePath) 79 | var readStream = fs.createReadStream(filePath) 80 | console.log("readStream") 81 | console.log(readStream) 82 | var formData = { 83 | files: readStream, 84 | containHeatMap: "true" 85 | } 86 | // console.log("posting to " + paiv_url) 87 | // require('request').post({url: paiv_url}).pipe(res) 88 | require('request').post({url: paiv_url, formData: formData}).pipe(res) 89 | }); 90 | 91 | 92 | // view engine setup 93 | app.set('views', path.join(__dirname, 'views')); 94 | app.set('view engine', 'jade'); 95 | // var url = process.env.url 96 | // console.log("url") 97 | // console.log(url) 98 | // app.use('/proxy', proxy(url)); 99 | 100 | // app.use('/token', function(req, res) { 101 | // var url = req.url 102 | // console.log(url) 103 | // console.log(req) 104 | // console.log(`setting up proxy for ${url}`) 105 | // app.use('/proxy', proxy(url)) 106 | // }) 107 | 108 | 109 | 110 | app.use(logger('dev')); 111 | app.use(express.json()); 112 | app.use(express.urlencoded({ extended: false })); 113 | app.use(cookieParser()); 114 | app.use(express.static(path.join(__dirname, 'public'))); 115 | app.use('/', indexRouter); 116 | app.use('/users', usersRouter); 117 | 118 | // catch 404 and forward to error handler 119 | app.use(function(req, res, next) { 120 | next(createError(404)); 121 | }); 122 | 123 | // error handler 124 | app.use(function(err, req, res, next) { 125 | // set locals, only providing error in development 126 | res.locals.message = err.message; 127 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 128 | 129 | // render the error page 130 | res.status(err.status || 500); 131 | res.render('error'); 132 | }); 133 | 134 | module.exports = app; 135 | -------------------------------------------------------------------------------- /backend/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('react-backend:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '30000'); 16 | app.set('port', port); 17 | 18 | console.log("Vue Server Started") 19 | /** 20 | * Create HTTP server. 21 | */ 22 | 23 | var server = http.createServer(app); 24 | 25 | /** 26 | * Listen on provided port, on all network interfaces. 27 | */ 28 | 29 | server.listen(port); 30 | server.on('error', onError); 31 | server.on('listening', onListening); 32 | 33 | /** 34 | * Normalize a port into a number, string, or false. 35 | */ 36 | 37 | function normalizePort(val) { 38 | var port = parseInt(val, 10); 39 | 40 | if (isNaN(port)) { 41 | // named pipe 42 | return val; 43 | } 44 | 45 | if (port >= 0) { 46 | // port number 47 | return port; 48 | } 49 | 50 | return false; 51 | } 52 | 53 | /** 54 | * Event listener for HTTP server "error" event. 55 | */ 56 | 57 | function onError(error) { 58 | if (error.syscall !== 'listen') { 59 | throw error; 60 | } 61 | 62 | var bind = typeof port === 'string' 63 | ? 'Pipe ' + port 64 | : 'Port ' + port; 65 | 66 | // handle specific listen errors with friendly messages 67 | switch (error.code) { 68 | case 'EACCES': 69 | console.error(bind + ' requires elevated privileges'); 70 | process.exit(1); 71 | break; 72 | case 'EADDRINUSE': 73 | console.error(bind + ' is already in use'); 74 | process.exit(1); 75 | break; 76 | default: 77 | throw error; 78 | } 79 | } 80 | 81 | /** 82 | * Event listener for HTTP server "listening" event. 83 | */ 84 | 85 | function onListening() { 86 | var addr = server.address(); 87 | var bind = typeof addr === 'string' 88 | ? 'pipe ' + addr 89 | : 'port ' + addr.port; 90 | debug('Listening on ' + bind); 91 | } 92 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-backend", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "async": "2.6.1", 10 | "body-parser": "^1.19.0", 11 | "cookie-parser": "~1.4.3", 12 | "cors": "2.8.4", 13 | "debug": "~2.6.9", 14 | "dotenv": "^8.2.0", 15 | "express": "~4.16.0", 16 | "express-form-data": "^2.0.10", 17 | "express-http-proxy": "^1.6.0", 18 | "fabric-ca-client": "1.2.0", 19 | "fabric-client": "1.2.0", 20 | "fluent-ffmpeg": "^2.1.2", 21 | "grpc": "1.11.0", 22 | "http-errors": "~1.6.2", 23 | "jade": "~1.11.0", 24 | "morgan": "~1.9.0", 25 | "multer": "^1.4.2", 26 | "node-fetch": "^2.6.0", 27 | "nopt": "^4.0.1", 28 | "underscore": "x.x.x", 29 | "url": "^0.11.0", 30 | "vuetify": "^2.1.10" 31 | }, 32 | "devDependencies": { 33 | "vue-card": "^1.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /backend/routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const request = require('request') 4 | const hfc = require('fabric-client') 5 | const CAClient = require('fabric-ca-client') 6 | const fs = require('fs') 7 | const cors = require('cors') 8 | const _ = require('underscore') 9 | const util = require('util') 10 | const async = require('async') 11 | const exec = require('child_process').exec; 12 | const glob = require("glob") 13 | const path = require('path'); 14 | const os = require('os'); 15 | const fetch = require('node-fetch') 16 | const proxy = require('express-http-proxy'); 17 | const ffmpeg = require('fluent-ffmpeg'); 18 | 19 | require('dotenv').config() 20 | 21 | router.all('*', cors()) 22 | module.exports = router; 23 | 24 | // TODO, this line is temporary 25 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 26 | 27 | // Authenticate to powerai 28 | var username, password, url, token 29 | 30 | if ( process.env.user ) { 31 | username = process.env.user 32 | password = process.env.password 33 | url = process.env.url 34 | console.log(username, password, url) 35 | getToken(username, password, url) 36 | // token = getToken(username, password, url) 37 | } 38 | 39 | // var tokenRefreshTime = 40 | function getToken(username = undefined, password = undefined, url = undefined) { 41 | return new Promise( (resolve, reject) => { 42 | console.log("requesting auth token") 43 | var options = { 44 | method: "POST", 45 | headers: { 46 | "Content-Type": "application/json" 47 | }, 48 | body: JSON.stringify({ 49 | username: username, 50 | password: password, 51 | grant_type: "password" 52 | }) 53 | } 54 | console.log("token options") 55 | console.log(options) 56 | fetch( url + "/api/tokens", options ).then ( (r) => { 57 | console.log(r) 58 | r.json().then( (t) => { 59 | console.log("token received: " + t.token) 60 | token = t.token 61 | tokenRefreshTime = Date.now() 62 | resolve(t.token) 63 | } ) 64 | }).catch ( (err) => { 65 | console.log("error finding token") 66 | console.log(err) 67 | reject(err) 68 | }) 69 | }) 70 | } 71 | 72 | router.post('/token', function(req, res) { 73 | console.log("token: " + token) 74 | // if token hasn't expired yet 75 | if ((token) && (tokenRefreshTime) && ( Date.now() - 60*60*100 < tokenRefreshTime )) { 76 | console.log("token hasn't expired yet, sending exisiting token") 77 | res.send(token) 78 | // otherwise, get a new token 79 | } else { 80 | console.log("requesting new token") 81 | console.log(req.body) 82 | console.log(req.body.url) 83 | if (req.body.username && req.body.password) { 84 | console.log(`creds found in http request: ${req.body.username} ${req.body.password} ${req.body.url}`) 85 | username = req.body.username 86 | password = req.body.password 87 | getToken(req.body.username, req.body.password, req.body.url).then( (t) => { 88 | token = t 89 | console.log(t) 90 | res.send(token) 91 | } ) 92 | 93 | } else { 94 | // var token = getToken(username, password, url) 95 | // res.send(token) 96 | console.log("username / password not set") 97 | res.send(401) 98 | /* 99 | getToken(username, password, url).then( (t) => { 100 | token = t 101 | console.log(t) 102 | res.send(token) 103 | } ) 104 | */ 105 | } 106 | } 107 | // res.set('Content-Type', 'text/json') 108 | }); 109 | 110 | 111 | // app.use('/download', function(req, res) { 112 | // proxy(url) 113 | // // res.send(200) 114 | // }); 115 | 116 | var getInferences = function(url, token) { 117 | return new Promise( (resolve, reject) => { 118 | var options = { 119 | method: "GET", 120 | headers: { 121 | "X-Auth-Token": token 122 | } 123 | } 124 | console.log(`posting ${url}/api/inferences`) 125 | fetch( url + "/api/inferences", options ).then ( (r) => { 126 | // result = r 127 | // console.log(r) 128 | r.json().then( (e) => { 129 | console.log("inferences received") 130 | inferences = e 131 | console.log(e) 132 | resolve(inferences) 133 | // next get detailed inference info by id 134 | } ).catch((err) => { 135 | console.log("error parsing json") 136 | console.log(err) 137 | } ) 138 | }).catch( (err) => { 139 | console.log(err) 140 | }) 141 | }) 142 | } 143 | 144 | var datasets = [] 145 | var getDatasets = function() { 146 | // var datasets = [] 147 | var options = { 148 | method: "GET", 149 | headers: { 150 | "X-Auth-Token": token 151 | } 152 | } 153 | // return new Promise( (resolve, reject) => { 154 | 155 | fetch(url + "/api/datasets", options).then((r) => { 156 | // result = r 157 | // console.log(r) 158 | r.json().then((ds) => { 159 | console.log("datasets received") 160 | // resolve(inferences) 161 | ds.map((set, idx) => { 162 | fetch(`${url}/api/datasets/${set._id}/files`, options).then((r) => { 163 | console.log("receiving files from set " + set._id) 164 | r.json().then((files) => { 165 | console.log("parsed json") 166 | console.log(files) 167 | datasets = datasets.concat(files) 168 | if (idx == ds.length) { 169 | console.log('datasets') 170 | // console.log(datasets) 171 | resolve(datasets) 172 | } 173 | }) 174 | }) 175 | // datasets.push() 176 | 177 | }) 178 | // next get detailed inference info by id 179 | }).catch((err) => { 180 | console.log("error parsing json") 181 | console.log(err) 182 | }) 183 | }).catch((err) => { 184 | console.log(err) 185 | }) 186 | 187 | // }) // end promise 188 | 189 | // /datasets 190 | // /datasets/{id}/files 191 | } 192 | 193 | router.get('/inferences', function(req, res) { 194 | getInferences(req.headers['x-proxy-url'], req.headers['x-auth-token']).then( (i) => { 195 | // if no new inferences, keep vars as is 196 | // if (inferences.map.sort() == i.inferences_list.sort()) { 197 | // console.log("no new inferences, skipping update") 198 | // } else { 199 | inferences = i.inferences_list 200 | res.send(i.inferences_list) 201 | inferences.map( (i) => { 202 | // setTimeout(function(){ processInferences(i); }, 1000); 203 | processInferences(req.headers['x-proxy-url'], req.headers['x-auth-token'], i) // store result in an object, reference by id 204 | }) 205 | // } 206 | }) 207 | }); 208 | 209 | function sleep(ms) { 210 | return new Promise(resolve => setTimeout(resolve, ms)); 211 | } 212 | 213 | var checkInferences = function() { 214 | return new Promise( (resolve, reject) => { 215 | // 216 | var retries = 5 217 | var r = [...Array(retries).keys()] 218 | r.map((idx) => { 219 | setTimeout( () => { 220 | console.log("printing") 221 | }, 1000) 222 | }) 223 | // console.log(Object.keys(inferenceData).sort()) 224 | // console.log(Object.keys(inferences).sort()) 225 | // inferences.map(i => i._id) 226 | // sleep(500).then(() => { 227 | // if (Object.keys(inferenceData).sort() == Object.keys(inferences).sort()) { 228 | // resolve() 229 | // } else { 230 | // console.log("sleeping") 231 | // } 232 | // } 233 | // }) 234 | // if (idx == retries) { 235 | // reject() 236 | // } 237 | // }) 238 | 239 | }) 240 | } 241 | 242 | router.get('/inferencedetailed', function(req, res) { 243 | console.log("requesting detections for all inferences ") 244 | console.log(inferenceData) 245 | res.json(inferenceData) 246 | 247 | }); 248 | // Flow, 249 | // User loads main dashboard page 250 | // Request is sent to receive inferences 251 | // Process list of inferences 252 | // Once processing is done, return processed results to frontend 253 | 254 | 255 | // inference_id = "7afb7810-bdfa-4968-aafc-06a8bd758f5b" 256 | // 1. Split frames based off fps modulus 257 | // 2. Get array of count of each 258 | 259 | // var countObjects = function( ) { 260 | // 261 | // } 262 | process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0 263 | var inferenceData = {} // object to keep inference analytics 264 | var fps = {} 265 | var seconds = {} 266 | 267 | var getFPS = function(url, endpoint, id) { 268 | return new Promise ( (resolve, reject) => { 269 | // if (url) { 270 | console.log("ffprobe analyzing " + `${url}${endpoint}`) 271 | ffmpeg.ffprobe(url + endpoint, (err, data) => { 272 | var fps_reading = data.streams.filter(s => s.codec_type == 'video' )[0]['avg_frame_rate'] 273 | // var fps_reading = data['streams'][0]['avg_frame_rate'] 274 | var vid_length = data.streams.filter(s => s.codec_type == 'video' )[0]['duration'] 275 | var fps_nums = fps_reading.split('/') 276 | // inferenceData[det._id]['fps'] = fps_nums[0] / fps_nums[1] 277 | var fps_calc = Math.round(fps_nums[0] / fps_nums[1]) 278 | fps[id] = fps_calc 279 | seconds[id] = Math.round(vid_length) 280 | console.log(fps_calc) 281 | resolve(fps_calc) 282 | }) 283 | // ffmpeg.ffprobe(url, (err, data) => { 284 | // console.log(`data ${JSON.stringify(data)}`) 285 | // console.log(data['streams'][0]['avg_frame_rate']) 286 | // console.log(`err ${err}`) 287 | // }) 288 | // } else { 289 | // console.log("problem initializing ffprobe") 290 | // reject("No valid url provided") 291 | // } 292 | }) 293 | } 294 | 295 | var matchFrames = function(frameNumbers, seconds, framesPerSecond) { 296 | var matches = [] 297 | 298 | // [...Array(seconds).keys()].map((s) => { 299 | Array.from({length: seconds}, (x,i) => i).map((s) => { 300 | console.log("---------------------") 301 | var start = (framesPerSecond * s) 302 | console.log(`checking second ${s} at frame ${start}`) 303 | if (frameNumbers.includes(start)) { 304 | console.log("found matching frame in " + start) 305 | matches.push(start) 306 | // return start 307 | } else { 308 | for (i = start; i <= (start + framesPerSecond); i++) { 309 | // console.log("checking frame: " + i) 310 | if (frameNumbers.includes(i)) { 311 | console.log("found matching frame in " + i) 312 | matches.push(i) 313 | break 314 | // return i 315 | } 316 | if (i == (start + framesPerSecond)) { 317 | matches.push(start) 318 | break 319 | } 320 | } 321 | } 322 | }) 323 | // after loop completes 324 | return matches 325 | } 326 | 327 | 328 | // m = matchFrames(frames, seconds, framesPerSecond) 329 | 330 | 331 | var processInferences = function(url, token, i) { 332 | // var infDetails = JSON.parse(fs.readFileSync('../inference.json').toString()) 333 | // var d = JSON.parse(data) //JSON.parse(fs.readFileSync('../detections.json').toString()) 334 | // get detections.json 335 | // reduce detections, select frames by modulus 336 | var options = { 337 | method: "GET", 338 | headers: { 339 | "X-Auth-Token": token 340 | } 341 | } 342 | // request inference detections 343 | console.log("calling url") 344 | console.log(url + "/api/inferences/" + i['_id']) 345 | processing = true 346 | fetch( url + "/api/inferences/" + i['_id'], options ).then ( (r) => { 347 | // result = r 348 | console.log("r") 349 | console.log(JSON.stringify(r)) 350 | console.log("r.status") 351 | console.log(r.status) 352 | r.json().then((det) => { 353 | console.log(`detections received for inf ${det._id}`) 354 | var d = det.classified 355 | var totalFrames = det.total_frames 356 | var times = det.classified.map(f => f.time_offset) 357 | var allFrames = det.classified.map(f => f.frame_number) 358 | console.log(`allFrames ${allFrames}`) 359 | console.log("starting ffprobe") 360 | var frameNumbers = Array.from(new Set(det.classified.map(f => f.frame_number))).sort((a, b) => a - b) 361 | console.log(`frameNumbers ${frameNumbers}`) 362 | var endpoint = encodeURI(det.video_in) 363 | console.log(endpoint) 364 | getFPS(url, endpoint, det['_id']).then(() => { 365 | console.log("received fps") 366 | var framesPerSecond = fps[det._id] 367 | console.log("framesPerSecond") 368 | console.log(framesPerSecond) 369 | console.log(`allFrames ${allFrames}`) 370 | console.log(`seconds[det['_id']] ${seconds[det['_id']]}`) 371 | var m = matchFrames(frameNumbers, seconds[det['_id']], framesPerSecond) 372 | console.log(`m ${m}`) 373 | console.log(`m.length ${m.length}`) 374 | // after getting an array of frame numbers, select a frame number corresponding to each second. this is needed because paiv doesn't process every single frame, likely skips those that are very similar 375 | 376 | // var frameNumbers = [...new Set(reducedFrames.map(e => Number(e.frame_number)))].reverse() 377 | 378 | var reducedFrames = d.filter( e => e.frame_number % framesPerSecond == 0 ) 379 | var allFrames = d.map( e => e.frame_number) 380 | 381 | console.log("totalFrames " + String(totalFrames)) 382 | console.log("d length: " + String(d.length)) 383 | console.log("times length " + String(times.length)) 384 | console.log("reducedFrames.length") 385 | console.log(reducedFrames.length) 386 | 387 | // get unique classes 388 | var labels = [...new Set(reducedFrames.map(e => e.label))] 389 | // get list of unique frame numbers 390 | console.log("frameNumbers") 391 | console.log(frameNumbers) 392 | // get total number of objects found 393 | var totalObjectCount = [] 394 | m.map((num) => { 395 | totalObjectCount.push(reducedFrames.filter(f => f.frame_number == num).length) 396 | }) 397 | 398 | // get number of classes by frame number 399 | objectCount = {} // TODO, function should be async instead of using global 400 | labels.map((label, lIdx) => { 401 | objectCount[label] = [] 402 | m.map( (num, frameIdx) => { 403 | var numInstances = reducedFrames.filter(f => (f.frame_number == num && f.label == label )).length 404 | objectCount[label].push(numInstances) 405 | if ( (frameIdx == (m.length - 1 )) && (lIdx == (labels.length - 1)) ) { 406 | console.log("processing complete") 407 | console.log(objectCount) 408 | processing = false 409 | inferenceData[i['_id']] = objectCount 410 | // return objectCount 411 | } 412 | }) 413 | }) 414 | }) 415 | // var totalTime = (Math.max(...times) * .001) // in seconds 416 | // var proFrames = det.processed_frames 417 | // var framesPerSecond = Math.round(proFrames / totalTime) 418 | } ).catch((err) => { 419 | console.log("error parsing json") 420 | console.log(err) 421 | } ) 422 | }).catch( (err) => { 423 | console.log(err) 424 | }) 425 | 426 | 427 | } 428 | 429 | // get detections from powerai with 430 | // powerai-vision-ny/api/inferences/7afb7810-bdfa-4968-aafc-06a8bd758f5b 431 | 432 | // Global dashboard view 433 | // List of inferences 434 | // List of all pic/vid thumbnails 435 | 436 | 437 | 438 | // Detailed View 439 | // Get and process detections for a given inference 440 | // Should give a response to render the following 441 | // Draw a graph (line, circle) 442 | // 443 | 444 | router.get('/inference/:infId', function(req, res) { 445 | console.log("requesting detections for id: " + req.params.infId) 446 | if ( req.params.infId in inferenceData.keys() ) { 447 | res.send(inferenceData[req.params.infId]) 448 | } else { 449 | res.send(404) 450 | } 451 | // if (req.user && req.password) { 452 | /* 453 | var options = { 454 | method: "GET", 455 | headers: { 456 | "X-Auth-Token": token 457 | } 458 | } 459 | fetch( url + "/api/inferences/" + id, options ).then ( (r) => { 460 | // result = r 461 | // console.log(r) 462 | r.json().then( (e) => { 463 | console.log("inference") 464 | inferences = e 465 | console.log(e) 466 | res.send(inferences) 467 | } ).catch((err) => { 468 | console.log("error parsing json") 469 | console.log(err) 470 | } ) 471 | }).catch( (err) => { 472 | console.log(err) 473 | } )*/ 474 | }); 475 | 476 | 477 | /* 478 | router.post('/api/chaincode', function(req, res) { 479 | console.log("chaincode request received") 480 | console.log(req.body) 481 | var chaincode = req.body.params.ctorMsg 482 | var chaincode_query = JSON.stringify({ 483 | "Args": [chaincode.function].concat(chaincode.args) 484 | }) 485 | if (typeof(client) !== 'undefined') { 486 | console.log("invoking chaincode with hfc client") 487 | console.log("req") 488 | console.log(req.body) 489 | console.log("req.body.method") 490 | console.log(req.body.method) 491 | if (req.body.method && req.body.method.includes('invoke')) { 492 | console.log("invoking request") 493 | var transaction_id = client.newTransactionID(true) 494 | var txRequest = { 495 | chaincodeId: sec_chaincode.name, 496 | chaincodeVersion: sec_chaincode.version, 497 | txId: transaction_id, 498 | fcn: req.body.params.ctorMsg.function, 499 | args: req.body.params.ctorMsg.args 500 | } 501 | console.log(txRequest) 502 | var txResult = proposeAndSubmitTransaction(txRequest) 503 | res.send(200) 504 | } else { 505 | console.log("querying chaincode with hfc client") 506 | var txRequest = { 507 | chaincodeId: sec_chaincode.name, 508 | chaincodeVersion: sec_chaincode.version, 509 | fcn: req.body.params.ctorMsg.function, 510 | args: req.body.params.ctorMsg.args 511 | } 512 | console.log("txRequest") 513 | console.log(txRequest) 514 | channel.queryByChaincode(txRequest).then((cc_response) => { 515 | console.log("cc query response received") 516 | console.log(cc_response[0].toString()) 517 | res.json(cc_response[0].toString()) 518 | }).catch((err) => { 519 | console.log("cc query failed") 520 | console.log(err) 521 | res.json(err) 522 | }) 523 | } 524 | } 525 | 526 | }); 527 | 528 | function submitTransaction(txRequest) { 529 | console.log(util.format('Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s"', proposalResponses[0].response.status, proposalResponses[0].response.message)); 530 | var promises = [] 531 | var sendPromise = channel.sendTransaction({ 532 | proposalResponses: proposalResponses, 533 | proposal: proposal 534 | }) 535 | sendPromise.then((result) => { 536 | console.log("transaction result") 537 | console.log(result) 538 | res.send(result) 539 | }) 540 | } 541 | 542 | function proposeAndSubmitTransaction(txRequest) { 543 | console.log("sending transaction proposal") 544 | channel.sendTransactionProposal(txRequest).then((proposalRes) => { 545 | console.log("response received") 546 | var proposalResponses = proposalRes[0]; 547 | var proposal = proposalRes[1]; 548 | let isProposalGood = false; 549 | console.log("proposalResponses[0].response") 550 | console.log(proposalResponses[0].response) 551 | if (proposalResponses && proposalResponses[0].response && proposalResponses[0].response.status === 200) { 552 | console.log('Transaction proposal was accepted'); 553 | channel.sendTransaction({ 554 | proposalResponses: proposalResponses, 555 | proposal: proposal 556 | }).then((res) => { 557 | console.log("Transaction result was accepted") 558 | return true 559 | }) 560 | } else { 561 | console.log('Transaction proposal was rejected'); 562 | return false 563 | } 564 | }).catch((err) => { 565 | return false 566 | console.log(err) 567 | }); 568 | } 569 | 570 | function uploadAdminCert(req, mspId) { 571 | var uploadAdminCertReq = { 572 | "msp_id": mspId, 573 | "adminCertName": "admin_cert" + Math.floor(Math.random() * 1000), 574 | "adminCertificate": user._signingIdentity._certificate, 575 | "peer_names": Object.keys(client._network_config._network_config.peers), 576 | "SKIP_CACHE": true 577 | } 578 | if (! req.body.api_endpoint.includes('/api/v1')) { 579 | var api_endpoint = req.body.api_endpoint + '/api/v1' 580 | } else { 581 | var api_endpoint = req.body.api_endpoint 582 | } 583 | var options = { 584 | url: api_endpoint + '/networks/' + req.body.network_id + '/certificates', 585 | method: 'POST', 586 | headers: { 587 | 'Accept': 'application/json', 588 | 'Content-Type': 'application/json', 589 | 'Accept-Charset': 'utf-8', 590 | "Authorization": "Basic " + new Buffer(req.body.key + ":" + req.body.secret, "utf8").toString("base64") 591 | }, 592 | body: uploadAdminCertReq 593 | } 594 | console.log("uploading admin cert") 595 | request(options, function(err, res, body) { 596 | console.log("res") 597 | console.log(res) 598 | if (err) { 599 | console.log(err) 600 | } 601 | }) 602 | } 603 | */ 604 | -------------------------------------------------------------------------------- /backend/routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res, next) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | 11 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # hello-world 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /frontend/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var serveStatic = require('serve-static'); 4 | app = express(); 5 | // app.use(serveStatic(__dirname + "/dist")); 6 | app.use(serveStatic(__dirname + "/index.html")); 7 | var port = process.env.PORT || 30000; 8 | var hostname = '127.0.0.1'; 9 | 10 | app.listen(port, hostname, () => { 11 | console.log(`Server running at http://${hostname}:${port}/`); 12 | }); 13 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | My first Vue app 5 | 6 | 7 | 8 |
9 | {{ message }} 10 |
11 | 12 |
13 |

{{ message }}

14 | 15 | 16 | 17 |
18 | 19 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "body-parser": "^1.19.0", 12 | "chart.js": "^2.9.3", 13 | "jszip": "^3.2.2", 14 | "node-sass": "^4.11.0", 15 | "sass-loader": "^7.1.0", 16 | "v-data-table": "^2.1.0", 17 | "vfc": "^1.1.2", 18 | "vue": "^2.6.6", 19 | "vue-card-element": "^0.1.2", 20 | "vue-chartjs": "^3.5.0", 21 | "vue-js-modal": "^1.3.28", 22 | "vue-json-tree-view": "^2.1.4", 23 | "vue-material": "^1.0.0-beta-11", 24 | "vue-plotly": "^1.0.1", 25 | "vue-router": "^3.1.3", 26 | "vue-select": "^2.6.4", 27 | "vue-upload-component": "^2.8.20" 28 | }, 29 | "devDependencies": { 30 | "@nuxtjs/vuetify": "^1.9.0", 31 | "@vue/cli-plugin-babel": "^3.5.0", 32 | "@vue/cli-plugin-eslint": "^3.5.0", 33 | "@vue/cli-service": "^3.5.0", 34 | "babel-eslint": "^10.0.1", 35 | "eslint": "^5.8.0", 36 | "eslint-plugin-vue": "^5.0.0", 37 | "sass": "^1.19.0", 38 | "sass-loader": "^8.0.0", 39 | "vue-cli-plugin-vuetify": "^2.0.2", 40 | "vue-template-compiler": "^2.5.21", 41 | "vuetify-loader": "^1.3.0" 42 | }, 43 | "eslintConfig": { 44 | "root": true, 45 | "env": { 46 | "node": true 47 | }, 48 | "extends": [ 49 | "plugin:vue/essential", 50 | "eslint:recommended" 51 | ], 52 | "rules": {}, 53 | "parserOptions": { 54 | "parser": "babel-eslint" 55 | } 56 | }, 57 | "postcss": { 58 | "plugins": { 59 | "autoprefixer": {} 60 | } 61 | }, 62 | "browserslist": [ 63 | "> 1%", 64 | "last 2 versions", 65 | "not ie <= 8" 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | PowerAI Dashboard 9 | 10 | 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-console': 'off', 4 | // "no-mixed-spaces-and-tabs": 0, 5 | // "vue/no-unused-components": 'off', 6 | // "vue/no-parsing-error": 'off' 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | Artboard 46 2 | -------------------------------------------------------------------------------- /frontend/src/components/DetailedInferenceView.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 327 | -------------------------------------------------------------------------------- /frontend/src/components/Drag.vue: -------------------------------------------------------------------------------- 1 | 55 | 88 | 89 | 103 | -------------------------------------------------------------------------------- /frontend/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 145 | -------------------------------------------------------------------------------- /frontend/src/components/Modal.vue: -------------------------------------------------------------------------------- 1 | 43 | 679 | 680 | 764 | -------------------------------------------------------------------------------- /frontend/src/dist/json-tree.css: -------------------------------------------------------------------------------- 1 | .tree-view-item { 2 | font-family: monospace; 3 | font-size: 14px; 4 | margin-left: 18px; 5 | } 6 | 7 | .tree-view-wrapper { 8 | overflow: auto; 9 | } 10 | 11 | /* Find the first nested node and override the indentation */ 12 | .tree-view-item-root > .tree-view-item-leaf > .tree-view-item { 13 | margin-left: 0; 14 | } 15 | 16 | /* Root node should not be indented */ 17 | .tree-view-item-root { 18 | margin-left: 0; 19 | } 20 | 21 | .tree-view-item-node { 22 | cursor: pointer; 23 | position: relative; 24 | white-space: nowrap; 25 | } 26 | 27 | .tree-view-item-leaf { 28 | white-space: nowrap; 29 | } 30 | 31 | .tree-view-item-key { 32 | font-weight: bold; 33 | } 34 | 35 | .tree-view-item-key-with-chevron { 36 | padding-left: 14px; 37 | } 38 | 39 | 40 | .tree-view-item-key-with-chevron.opened::before { 41 | top:4px; 42 | transform: rotate(90deg); 43 | -webkit-transform: rotate(90deg); 44 | } 45 | 46 | .tree-view-item-key-with-chevron::before { 47 | color: #444; 48 | content: '\25b6'; 49 | font-size: 10px; 50 | left: 1px; 51 | position: absolute; 52 | top: 3px; 53 | transition: -webkit-transform .1s ease; 54 | transition: transform .1s ease; 55 | transition: transform .1s ease, -webkit-transform .1s ease; 56 | -webkit-transition: -webkit-transform .1s ease; 57 | } 58 | 59 | .tree-view-item-hint { 60 | color: #ccc 61 | } 62 | body { 63 | font-family: Menlo, Consolas, monospace; 64 | color: #444; 65 | } 66 | .item { 67 | cursor: pointer; 68 | } 69 | .bold { 70 | font-weight: bold; 71 | } 72 | ul { 73 | padding-left: 1em; 74 | line-height: 1.5em; 75 | list-style-type: dot; 76 | } 77 | th.active .arrow.asc { 78 | border-bottom: 4px solid #4d4d4d; 79 | } 80 | 81 | th.active .arrow.dsc { 82 | border-top: 4px solid #4d4d4d; 83 | } 84 | 85 | .arrow { 86 | display: inline-block; 87 | vertical-align: middle; 88 | width: 0; 89 | height: 0; 90 | margin-left: 5px; 91 | } 92 | 93 | .arrow.asc { 94 | border-left: 4px solid transparent; 95 | border-right: 4px solid transparent; 96 | border-bottom: 4px solid #cdc; 97 | } 98 | 99 | .arrow.dsc { 100 | border-left: 4px solid transparent; 101 | border-right: 4px solid transparent; 102 | border-top: 4px solid #cdc; 103 | } 104 | table { 105 | border: 1px solid #cdc; 106 | text-indent: 15px; 107 | margin: 50px; 108 | display: inline-block; 109 | text-align: center; 110 | 111 | } 112 | /* table thead th { border-bottom: 1px solid #000; } */ 113 | 114 | th { 115 | /* text-indent: 15px; */ 116 | text-align: center; 117 | /* align:left; */ 118 | /* padding: 10px 0; */ 119 | } 120 | 121 | tr { 122 | border-bottom: 4px solid #cdc; 123 | border-bottom: 1px solid #000; 124 | } 125 | td { 126 | padding: 15px 0; 127 | /* padding-right: 40px; */ 128 | text-align: center; 129 | 130 | } 131 | -------------------------------------------------------------------------------- /frontend/src/dist/vfc.css: -------------------------------------------------------------------------------- 1 | .vue-input{display:inline-block;font-family:Helvetica,Arial,sans-serif}.vue-input,.vue-input__inner{position:relative;font-size:14px;width:100%}.vue-input__inner{-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;border-radius:4px;outline:none;border:1px solid #ddd;line-height:40px;padding:0 15px;height:40px;background-color:transparent;color:#303133}.vue-input__inner,.vue-input__inner:focus{-webkit-transition:border-color .3s;transition:border-color .3s}.vue-input__inner:focus{border-color:#498aed}.vue-input__inner::-webkit-input-placeholder{color:#a2a3a7}.vue-input__inner[disabled]{cursor:no-drop;background-color:#f6f6f6}.vue-input__inner[type=number]::-webkit-inner-spin-button,.vue-input__inner[type=number]::-webkit-outer-spin-button{-webkit-appearance:none}.vue-input__prefix,.vue-input__suffix{color:#303133;position:absolute;top:0;bottom:0;width:40px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;z-index:10}.vue-input__prefix{left:0}.vue-input__suffix{right:0}.vue-input__prepend{border-left:1px solid #ddd;border-top:1px solid #ddd;border-bottom:1px solid #ddd;border-top-left-radius:4px;border-bottom-left-radius:4px}.vue-input__append,.vue-input__prepend{display:table-cell;color:#7a7d82;padding:0 15px;background-color:#f6f6f6;position:relative;width:1px;white-space:nowrap}.vue-input__append{border-top:1px solid #ddd;border-right:1px solid #ddd;border-bottom:1px solid #ddd;border-top-right-radius:4px;border-bottom-right-radius:4px}.vue-input--prefix .vue-input__inner{padding-left:40px}.vue-input--suffix .vue-input__inner{padding-right:40px}.vue-input--prepend{display:inline-table;border-collapse:separate}.vue-input--prepend .vue-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.vue-input--append{display:inline-table;border-collapse:separate}.vue-input--append .vue-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.vue-textarea__inner{-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;border-radius:4px;outline:none;border:1px solid #ddd;line-height:40px;padding:0 15px;height:40px;background-color:transparent;font-size:14px;color:#303133;padding:5px 15px;height:auto;line-height:1.5;resize:vertical}.vue-textarea__inner,.vue-textarea__inner:focus{-webkit-transition:border-color .3s;transition:border-color .3s}.vue-textarea__inner:focus{border-color:#498aed}.vue-textarea__inner::-webkit-input-placeholder{color:#a2a3a7}.vue-textarea__inner[disabled]{cursor:no-drop;background-color:#f6f6f6}.vue-button{position:relative;display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;-webkit-appearance:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px;-webkit-transition:all .3s;transition:all .3s;background-color:#fff;border-color:#fff;color:#303133;border:1px solid #ddd}.vue-button:hover{background-color:#fff}.vue-button:active{background-color:#f2f2f2}.vue-button[disabled]{background-color:#f6f6f6;border-color:#f6f6f6;cursor:no-drop}.vue-button+.vue-button{margin-left:10px}.vue-button--primary{background-color:#498aed;border-color:#498aed;color:#fff}.vue-button--primary:hover{background-color:#6099ef}.vue-button--primary:active{background-color:#327beb}.vue-button--primary[disabled]{background-color:#8fb7f4;border-color:#8fb7f4;cursor:no-drop}.vue-button--success{background-color:#68cc68;border-color:#68cc68;color:#fff}.vue-button--success:hover{background-color:#7bd27b}.vue-button--success:active{background-color:#55c655}.vue-button--success[disabled]{background-color:#a1dfa1;border-color:#a1dfa1;cursor:no-drop}.vue-button--warning{background-color:#efc669;border-color:#efc669;color:#fff}.vue-button--warning:hover{background-color:#f1cf80}.vue-button--warning:active{background-color:#edbd52}.vue-button--warning[disabled]{background-color:#f4d897;border-color:#f4d897;cursor:no-drop}.vue-button--danger{background-color:#e4474e;border-color:#e4474e;color:#fff}.vue-button--danger:hover{background-color:#e75d63}.vue-button--danger:active{background-color:#e13139}.vue-button--danger[disabled]{background-color:#f4b6b9;border-color:#f4b6b9;cursor:no-drop}.vue-checkbox{font-size:14px;cursor:pointer;display:inline-table;color:#303133}.vue-checkbox+.vue-checkbox{margin-left:10px}.vue-checkbox--checked{color:#498aed}.vue-checkbox--checked .vue-checkbox__inner{background-color:#498aed;border-color:#498aed}.vue-checkbox--checked.vue-checkbox--bordered{border-color:#498aed}.vue-checkbox--checked.vue-checkbox--disabled .vue-checkbox__inner{border-color:#aaa}.vue-checkbox--checked.vue-checkbox--disabled i{color:#aaa}.vue-checkbox--disabled{cursor:no-drop}.vue-checkbox--disabled .vue-checkbox__inner{background-color:#f6f6f6;cursor:no-drop}.vue-checkbox--disabled .vue-checkbox__label{color:#aaa}.vue-checkbox--bordered{border:1px solid #ddd;border-radius:4px;padding:0 15px;line-height:38px;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:all .2s;transition:all .2s}.vue-checkbox:last-of-type{margin-right:0}.vue-checkbox__label{display:table-cell;width:100%;line-height:1.3em}.vue-checkbox__inner{top:3px;width:14px;height:14px;margin-right:5px;border:1px solid #ddd;border-radius:3px;position:relative;cursor:pointer;display:inline-block;margin-right:10px}.vue-checkbox__inner i{position:absolute;color:#fff;left:0}.vue-checkbox input{display:none}.vue-radio{font-size:14px;cursor:pointer;color:#303133}.vue-radio+.vue-radio{margin-left:10px}.vue-radio__inner{width:16px;height:16px;border-radius:100%;cursor:pointer;display:inline-block;border:1px solid #ddd;margin-right:10px;position:relative;top:2px;-webkit-box-sizing:border-box;box-sizing:border-box}.vue-radio--checked .vue-radio__inner{background-color:#498aed;border-color:#498aed}.vue-radio--checked .vue-radio__inner:after{position:absolute;content:"";width:6px;height:6px;background-color:#fff;border-radius:100%;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.vue-radio--checked.vue-radio--bordered{border-color:#498aed}.vue-radio--disabled{color:#aaa;cursor:no-drop}.vue-radio--disabled .vue-radio__inner{background-color:#f6f6f6;border-color:#aaa}.vue-radio--disabled .vue-radio__inner:after{background-color:#ddd}.vue-radio--bordered{border-color:#498aed;border:1px solid #ddd;border-radius:4px;padding:0 15px;line-height:38px;height:38px;-webkit-transition:all .2s;transition:all .2s;display:inline-block}.vue-radio input{display:none}.vue-popper{background-color:#fff;border-radius:4px;border:1px solid #fff;font-family:Arial,Helvetica,sans-serif;-webkit-box-shadow:0 0 10px rgba(0,0,0,.1);box-shadow:0 0 10px rgba(0,0,0,.1);z-index:1010}.vue-popper,.vue-popper *{-webkit-box-sizing:border-box;box-sizing:border-box}.vue-popper__inner{padding:10px;overflow-y:auto}.vue-popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0,0,0,.03));filter:drop-shadow(0 2px 12px rgba(0,0,0,.03))}.vue-popper__arrow,.vue-popper__arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.vue-popper__arrow:after{content:" "}.vue-popper[x-placement^=bottom]{margin-top:5px}.vue-popper[x-placement^=bottom] .vue-popper__arrow{top:-6px;left:50%!important;-webkit-transform:translateX(-50%);transform:translateX(-50%);margin-right:3px;border-top-width:0;border-bottom-color:#fff}.vue-popper[x-placement^=bottom] .vue-popper__arrow:after{top:1px;border-top-width:1px;margin-top:0;margin-left:-3px;border-color:transparent;border-style:solid;-webkit-transform:scale(1.5);transform:scale(1.5);border-bottom-color:#fff}.vue-popper[x-placement^=bottom-start]{margin-top:5px}.vue-popper[x-placement^=bottom-start] .vue-popper__arrow{top:-6px;left:10px!important;margin-right:3px;border-top-width:0;border-bottom-color:#fff}.vue-popper[x-placement^=bottom-start] .vue-popper__arrow:after{top:1px;border-top-width:1px;margin-top:0;margin-left:-3px;border-color:transparent;border-style:solid;-webkit-transform:scale(1.5);transform:scale(1.5);border-bottom-color:#fff}.vue-popper[x-placement^=bottom-end]{margin-top:5px}.vue-popper[x-placement^=bottom-end] .vue-popper__arrow{top:-6px;left:calc(100% - 10px)!important;margin-right:3px;border-top-width:0;border-bottom-color:#fff}.vue-popper[x-placement^=bottom-end] .vue-popper__arrow:after{top:1px;border-top-width:1px;margin-top:0;margin-left:-3px;border-color:transparent;border-style:solid;-webkit-transform:scale(1.5);transform:scale(1.5);border-bottom-color:#fff}.vue-popper[x-placement^=top]{margin:0;margin-bottom:6px}.vue-popper[x-placement^=top] .vue-popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-bottom-width:0;border-top-color:#fff}.vue-popper[x-placement^=top] .vue-popper__arrow:after{bottom:1px;margin-right:3px;border-bottom-width:1px;margin-top:-6px;margin-left:-3px;-webkit-transform:scale(1.5);transform:scale(1.5);border-top-color:#fff}.vue-popper[x-placement^=top-start]{margin:0;margin-bottom:6px}.vue-popper[x-placement^=top-start] .vue-popper__arrow{bottom:-6px;left:10px!important;margin-right:3px;border-bottom-width:0;border-top-color:#fff}.vue-popper[x-placement^=top-start] .vue-popper__arrow:after{bottom:1px;margin-right:3px;border-bottom-width:1px;margin-top:-6px;margin-left:-3px;-webkit-transform:scale(1.5);transform:scale(1.5);border-top-color:#fff}.vue-popper[x-placement^=top-end]{margin:0;margin-bottom:6px}.vue-popper[x-placement^=top-end] .vue-popper__arrow{bottom:-6px;left:calc(100% - 15px)!important;margin-right:3px;border-bottom-width:0;border-top-color:#fff}.vue-popper[x-placement^=top-end] .vue-popper__arrow:after{bottom:1px;margin-right:3px;border-bottom-width:1px;margin-top:-6px;margin-left:-3px;-webkit-transform:scale(1.5);transform:scale(1.5);border-top-color:#fff}@font-face{font-family:icomoon;src:url(data:font/ttf;base64,AAEAAAALAIAAAwAwT1MvMg8SBawAAAC8AAAAYGNtYXAXVtKJAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5ZtfZZSAAAAF4AAABYGhlYWQScJwaAAAC2AAAADZoaGVhBzUDyAAAAxAAAAAkaG10eBIAAlEAAAM0AAAAHGxvY2EAmgEMAAADUAAAABBtYXhwAAkAKQAAA2AAAAAgbmFtZZlKCfsAAAOAAAABhnBvc3QAAwAAAAAFCAAAACAAAwOAAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADpAgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6QL//f//AAAAAAAg6QD//f//AAH/4xcEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAI0AqwNzAskAFgAAASYiBwEnJiIHBhQfAR4BMzI2NwE2NCcDcw0iDf5Jtw0iDQ0N1QcNCgoNBwHVDQ0CyQwM/ki4DAwNIg3WBgYGBgHWDSINAAABAOIBAAMeAkkAFgAAASYiDwEnJiIHBhQXAR4BMzI2NwE2NCcDHg0iDeLiDSINDQ0BAAcRBgYRBwEADQ0CSQwM4+MMDA0iDf8ABwYGBwEADSINAAABAOIAgAMeAskAJgAAATc2NCcmIg8BJyYiBwYUHwEHBhQXHgEzMjY/ARceATMyNjc2NC8BAjziDQ0NIg3i4g0iDQ0N4uINDQcNCgoNB+LiBxEGBhEHDQ3iAaviDSINDAzj4wwMDSIN4uINIg0HBgYH4uIHBgYHDSIN4gAAAQAAAAEAALaGQp9fDzz1AAsEAAAAAADX76wqAAAAANfvrCoAAAAAA3MCyQAAAAgAAgAAAAAAAAABAAADwP/AAAAEAAAAAAADcwABAAAAAAAAAAAAAAAAAAAABwQAAAAAAAAAAAAAAAIAAAAEAACNBAAA4gQAAOIAAAAAAAoAFAAeAEgAcgCwAAEAAAAHACcAAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAHAAAAAQAAAAAAAgAHAGAAAQAAAAAAAwAHADYAAQAAAAAABAAHAHUAAQAAAAAABQALABUAAQAAAAAABgAHAEsAAQAAAAAACgAaAIoAAwABBAkAAQAOAAcAAwABBAkAAgAOAGcAAwABBAkAAwAOAD0AAwABBAkABAAOAHwAAwABBAkABQAWACAAAwABBAkABgAOAFIAAwABBAkACgA0AKRpY29tb29uAGkAYwBvAG0AbwBvAG5WZXJzaW9uIDEuMABWAGUAcgBzAGkAbwBuACAAMQAuADBpY29tb29uAGkAYwBvAG0AbwBvAG5pY29tb29uAGkAYwBvAG0AbwBvAG5SZWd1bGFyAFIAZQBnAHUAbABhAHJpY29tb29uAGkAYwBvAG0AbwBvAG5Gb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format("truetype"),url(data:application/font-woff;base64,d09GRgABAAAAAAV0AAsAAAAABSgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgDxIFrGNtYXAAAAFoAAAAVAAAAFQXVtKJZ2FzcAAAAbwAAAAIAAAACAAAABBnbHlmAAABxAAAAWAAAAFg19llIGhlYWQAAAMkAAAANgAAADYScJwaaGhlYQAAA1wAAAAkAAAAJAc1A8hobXR4AAADgAAAABwAAAAcEgACUWxvY2EAAAOcAAAAEAAAABAAmgEMbWF4cAAAA6wAAAAgAAAAIAAJACluYW1lAAADzAAAAYYAAAGGmUoJ+3Bvc3QAAAVUAAAAIAAAACAAAwAAAAMDgAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA6QIDwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADgAAAAKAAgAAgACAAEAIOkC//3//wAAAAAAIOkA//3//wAB/+MXBAADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQCNAKsDcwLJABYAAAEmIgcBJyYiBwYUHwEeATMyNjcBNjQnA3MNIg3+SbcNIg0NDdUHDQoKDQcB1Q0NAskMDP5IuAwMDSIN1gYGBgYB1g0iDQAAAQDiAQADHgJJABYAAAEmIg8BJyYiBwYUFwEeATMyNjcBNjQnAx4NIg3i4g0iDQ0NAQAHEQYGEQcBAA0NAkkMDOPjDAwNIg3/AAcGBgcBAA0iDQAAAQDiAIADHgLJACYAAAE3NjQnJiIPAScmIgcGFB8BBwYUFx4BMzI2PwEXHgEzMjY3NjQvAQI84g0NDSIN4uINIg0NDeLiDQ0HDQoKDQfi4gcRBgYRBw0N4gGr4g0iDQwM4+MMDA0iDeLiDSINBwYGB+LiBwYGBw0iDeIAAAEAAAABAAC2hkKfXw889QALBAAAAAAA1++sKgAAAADX76wqAAAAAANzAskAAAAIAAIAAAAAAAAAAQAAA8D/wAAABAAAAAAAA3MAAQAAAAAAAAAAAAAAAAAAAAcEAAAAAAAAAAAAAAACAAAABAAAjQQAAOIEAADiAAAAAAAKABQAHgBIAHIAsAABAAAABwAnAAEAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEABwAAAAEAAAAAAAIABwBgAAEAAAAAAAMABwA2AAEAAAAAAAQABwB1AAEAAAAAAAUACwAVAAEAAAAAAAYABwBLAAEAAAAAAAoAGgCKAAMAAQQJAAEADgAHAAMAAQQJAAIADgBnAAMAAQQJAAMADgA9AAMAAQQJAAQADgB8AAMAAQQJAAUAFgAgAAMAAQQJAAYADgBSAAMAAQQJAAoANACkaWNvbW9vbgBpAGMAbwBtAG8AbwBuVmVyc2lvbiAxLjAAVgBlAHIAcwBpAG8AbgAgADEALgAwaWNvbW9vbgBpAGMAbwBtAG8AbwBuaWNvbW9vbgBpAGMAbwBtAG8AbwBuUmVndWxhcgBSAGUAZwB1AGwAYQByaWNvbW9vbgBpAGMAbwBtAG8AbwBuRm9udCBnZW5lcmF0ZWQgYnkgSWNvTW9vbi4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format("woff");font-weight:400;font-style:normal}[class*=" icon-"],[class^=icon-]{font-family:icomoon!important;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-check:before{content:"\E900"}.icon-chevron-down:before{content:"\E901"}.icon-close:before{content:"\E902"}.vue-select{display:inline-block;cursor:pointer;position:relative;width:100%}.vue-select__tag{position:absolute;top:0;left:0;right:0;bottom:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:2px 40px 2px 5px;z-index:10}.vue-select__tag-item{-webkit-box-sizing:border-box;box-sizing:border-box;font-size:14px;background-color:#f6f6f6;border:1px solid #ddd;border-radius:4px;color:#303133;padding:2px 5px;-ms-flex-negative:0;flex-shrink:0;margin:2px;display:inline-block}.vue-select__tag-item>i{position:relative;top:1px;color:#aaa}.vue-select__tag-item>i:hover{color:#303133}.vue-select__option-list-empty{font-size:14px}.vue-select .vue-input__inner{cursor:pointer}.vue-select .vue-input i{-webkit-transition:all .2s;transition:all .2s}.vue-select--opened .vue-input i{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.vue-select--disabled .vue-input__suffix i{color:#aaa}.vue-select--disabled .vue-input__inner{color:#aaa;cursor:no-drop!important}.vue-select__option{cursor:pointer;margin:0 -10px;padding:10px 15px;font-size:14px}.vue-select__option[disabled]{cursor:no-drop!important;color:#ddd}.vue-select__option--hovered{background-color:#f6f6f6}.vue-select__option--hovered[disabled]{background-color:inherit}.vue-select__option--selected{color:#498aed}.vue-select__option--selected:hover{background-color:#f6f6f6}.vue-form{width:400px}.vue-form--label-right .vue-form__item-label{text-align:right}.vue-form--label-top .vue-form__item{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column;flex-flow:column}.vue-form--label-top .vue-form__item-label{-ms-flex-preferred-size:auto!important;flex-basis:auto!important;-ms-flex-item-align:start;align-self:flex-start;margin-bottom:15px}.vue-form__item{margin-bottom:22px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;position:relative}.vue-form__item-label{-webkit-box-sizing:border-box;box-sizing:border-box;font-size:14px;color:#303133;padding-right:20px;-ms-flex-item-align:center;align-self:center}.vue-form__item-content{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.vue-form__item-error{position:absolute;font-size:12px;color:#e4474e;line-height:20px}.vue-form__item-validate{overflow:hidden}.form-slide-fade-enter-active,.form-slide-fade-leave-active{-webkit-transition:all .2s;transition:all .2s}.form-slide-fade-enter,.form-slide-fade-leave-to{-webkit-transform:translateY(-10px);transform:translateY(-10px);opacity:0} 2 | -------------------------------------------------------------------------------- /frontend/src/dist/vuetable-2.css: -------------------------------------------------------------------------------- 1 | /** 2 | * vuetable-2 v1.6.0 3 | * https://github.com/ratiw/vuetable-2 4 | * Released under the MIT License. 5 | */ 6 | 7 | [v-cloak][_v-0c70c9de]{display:none}.vuetable th.sortable[_v-0c70c9de]:hover{color:#2185d0;cursor:pointer}.vuetable-actions[_v-0c70c9de]{width:15%;padding:12px 0;text-align:center}.vuetable-pagination[_v-0c70c9de]{background:#f9fafb!important}.vuetable-pagination-info[_v-0c70c9de]{margin-top:auto;margin-bottom:auto}.vuetable-empty-result[_v-0c70c9de]{text-align:center} 8 | -------------------------------------------------------------------------------- /frontend/src/dist/vuetable-2.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Vuetable=e():t.Vuetable=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="/",e(e.s=76)}([function(t,e,n){"use strict";function r(t){return"[object Array]"===C.call(t)}function i(t){return"[object ArrayBuffer]"===C.call(t)}function o(t){return"undefined"!=typeof FormData&&t instanceof FormData}function a(t){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(t):t&&t.buffer&&t.buffer instanceof ArrayBuffer}function s(t){return"string"==typeof t}function c(t){return"number"==typeof t}function l(t){return void 0===t}function u(t){return null!==t&&"object"==typeof t}function f(t){return"[object Date]"===C.call(t)}function d(t){return"[object File]"===C.call(t)}function h(t){return"[object Blob]"===C.call(t)}function p(t){return"[object Function]"===C.call(t)}function v(t){return u(t)&&p(t.pipe)}function m(t){return"undefined"!=typeof URLSearchParams&&t instanceof URLSearchParams}function g(t){return t.replace(/^\s*/,"").replace(/\s*$/,"")}function b(){return"undefined"!=typeof window&&"undefined"!=typeof document&&"function"==typeof document.createElement}function y(t,e){if(null!==t&&void 0!==t)if("object"==typeof t||r(t)||(t=[t]),r(t))for(var n=0,i=t.length;n=200&&t<300}};c.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(t){c.headers[t]={}}),i.forEach(["post","put","patch"],function(t){c.headers[t]=i.merge(s)}),t.exports=c}).call(e,n(46))},function(t,e){var n=t.exports={version:"2.5.3"};"number"==typeof __e&&(__e=n)},function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,e){t.exports={}},function(t,e){t.exports=!0},function(t,e,n){var r=n(44),i=n(18);t.exports=Object.keys||function(t){return r(t,i)}},function(t,e){e.f={}.propertyIsEnumerable},function(t,e,n){var r=n(5).f,i=n(2),o=n(7)("toStringTag");t.exports=function(t,e,n){t&&!i(t=n?t:t.prototype,o)&&r(t,o,{configurable:!0,value:e})}},function(t,e,n){var r=n(25)("keys"),i=n(13);t.exports=function(t){return r[t]||(r[t]=i(t))}},function(t,e,n){var r=n(1),i=r["__core-js_shared__"]||(r["__core-js_shared__"]={});t.exports=function(t){return i[t]||(i[t]={})}},function(t,e){var n=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:n)(t)}},function(t,e,n){var r=n(9);t.exports=function(t,e){if(!r(t))return t;var n,i;if(e&&"function"==typeof(n=t.toString)&&!r(i=n.call(t)))return i;if("function"==typeof(n=t.valueOf)&&!r(i=n.call(t)))return i;if(!e&&"function"==typeof(n=t.toString)&&!r(i=n.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,e,n){var r=n(1),i=n(16),o=n(20),a=n(29),s=n(5).f;t.exports=function(t){var e=i.Symbol||(i.Symbol=o?{}:r.Symbol||{});"_"==t.charAt(0)||t in e||s(e,t,{value:a.f(t)})}},function(t,e,n){e.f=n(7)},function(t,e,n){var r=n(8)(n(74),null,null,null,null);t.exports=r.exports},function(t,e,n){"use strict";var r=n(0),i=n(59),o=n(62),a=n(68),s=n(66),c=n(34),l="undefined"!=typeof window&&window.btoa&&window.btoa.bind(window)||n(61);t.exports=function(t){return new Promise(function(e,u){var f=t.data,d=t.headers;r.isFormData(f)&&delete d["Content-Type"];var h=new XMLHttpRequest,p="onreadystatechange",v=!1;if("undefined"==typeof window||!window.XDomainRequest||"withCredentials"in h||s(t.url)||(h=new window.XDomainRequest,p="onload",v=!0,h.onprogress=function(){},h.ontimeout=function(){}),t.auth){var m=t.auth.username||"",g=t.auth.password||"";d.Authorization="Basic "+l(m+":"+g)}if(h.open(t.method.toUpperCase(),o(t.url,t.params,t.paramsSerializer),!0),h.timeout=t.timeout,h[p]=function(){if(h&&(4===h.readyState||v)&&(0!==h.status||h.responseURL&&0===h.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in h?a(h.getAllResponseHeaders()):null,r=t.responseType&&"text"!==t.responseType?h.response:h.responseText,o={data:r,status:1223===h.status?204:h.status,statusText:1223===h.status?"No Content":h.statusText,headers:n,config:t,request:h};i(e,u,o),h=null}},h.onerror=function(){u(c("Network Error",t)),h=null},h.ontimeout=function(){u(c("timeout of "+t.timeout+"ms exceeded",t,"ECONNABORTED")),h=null},r.isStandardBrowserEnv()){var b=n(64),y=(t.withCredentials||s(t.url))&&t.xsrfCookieName?b.read(t.xsrfCookieName):void 0;y&&(d[t.xsrfHeaderName]=y)}if("setRequestHeader"in h&&r.forEach(d,function(t,e){void 0===f&&"content-type"===e.toLowerCase()?delete d[e]:h.setRequestHeader(e,t)}),t.withCredentials&&(h.withCredentials=!0),t.responseType)try{h.responseType=t.responseType}catch(t){if("json"!==h.responseType)throw t}"function"==typeof t.onDownloadProgress&&h.addEventListener("progress",t.onDownloadProgress),"function"==typeof t.onUploadProgress&&h.upload&&h.upload.addEventListener("progress",t.onUploadProgress),t.cancelToken&&t.cancelToken.promise.then(function(t){h&&(h.abort(),u(t),h=null)}),void 0===f&&(f=null),h.send(f)})}},function(t,e,n){"use strict";function r(t){this.message=t}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,t.exports=r},function(t,e,n){"use strict";t.exports=function(t){return!(!t||!t.__CANCEL__)}},function(t,e,n){"use strict";var r=n(58);t.exports=function(t,e,n,i){var o=new Error(t);return r(o,e,n,i)}},function(t,e,n){"use strict";t.exports=function(t,e){return function(){for(var n=new Array(arguments.length),r=0;rdocument.F=Object<\/script>"),t.close(),c=t.F;r--;)delete c.prototype[o[r]];return c()};t.exports=Object.create||function(t,e){var n;return null!==t?(s.prototype=r(t),n=new s,s.prototype=null,n[a]=t):n=c(),void 0===e?n:i(n,e)}},function(t,e,n){var r=n(44),i=n(18).concat("length","prototype");e.f=Object.getOwnPropertyNames||function(t){return r(t,i)}},function(t,e){e.f=Object.getOwnPropertySymbols},function(t,e,n){var r=n(2),i=n(6),o=n(84)(!1),a=n(24)("IE_PROTO");t.exports=function(t,e){var n,s=i(t),c=0,l=[];for(n in s)n!=a&&r(s,n)&&l.push(n);for(;e.length>c;)r(s,n=e[c++])&&(~o(l,n)||l.push(n));return l}},function(t,e,n){t.exports=n(4)},function(t,e){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function i(t){if(u===setTimeout)return setTimeout(t,0);if((u===n||!u)&&setTimeout)return u=setTimeout,setTimeout(t,0);try{return u(t,0)}catch(e){try{return u.call(null,t,0)}catch(e){return u.call(this,t,0)}}}function o(t){if(f===clearTimeout)return clearTimeout(t);if((f===r||!f)&&clearTimeout)return f=clearTimeout,clearTimeout(t);try{return f(t)}catch(e){try{return f.call(null,t)}catch(e){return f.call(this,t)}}}function a(){v&&h&&(v=!1,h.length?p=h.concat(p):m=-1,p.length&&s())}function s(){if(!v){var t=i(a);v=!0;for(var e=p.length;e;){for(h=p,p=[];++m1)for(var n=1;n>8-s%1*8)){if((n=i.charCodeAt(s+=.75))>255)throw new r;e=e<<8|n}return a}var o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.prototype=new Error,r.prototype.code=5,r.prototype.name="InvalidCharacterError",t.exports=i},function(t,e,n){"use strict";function r(t){return encodeURIComponent(t).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var i=n(0);t.exports=function(t,e,n){if(!e)return t;var o;if(n)o=n(e);else if(i.isURLSearchParams(e))o=e.toString();else{var a=[];i.forEach(e,function(t,e){null!==t&&void 0!==t&&(i.isArray(t)&&(e+="[]"),i.isArray(t)||(t=[t]),i.forEach(t,function(t){i.isDate(t)?t=t.toISOString():i.isObject(t)&&(t=JSON.stringify(t)),a.push(r(e)+"="+r(t))}))}),o=a.join("&")}return o&&(t+=(-1===t.indexOf("?")?"?":"&")+o),t}},function(t,e,n){"use strict";t.exports=function(t,e){return t.replace(/\/+$/,"")+"/"+e.replace(/^\/+/,"")}},function(t,e,n){"use strict";var r=n(0);t.exports=r.isStandardBrowserEnv()?function(){return{write:function(t,e,n,i,o,a){var s=[];s.push(t+"="+encodeURIComponent(e)),r.isNumber(n)&&s.push("expires="+new Date(n).toGMTString()),r.isString(i)&&s.push("path="+i),r.isString(o)&&s.push("domain="+o),!0===a&&s.push("secure"),document.cookie=s.join("; ")},read:function(t){var e=document.cookie.match(new RegExp("(^|;\\s*)("+t+")=([^;]*)"));return e?decodeURIComponent(e[3]):null},remove:function(t){this.write(t,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(t,e,n){"use strict";t.exports=function(t){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(t)}},function(t,e,n){"use strict";var r=n(0);t.exports=r.isStandardBrowserEnv()?function(){function t(t){var e=t;return n&&(i.setAttribute("href",e),e=i.href),i.setAttribute("href",e),{href:i.href,protocol:i.protocol?i.protocol.replace(/:$/,""):"",host:i.host,search:i.search?i.search.replace(/^\?/,""):"",hash:i.hash?i.hash.replace(/^#/,""):"",hostname:i.hostname,port:i.port,pathname:"/"===i.pathname.charAt(0)?i.pathname:"/"+i.pathname}}var e,n=/(msie|trident)/i.test(navigator.userAgent),i=document.createElement("a");return e=t(window.location.href),function(n){var i=r.isString(n)?t(n):n;return i.protocol===e.protocol&&i.host===e.host}}():function(){return function(){return!0}}()},function(t,e,n){"use strict";var r=n(0);t.exports=function(t,e){r.forEach(t,function(n,r){r!==e&&r.toUpperCase()===e.toUpperCase()&&(t[e]=n,delete t[r])})}},function(t,e,n){"use strict";var r=n(0);t.exports=function(t){var e,n,i,o={};return t?(r.forEach(t.split("\n"),function(t){i=t.indexOf(":"),e=r.trim(t.substr(0,i)).toLowerCase(),n=r.trim(t.substr(i+1)),e&&(o[e]=o[e]?o[e]+", "+n:n)}),o):o}},function(t,e,n){"use strict";t.exports=function(t){return function(e){return t.apply(null,e)}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(79),i=n.n(r),o=n(52),a=n.n(o);e.default={props:{fields:{type:Array,required:!0},loadOnStart:{type:Boolean,default:!0},apiUrl:{type:String,default:""},httpMethod:{type:String,default:"get",validator:function(t){return["get","post"].indexOf(t)>-1}},reactiveApiUrl:{type:Boolean,default:!0},apiMode:{type:Boolean,default:!0},data:{type:[Array,Object],default:null},dataTotal:{type:Number,default:0},dataManager:{type:Function,default:null},dataPath:{type:String,default:"data"},paginationPath:{type:[String],default:"links.pagination"},queryParams:{type:[Object,Function],default:function(){return{sort:"sort",page:"page",perPage:"per_page"}}},appendParams:{type:Object,default:function(){return{}}},httpOptions:{type:Object,default:function(){return{}}},httpFetch:{type:Function,default:null},perPage:{type:Number,default:10},initialPage:{type:Number,default:1},sortOrder:{type:Array,default:function(){return[]}},multiSort:{type:Boolean,default:function(){return!1}},tableHeight:{type:String,default:null},multiSortKey:{type:String,default:"alt"},rowClassCallback:{type:[String,Function],default:""},rowClass:{type:[String,Function],default:""},detailRowComponent:{type:String,default:""},detailRowTransition:{type:String,default:""},trackBy:{type:String,default:"id"},css:{type:Object,default:function(){return{tableClass:"ui blue selectable celled stackable attached table",loadingClass:"loading",ascendingIcon:"blue chevron up icon",descendingIcon:"blue chevron down icon",ascendingClass:"sorted-asc",descendingClass:"sorted-desc",sortableIcon:"",detailRowClass:"vuetable-detail-row",handleIcon:"grey sidebar icon",tableBodyClass:"vuetable-semantic-no-top vuetable-fixed-layout",tableHeaderClass:"vuetable-fixed-layout"}}},minRows:{type:Number,default:0},silent:{type:Boolean,default:!1},noDataTemplate:{type:String,default:function(){return"No Data Available"}},showSortIcons:{type:Boolean,default:!0}},data:function(){return{eventPrefix:"vuetable:",tableFields:[],tableData:null,tablePagination:null,currentPage:this.initialPage,selectedTo:[],visibleDetailRows:[],lastScrollPosition:0,scrollBarWidth:"17px",scrollVisible:!1}},mounted:function(){if(this.normalizeFields(),this.normalizeSortOrder(),this.isFixedHeader&&(this.scrollBarWidth=this.getScrollBarWidth()+"px"),this.$nextTick(function(){this.fireEvent("initialized",this.tableFields)}),this.loadOnStart&&this.loadData(),this.isFixedHeader){var t=this.$el.getElementsByClassName("vuetable-body-wrapper")[0];null!=t&&t.addEventListener("scroll",this.handleScroll)}},destroyed:function(){var t=this.$el.getElementsByClassName("vuetable-body-wrapper")[0];null!=t&&t.removeEventListener("scroll",this.handleScroll)},computed:{version:function(){return"1.7.5"},useDetailRow:function(){return this.tableData&&this.tableData[0]&&""!==this.detailRowComponent&&void 0===this.tableData[0][this.trackBy]?(this.warn("You need to define unique row identifier in order for detail-row feature to work. Use `track-by` prop to define one!"),!1):""!==this.detailRowComponent},countVisibleFields:function(){return this.tableFields.filter(function(t){return t.visible}).length},countTableData:function(){return null===this.tableData?0:this.tableData.length},displayEmptyDataRow:function(){return 0===this.countTableData&&this.noDataTemplate.length>0},lessThanMinRows:function(){return null===this.tableData||0===this.tableData.length||this.tableData.length=this.minRows?0:this.minRows-this.tableData.length},isApiMode:function(){return this.apiMode},isDataMode:function(){return!this.apiMode},isFixedHeader:function(){return null!=this.tableHeight}},methods:{getScrollBarWidth:function(){var t=document.createElement("div"),e=document.createElement("div");t.style.visibility="hidden",t.style.width="100px",e.style.width="100%",t.appendChild(e),document.body.appendChild(t);var n=t.offsetWidth;t.style.overflow="scroll";var r=e.offsetWidth;return document.body.removeChild(t),n-r},handleScroll:function(t){var e=t.currentTarget.scrollLeft;if(e!=this.lastScrollPosition){var n=this.$el.getElementsByClassName("vuetable-head-wrapper")[0];null!=n&&(n.scrollLeft=e),this.lastScrollPosition=e}},normalizeFields:function(){if(void 0===this.fields)return void this.warn('You need to provide "fields" prop.');this.tableFields=[];var t=this,e=void 0;this.fields.forEach(function(n,r){e="string"==typeof n?{name:n,title:t.setTitle(n),titleClass:"",dataClass:"",callback:null,visible:!0}:{name:n.name,width:n.width,title:void 0===n.title?t.setTitle(n.name):n.title,sortField:n.sortField,titleClass:void 0===n.titleClass?"":n.titleClass,dataClass:void 0===n.dataClass?"":n.dataClass,callback:void 0===n.callback?"":n.callback,visible:void 0===n.visible||n.visible},t.tableFields.push(e)})},setData:function(t){if(null!==t&&void 0!==t){if(this.fireEvent("loading"),Array.isArray(t))return this.tableData=t,void this.fireEvent("loaded");this.tableData=this.getObjectValue(t,this.dataPath,null),this.tablePagination=this.getObjectValue(t,this.paginationPath,null),this.$nextTick(function(){this.fixHeader(),this.fireEvent("pagination-data",this.tablePagination),this.fireEvent("loaded")})}},setTitle:function(t){return this.isSpecialField(t)?"":this.titleCase(t)},getTitle:function(t){return"function"==typeof t.title?t.title():void 0===t.title?t.name.replace("."," "):t.title},renderTitle:function(t){var e=this.getTitle(t);if(e.length>0&&this.isInCurrentSortGroup(t)||this.hasSortableIcon(t)){var n="opacity:"+this.sortIconOpacity(t)+";position:relative;float:right";return e+" "+(this.showSortIcons?this.renderIconTag(["sort-icon",this.sortIcon(t)],'style="'+n+'"'):"")}return e},renderSequence:function(t){return this.tablePagination?this.tablePagination.from+t:t},renderNormalField:function(t,e){return this.hasCallback(t)?this.callCallback(t,e):this.getObjectValue(e,t.name,"")},isSpecialField:function(t){return"__"===t.slice(0,2)},titleCase:function(t){return t.replace(/\w+/g,function(t){return t.charAt(0).toUpperCase()+t.substr(1).toLowerCase()})},camelCase:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"_",n=this;return t.split(e).map(function(t){return n.titleCase(t)}).join("")},notIn:function(t,e){return-1===e.indexOf(t)},loadData:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.loadSuccess,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.loadFailed;return this.isDataMode?void this.callDataManager():(this.fireEvent("loading"),this.httpOptions.params=this.getAppendParams(this.getAllQueryParams()),this.fetch(this.apiUrl,this.httpOptions).then(t,e).catch(function(){return e()}))},fetch:function(t,e){return this.httpFetch?this.httpFetch(t,e):a.a[this.httpMethod](t,e)},loadSuccess:function(t){this.fireEvent("load-success",t);var e=this.transform(t.data);this.tableData=this.getObjectValue(e,this.dataPath,null),this.tablePagination=this.getObjectValue(e,this.paginationPath,null),null===this.tablePagination&&this.warn('vuetable: pagination-path "'+this.paginationPath+'" not found. It looks like the data returned from the sever does not have pagination information or you may have set it incorrectly.\nYou can explicitly suppress this warning by setting pagination-path="".'),this.$nextTick(function(){this.fixHeader(),this.fireEvent("pagination-data",this.tablePagination),this.fireEvent("loaded")})},fixHeader:function(){if(this.isFixedHeader){var t=this.$el.getElementsByClassName("vuetable-body-wrapper")[0];null!=t&&(t.scrollHeight>t.clientHeight?this.scrollVisible=!0:this.scrollVisible=!1)}},loadFailed:function(t){console.error("load-error",t),this.fireEvent("load-error",t),this.fireEvent("loaded")},transform:function(t){return this.parentFunctionExists("transform")?this.$parent.transform.call(this.$parent,t):t},parentFunctionExists:function(t){return""!==t&&"function"==typeof this.$parent[t]},callParentFunction:function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;return this.parentFunctionExists(t)?this.$parent[t].call(this.$parent,e):n},fireEvent:function(t,e){this.$emit(this.eventPrefix+t,e)},warn:function(t){this.silent||console.warn(t)},getAllQueryParams:function(){var t={};return"function"==typeof this.queryParams?(t=this.queryParams(this.sortOrder,this.currentPage,this.perPage),"object"!==(void 0===t?"undefined":i()(t))?{}:t):(t[this.queryParams.sort]=this.getSortParam(),t[this.queryParams.page]=this.currentPage,t[this.queryParams.perPage]=this.perPage,t)},getSortParam:function(){return this.sortOrder&&""!=this.sortOrder.field?"function"==typeof this.$parent.getSortParam?this.$parent.getSortParam.call(this.$parent,this.sortOrder):this.getDefaultSortParam():""},getDefaultSortParam:function(){for(var t="",e=0;e0?this.$parent[r].apply(this.$parent,[i].concat(n)):this.$parent[r].call(this.$parent,i)}return null}},getObjectValue:function(t,e,n){n=void 0===n?null:n;var r=t;if(""!=e.trim()){e.split(".").forEach(function(t){if(null===r||void 0===r[t]||null===r[t])return void(r=n);r=r[t]})}return r},toggleCheckbox:function(t,e,n){var r=n.target.checked,i=this.trackBy;if(void 0===t[i])return void this.warn('__checkbox field: The "'+this.trackBy+'" field does not exist! Make sure the field you specify in "track-by" prop does exist.');var o=t[i];r?this.selectId(o):this.unselectId(o),this.$emit("vuetable:checkbox-toggled",r,t)},selectId:function(t){this.isSelectedRow(t)||this.selectedTo.push(t)},unselectId:function(t){this.selectedTo=this.selectedTo.filter(function(e){return e!==t})},isSelectedRow:function(t){return this.selectedTo.indexOf(t)>=0},rowSelected:function(t,e){var n=this.trackBy,r=t[n];return this.isSelectedRow(r)},checkCheckboxesState:function(t){if(this.tableData){var e=this,n=this.trackBy,r="th.vuetable-th-checkbox-"+n+" input[type=checkbox]",i=document.querySelectorAll(r);void 0===i.forEach&&(i.forEach=function(t){[].forEach.call(i,t)});var o=this.tableData.filter(function(t){return e.selectedTo.indexOf(t[n])>=0});return o.length<=0?(i.forEach(function(t){t.indeterminate=!1}),!1):o.length1&&(this.currentPage--,this.loadData())},gotoNextPage:function(){this.currentPage0&&t<=this.tablePagination.last_page&&(this.currentPage=t,this.loadData())},isVisibleDetailRow:function(t){return this.visibleDetailRows.indexOf(t)>=0},showDetailRow:function(t){this.isVisibleDetailRow(t)||this.visibleDetailRows.push(t)},hideDetailRow:function(t){this.isVisibleDetailRow(t)&&this.visibleDetailRows.splice(this.visibleDetailRows.indexOf(t),1)},toggleDetailRow:function(t){this.isVisibleDetailRow(t)?this.hideDetailRow(t):this.showDetailRow(t)},showField:function(t){t<0||t>this.tableFields.length||(this.tableFields[t].visible=!0)},hideField:function(t){t<0||t>this.tableFields.length||(this.tableFields[t].visible=!1)},toggleField:function(t){t<0||t>this.tableFields.length||(this.tableFields[t].visible=!this.tableFields[t].visible)},renderIconTag:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return void 0===this.css.renderIcon?'":this.css.renderIcon(t,e)},makePagination:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;return t=null===t?this.dataTotal:t,e=null===e?this.perPage:e,n=null===n?this.currentPage:n,{total:t,per_page:e,current_page:n,last_page:Math.ceil(t/e)||0,next_page_url:"",prev_page_url:"",from:(n-1)*e+1,to:Math.min(n*e,t)}},normalizeSortOrder:function(){this.sortOrder.forEach(function(t){t.sortField=t.sortField||t.field})},callDataManager:function(){if(null!==this.dataManager||null!==this.data)return Array.isArray(this.data)?this.setData(this.data):(this.normalizeSortOrder(),this.setData(this.dataManager?this.dataManager(this.sortOrder,this.makePagination()):this.data))},onRowClass:function(t,e){return""!==this.rowClassCallback?void this.warn('"row-class-callback" prop is deprecated, please use "row-class" prop instead.'):"function"==typeof this.rowClass?this.rowClass(t,e):this.rowClass},onRowChanged:function(t){return this.fireEvent("row-changed",t),!0},onRowClicked:function(t,e){return this.$emit(this.eventPrefix+"row-clicked",t,e),!0},onRowDoubleClicked:function(t,e){this.$emit(this.eventPrefix+"row-dblclicked",t,e)},onDetailRowClick:function(t,e){this.$emit(this.eventPrefix+"detail-row-clicked",t,e)},onCellClicked:function(t,e,n){this.$emit(this.eventPrefix+"cell-clicked",t,e,n)},onCellDoubleClicked:function(t,e,n){this.$emit(this.eventPrefix+"cell-dblclicked",t,e,n)},onCellRightClicked:function(t,e,n){this.$emit(this.eventPrefix+"cell-rightclicked",t,e,n)},changePage:function(t){"prev"===t?this.gotoPreviousPage():"next"===t?this.gotoNextPage():this.gotoPage(t)},reload:function(){return this.loadData()},refresh:function(){return this.currentPage=1,this.loadData()},resetData:function(){this.tableData=null,this.tablePagination=null,this.fireEvent("data-reset")}},watch:{multiSort:function(t,e){!1===t&&this.sortOrder.length>1&&(this.sortOrder.splice(1),this.loadData())},apiUrl:function(t,e){this.reactiveApiUrl&&t!==e&&this.refresh()},data:function(t,e){this.setData(t)},tableHeight:function(t,e){this.fixHeader()}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(14),i=n.n(r);e.default={mixins:[i.a]}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(14),i=n.n(r);e.default={mixins:[i.a],props:{pageText:{type:String,default:function(){return"Page"}}},methods:{registerEvents:function(){var t=this;this.$on("vuetable:pagination-data",function(e){t.setPaginationData(e)})}},created:function(){this.registerEvents()}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(30),i=n.n(r);e.default={mixins:[i.a]}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={props:{css:{type:Object,default:function(){return{infoClass:"left floated left aligned six wide column"}}},infoTemplate:{type:String,default:function(){return"Displaying {from} to {to} of {total} items"}},noDataTemplate:{type:String,default:function(){return"No relevant data"}}},data:function(){return{tablePagination:null}},computed:{paginationInfo:function(){return null==this.tablePagination||0==this.tablePagination.total?this.noDataTemplate:this.infoTemplate.replace("{from}",this.tablePagination.from||0).replace("{to}",this.tablePagination.to||0).replace("{total}",this.tablePagination.total||0)}},methods:{setPaginationData:function(t){this.tablePagination=t},resetData:function(){this.tablePagination=null}}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default={props:{css:{type:Object,default:function(){return{wrapperClass:"ui right floated pagination menu",activeClass:"active large",disabledClass:"disabled",pageClass:"item",linkClass:"icon item",paginationClass:"ui bottom attached segment grid",paginationInfoClass:"left floated left aligned six wide column",dropdownClass:"ui search dropdown",icons:{first:"angle double left icon",prev:"left chevron icon",next:"right chevron icon",last:"angle double right icon"}}}},onEachSide:{type:Number,default:function(){return 2}}},data:function(){return{eventPrefix:"vuetable-pagination:",tablePagination:null}},computed:{totalPage:function(){return null===this.tablePagination?0:this.tablePagination.last_page},isOnFirstPage:function(){return null!==this.tablePagination&&1===this.tablePagination.current_page},isOnLastPage:function(){return null!==this.tablePagination&&this.tablePagination.current_page===this.tablePagination.last_page},notEnoughPages:function(){return this.totalPage<2*this.onEachSide+4},windowSize:function(){return 2*this.onEachSide+1},windowStart:function(){return!this.tablePagination||this.tablePagination.current_page<=this.onEachSide?1:this.tablePagination.current_page>=this.totalPage-this.onEachSide?this.totalPage-2*this.onEachSide:this.tablePagination.current_page-this.onEachSide}},methods:{loadPage:function(t){this.$emit(this.eventPrefix+"change-page",t)},isCurrentPage:function(t){return t===this.tablePagination.current_page},setPaginationData:function(t){this.tablePagination=t},resetData:function(){this.tablePagination=null}}}},function(t,e,n){"use strict";function r(t){t.component("vuetable",o.a),t.component("vuetable-pagination",s.a),t.component("vuetable-pagination-dropdown",l.a),t.component("vuetable-pagination-info",f.a)}Object.defineProperty(e,"__esModule",{value:!0}),n.d(e,"install",function(){return r});var i=n(48),o=n.n(i),a=n(49),s=n.n(a),c=n(50),l=n.n(c),u=n(51),f=n.n(u),d=n(14),h=n.n(d),p=n(30),v=n.n(p),m=n(47),g=n.n(m);n.d(e,"Vuetable",function(){return o.a}),n.d(e,"VuetablePagination",function(){return s.a}),n.d(e,"VuetablePaginationDropDown",function(){return l.a}),n.d(e,"VuetablePaginationInfo",function(){return f.a}),n.d(e,"VuetablePaginationMixin",function(){return h.a}),n.d(e,"VuetablePaginationInfoMixin",function(){return v.a}),window.Promise||(window.Promise=g.a),e.default=o.a},function(t,e,n){t.exports={default:n(80),__esModule:!0}},function(t,e,n){t.exports={default:n(81),__esModule:!0}},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}e.__esModule=!0;var i=n(78),o=r(i),a=n(77),s=r(a),c="function"==typeof s.default&&"symbol"==typeof o.default?function(t){return typeof t}:function(t){return t&&"function"==typeof s.default&&t.constructor===s.default&&t!==s.default.prototype?"symbol":typeof t};e.default="function"==typeof s.default&&"symbol"===c(o.default)?function(t){return void 0===t?"undefined":c(t)}:function(t){return t&&"function"==typeof s.default&&t.constructor===s.default&&t!==s.default.prototype?"symbol":void 0===t?"undefined":c(t)}},function(t,e,n){n(104),n(102),n(105),n(106),t.exports=n(16).Symbol},function(t,e,n){n(103),n(107),t.exports=n(29).f("iterator")},function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,e){t.exports=function(){}},function(t,e,n){var r=n(6),i=n(99),o=n(98);t.exports=function(t){return function(e,n,a){var s,c=r(e),l=i(c.length),u=o(a,l);if(t&&n!=n){for(;l>u;)if((s=c[u++])!=s)return!0}else for(;l>u;u++)if((t||u in c)&&c[u]===n)return t||u||0;return!t&&-1}}},function(t,e,n){var r=n(82);t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,i){return t.call(e,n,r,i)}}return function(){return t.apply(e,arguments)}}},function(t,e,n){var r=n(21),i=n(43),o=n(22);t.exports=function(t){var e=r(t),n=i.f;if(n)for(var a,s=n(t),c=o.f,l=0;s.length>l;)c.call(t,a=s[l++])&&e.push(a);return e}},function(t,e,n){var r=n(1).document;t.exports=r&&r.documentElement},function(t,e,n){var r=n(36);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==r(t)?t.split(""):Object(t)}},function(t,e,n){var r=n(36);t.exports=Array.isArray||function(t){return"Array"==r(t)}},function(t,e,n){"use strict";var r=n(41),i=n(12),o=n(23),a={};n(4)(a,n(7)("iterator"),function(){return this}),t.exports=function(t,e,n){t.prototype=r(a,{next:i(1,n)}),o(t,e+" Iterator")}},function(t,e){t.exports=function(t,e){return{value:e,done:!!t}}},function(t,e,n){var r=n(13)("meta"),i=n(9),o=n(2),a=n(5).f,s=0,c=Object.isExtensible||function(){return!0},l=!n(11)(function(){return c(Object.preventExtensions({}))}),u=function(t){a(t,r,{value:{i:"O"+ ++s,w:{}}})},f=function(t,e){if(!i(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!o(t,r)){if(!c(t))return"F";if(!e)return"E";u(t)}return t[r].i},d=function(t,e){if(!o(t,r)){if(!c(t))return!0;if(!e)return!1;u(t)}return t[r].w},h=function(t){return l&&p.NEED&&c(t)&&!o(t,r)&&u(t),t},p=t.exports={KEY:r,NEED:!1,fastKey:f,getWeak:d,onFreeze:h}},function(t,e,n){var r=n(5),i=n(10),o=n(21);t.exports=n(3)?Object.defineProperties:function(t,e){i(t);for(var n,a=o(e),s=a.length,c=0;s>c;)r.f(t,n=a[c++],e[n]);return t}},function(t,e,n){var r=n(22),i=n(12),o=n(6),a=n(27),s=n(2),c=n(39),l=Object.getOwnPropertyDescriptor;e.f=n(3)?l:function(t,e){if(t=o(t),e=a(e,!0),c)try{return l(t,e)}catch(t){}if(s(t,e))return i(!r.f.call(t,e),t[e])}},function(t,e,n){var r=n(6),i=n(42).f,o={}.toString,a="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],s=function(t){try{return i(t)}catch(t){return a.slice()}};t.exports.f=function(t){return a&&"[object Window]"==o.call(t)?s(t):i(r(t))}},function(t,e,n){var r=n(2),i=n(100),o=n(24)("IE_PROTO"),a=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=i(t),r(t,o)?t[o]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?a:null}},function(t,e,n){var r=n(26),i=n(17);t.exports=function(t){return function(e,n){var o,a,s=String(i(e)),c=r(n),l=s.length;return c<0||c>=l?t?"":void 0:(o=s.charCodeAt(c),o<55296||o>56319||c+1===l||(a=s.charCodeAt(c+1))<56320||a>57343?t?s.charAt(c):o:t?s.slice(c,c+2):a-56320+(o-55296<<10)+65536)}}},function(t,e,n){var r=n(26),i=Math.max,o=Math.min;t.exports=function(t,e){return t=r(t),t<0?i(t+e,0):o(t,e)}},function(t,e,n){var r=n(26),i=Math.min;t.exports=function(t){return t>0?i(r(t),9007199254740991):0}},function(t,e,n){var r=n(17);t.exports=function(t){return Object(r(t))}},function(t,e,n){"use strict";var r=n(83),i=n(91),o=n(19),a=n(6);t.exports=n(40)(Array,"Array",function(t,e){this._t=a(t),this._i=0,this._k=e},function(){var t=this._t,e=this._k,n=this._i++;return!t||n>=t.length?(this._t=void 0,i(1)):"keys"==e?i(0,n):"values"==e?i(0,t[n]):i(0,[n,t[n]])},"values"),o.Arguments=o.Array,r("keys"),r("values"),r("entries")},function(t,e){},function(t,e,n){"use strict";var r=n(97)(!0);n(40)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,e=this._t,n=this._i;return n>=e.length?{value:void 0,done:!0}:(t=r(e,n),this._i+=t.length,{value:t,done:!1})})},function(t,e,n){"use strict";var r=n(1),i=n(2),o=n(3),a=n(38),s=n(45),c=n(92).KEY,l=n(11),u=n(25),f=n(23),d=n(13),h=n(7),p=n(29),v=n(28),m=n(86),g=n(89),b=n(10),y=n(9),w=n(6),_=n(27),x=n(12),C=n(41),P=n(95),k=n(94),S=n(5),O=n(21),T=k.f,E=S.f,F=P.f,R=r.Symbol,D=r.JSON,j=D&&D.stringify,M=h("_hidden"),I=h("toPrimitive"),L={}.propertyIsEnumerable,A=u("symbol-registry"),N=u("symbols"),B=u("op-symbols"),H=Object.prototype,V="function"==typeof R,$=r.QObject,q=!$||!$.prototype||!$.prototype.findChild,U=o&&l(function(){return 7!=C(E({},"a",{get:function(){return E(this,"a",{value:7}).a}})).a})?function(t,e,n){var r=T(H,e);r&&delete H[e],E(t,e,n),r&&t!==H&&E(H,e,r)}:E,z=function(t){var e=N[t]=C(R.prototype);return e._k=t,e},G=V&&"symbol"==typeof R.iterator?function(t){return"symbol"==typeof t}:function(t){return t instanceof R},W=function(t,e,n){return t===H&&W(B,e,n),b(t),e=_(e,!0),b(n),i(N,e)?(n.enumerable?(i(t,M)&&t[M][e]&&(t[M][e]=!1),n=C(n,{enumerable:x(0,!1)})):(i(t,M)||E(t,M,x(1,{})),t[M][e]=!0),U(t,e,n)):E(t,e,n)},J=function(t,e){b(t);for(var n,r=m(e=w(e)),i=0,o=r.length;o>i;)W(t,n=r[i++],e[n]);return t},X=function(t,e){return void 0===e?C(t):J(C(t),e)},K=function(t){var e=L.call(this,t=_(t,!0));return!(this===H&&i(N,t)&&!i(B,t))&&(!(e||!i(this,t)||!i(N,t)||i(this,M)&&this[M][t])||e)},Y=function(t,e){if(t=w(t),e=_(e,!0),t!==H||!i(N,e)||i(B,e)){var n=T(t,e);return!n||!i(N,e)||i(t,M)&&t[M][e]||(n.enumerable=!0),n}},Q=function(t){for(var e,n=F(w(t)),r=[],o=0;n.length>o;)i(N,e=n[o++])||e==M||e==c||r.push(e);return r},Z=function(t){for(var e,n=t===H,r=F(n?B:w(t)),o=[],a=0;r.length>a;)!i(N,e=r[a++])||n&&!i(H,e)||o.push(N[e]);return o};V||(R=function(){if(this instanceof R)throw TypeError("Symbol is not a constructor!");var t=d(arguments.length>0?arguments[0]:void 0),e=function(n){this===H&&e.call(B,n),i(this,M)&&i(this[M],t)&&(this[M][t]=!1),U(this,t,x(1,n))};return o&&q&&U(H,t,{configurable:!0,set:e}),z(t)},s(R.prototype,"toString",function(){return this._k}),k.f=Y,S.f=W,n(42).f=P.f=Q,n(22).f=K,n(43).f=Z,o&&!n(20)&&s(H,"propertyIsEnumerable",K,!0),p.f=function(t){return z(h(t))}),a(a.G+a.W+a.F*!V,{Symbol:R});for(var tt="hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split(","),et=0;tt.length>et;)h(tt[et++]);for(var nt=O(h.store),rt=0;nt.length>rt;)v(nt[rt++]);a(a.S+a.F*!V,"Symbol",{for:function(t){return i(A,t+="")?A[t]:A[t]=R(t)},keyFor:function(t){if(!G(t))throw TypeError(t+" is not a symbol!");for(var e in A)if(A[e]===t)return e},useSetter:function(){q=!0},useSimple:function(){q=!1}}),a(a.S+a.F*!V,"Object",{create:X,defineProperty:W,defineProperties:J,getOwnPropertyDescriptor:Y,getOwnPropertyNames:Q,getOwnPropertySymbols:Z}),D&&a(a.S+a.F*(!V||l(function(){var t=R();return"[null]"!=j([t])||"{}"!=j({a:t})||"{}"!=j(Object(t))})),"JSON",{stringify:function(t){for(var e,n,r=[t],i=1;arguments.length>i;)r.push(arguments[i++]);if(n=e=r[1],(y(e)||void 0!==t)&&!G(t))return g(e)||(e=function(t,e){if("function"==typeof n&&(e=n.call(this,t,e)),!G(e))return e}),r[1]=e,j.apply(D,r)}}),R.prototype[I]||n(4)(R.prototype,I,R.prototype.valueOf),f(R,"Symbol"),f(Math,"Math",!0),f(r.JSON,"JSON",!0)},function(t,e,n){n(28)("asyncIterator")},function(t,e,n){n(28)("observable")},function(t,e,n){n(101);for(var r=n(1),i=n(4),o=n(19),a=n(7)("toStringTag"),s="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),c=0;c=0&&(t._idleTimeoutId=setTimeout(function(){t._onTimeout&&t._onTimeout()},e))},n(110),e.setImmediate=setImmediate,e.clearImmediate=clearImmediate},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{class:[t.css.wrapperClass]},[n("a",{class:[t.css.linkClass,(r={},r[t.css.disabledClass]=t.isOnFirstPage,r)],on:{click:function(e){t.loadPage("prev")}}},[n("i",{class:t.css.icons.prev})]),t._v(" "),n("select",{class:["vuetable-pagination-dropdown",t.css.dropdownClass],on:{change:function(e){t.loadPage(e.target.selectedIndex+1)}}},t._l(t.totalPage,function(e){return n("option",{class:[t.css.pageClass],domProps:{value:e,selected:t.isCurrentPage(e)}},[t._v("\n "+t._s(t.pageText)+" "+t._s(e)+"\n ")])})),t._v(" "),n("a",{class:[t.css.linkClass,(i={},i[t.css.disabledClass]=t.isOnLastPage,i)],on:{click:function(e){t.loadPage("next")}}},[n("i",{class:t.css.icons.next})])]);var r,i},staticRenderFns:[]}},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return t.isFixedHeader?n("div",[n("div",{staticClass:"vuetable-head-wrapper"},[n("table",{class:["vuetable",t.css.tableClass,t.css.tableHeaderClass]},[n("thead",[n("tr",[t._l(t.tableFields,function(e,r){return[e.visible?[t.isSpecialField(e.name)?["__checkbox"==t.extractName(e.name)?n("th",{key:r,class:["vuetable-th-checkbox-"+t.trackBy,e.titleClass],style:{width:e.width}},[n("input",{attrs:{type:"checkbox"},domProps:{checked:t.checkCheckboxesState(e.name)},on:{change:function(n){t.toggleAllCheckboxes(e.name,n)}}})]):t._e(),t._v(" "),"__component"==t.extractName(e.name)?n("th",{key:r,class:["vuetable-th-component-"+t.trackBy,e.titleClass,t.sortClass(e),{sortable:t.isSortable(e)}],style:{width:e.width},domProps:{innerHTML:t._s(t.renderTitle(e))},on:{click:function(n){t.orderBy(e,n)}}}):t._e(),t._v(" "),"__slot"==t.extractName(e.name)?n("th",{key:r,class:["vuetable-th-slot-"+t.extractArgs(e.name),e.titleClass,t.sortClass(e),{sortable:t.isSortable(e)}],style:{width:e.width},domProps:{innerHTML:t._s(t.renderTitle(e))},on:{click:function(n){t.orderBy(e,n)}}}):t._e(),t._v(" "),"__sequence"==t.extractName(e.name)?n("th",{key:r,class:["vuetable-th-sequence",e.titleClass||""],style:{width:e.width},domProps:{innerHTML:t._s(t.renderTitle(e))}}):t._e(),t._v(" "),t.notIn(t.extractName(e.name),["__sequence","__checkbox","__component","__slot"])?n("th",{key:r,class:["vuetable-th-"+e.name,e.titleClass||""],style:{width:e.width},domProps:{innerHTML:t._s(t.renderTitle(e))}}):t._e()]:[n("th",{key:r,class:["vuetable-th-"+e.name,e.titleClass,t.sortClass(e),{sortable:t.isSortable(e)}],style:{width:e.width},attrs:{id:"_"+e.name},domProps:{innerHTML:t._s(t.renderTitle(e))},on:{click:function(n){t.orderBy(e,n)}}})]]:t._e()]}),t._v(" "),t.scrollVisible?n("th",{staticClass:"vuetable-gutter-col",style:{width:t.scrollBarWidth}}):t._e()],2)])])]),t._v(" "),n("div",{staticClass:"vuetable-body-wrapper",style:{height:t.tableHeight}},[n("table",{class:["vuetable",t.css.tableClass,t.css.tableBodyClass]},[n("colgroup",[t._l(t.tableFields,function(e,r){return[e.visible?[n("col",{key:r,class:["vuetable-th-"+e.name,e.titleClass],style:{width:e.width},attrs:{id:"_col_"+e.name}})]:t._e()]})],2),t._v(" "),n("tbody",{staticClass:"vuetable-body"},[t._l(t.tableData,function(e,r){return[n("tr",{key:r,class:t.onRowClass(e,r),attrs:{"item-index":r,render:t.onRowChanged(e)},on:{click:function(n){t.onRowClicked(e,n)},dblclick:function(n){t.onRowDoubleClicked(e,n)}}},[t._l(t.tableFields,function(i,o){return[i.visible?[t.isSpecialField(i.name)?["__sequence"==t.extractName(i.name)?n("td",{key:o,class:["vuetable-sequence",i.dataClass],domProps:{innerHTML:t._s(t.renderSequence(r))}}):t._e(),t._v(" "),"__handle"==t.extractName(i.name)?n("td",{key:o,class:["vuetable-handle",i.dataClass],domProps:{innerHTML:t._s(t.renderIconTag(["handle-icon",t.css.handleIcon]))}}):t._e(),t._v(" "),"__checkbox"==t.extractName(i.name)?n("td",{key:o,class:["vuetable-checkboxes",i.dataClass]},[n("input",{attrs:{type:"checkbox"},domProps:{checked:t.rowSelected(e,i.name)},on:{change:function(n){t.toggleCheckbox(e,i.name,n)}}})]):t._e(),t._v(" "),"__component"===t.extractName(i.name)?n("td",{key:o,class:["vuetable-component",i.dataClass]},[n(t.extractArgs(i.name),{tag:"component",attrs:{"row-data":e,"row-index":r,"row-field":i.sortField}})],1):t._e(),t._v(" "),"__slot"===t.extractName(i.name)?n("td",{key:o,class:["vuetable-slot",i.dataClass]},[t._t(t.extractArgs(i.name),null,{rowData:e,rowIndex:r,rowField:i.sortField})],2):t._e()]:[n("td",{key:o,class:i.dataClass,domProps:{innerHTML:t._s(t.renderNormalField(i,e))},on:{click:function(n){t.onCellClicked(e,i,n)},dblclick:function(n){t.onCellDoubleClicked(e,i,n)},contextmenu:function(n){t.onCellRightClicked(e,i,n)}}})]]:t._e()]})],2),t._v(" "),t.useDetailRow?[n("transition",{key:r,attrs:{name:t.detailRowTransition}},[t.isVisibleDetailRow(e[t.trackBy])?n("tr",{class:[t.css.detailRowClass],on:{click:function(n){t.onDetailRowClick(e,n)}}},[n("td",{attrs:{colspan:t.countVisibleFields}},[n(t.detailRowComponent,{tag:"component",attrs:{"row-data":e,"row-index":r}})],1)]):t._e()])]:t._e()]}),t._v(" "),t.displayEmptyDataRow?[n("tr",[n("td",{staticClass:"vuetable-empty-result",attrs:{colspan:t.countVisibleFields},domProps:{innerHTML:t._s(t.noDataTemplate)}})])]:t._e(),t._v(" "),t.lessThanMinRows?t._l(t.blankRows,function(e){return n("tr",{key:e,staticClass:"blank-row"},[t._l(t.tableFields,function(e,r){return[e.visible?n("td",{key:r},[t._v(" ")]):t._e()]})],2)}):t._e()],2)])])]):n("table",{class:["vuetable",t.css.tableClass]},[n("thead",[n("tr",[t._l(t.tableFields,function(e,r){return[e.visible?[t.isSpecialField(e.name)?["__checkbox"==t.extractName(e.name)?n("th",{key:r,class:["vuetable-th-checkbox-"+t.trackBy,e.titleClass],style:{width:e.width}},[n("input",{attrs:{type:"checkbox"},domProps:{checked:t.checkCheckboxesState(e.name)},on:{change:function(n){t.toggleAllCheckboxes(e.name,n)}}})]):t._e(),t._v(" "),"__component"==t.extractName(e.name)?n("th",{key:r,class:["vuetable-th-component-"+t.trackBy,e.titleClass,t.sortClass(e),{sortable:t.isSortable(e)}],style:{width:e.width},domProps:{innerHTML:t._s(t.renderTitle(e))},on:{click:function(n){t.orderBy(e,n)}}}):t._e(),t._v(" "),"__slot"==t.extractName(e.name)?n("th",{key:r,class:["vuetable-th-slot-"+t.extractArgs(e.name),e.titleClass,t.sortClass(e),{sortable:t.isSortable(e)}],style:{width:e.width},domProps:{innerHTML:t._s(t.renderTitle(e))},on:{click:function(n){t.orderBy(e,n)}}}):t._e(),t._v(" "),"__sequence"==t.extractName(e.name)?n("th",{key:r,class:["vuetable-th-sequence",e.titleClass||"",t.sortClass(e)],style:{width:e.width},domProps:{innerHTML:t._s(t.renderTitle(e))}}):t._e(),t._v(" "),t.notIn(t.extractName(e.name),["__sequence","__checkbox","__component","__slot"])?n("th",{key:r,class:["vuetable-th-"+e.name,e.titleClass||"",t.sortClass(e)],style:{width:e.width},domProps:{innerHTML:t._s(t.renderTitle(e))}}):t._e()]:[n("th",{key:r,class:["vuetable-th-"+e.name,e.titleClass,t.sortClass(e),{sortable:t.isSortable(e)}],style:{width:e.width},attrs:{id:"_"+e.name},domProps:{innerHTML:t._s(t.renderTitle(e))},on:{click:function(n){t.orderBy(e,n)}}})]]:t._e()]})],2)]),t._v(" "),n("tbody",{staticClass:"vuetable-body"},[t._l(t.tableData,function(e,r){return[n("tr",{key:r,class:t.onRowClass(e,r),attrs:{"item-index":r,render:t.onRowChanged(e)},on:{dblclick:function(n){t.onRowDoubleClicked(e,n)},click:function(n){t.onRowClicked(e,n)}}},[t._l(t.tableFields,function(i,o){return[i.visible?[t.isSpecialField(i.name)?["__sequence"==t.extractName(i.name)?n("td",{key:o,class:["vuetable-sequence",i.dataClass],domProps:{innerHTML:t._s(t.renderSequence(r))}}):t._e(),t._v(" "),"__handle"==t.extractName(i.name)?n("td",{key:o,class:["vuetable-handle",i.dataClass],domProps:{innerHTML:t._s(t.renderIconTag(["handle-icon",t.css.handleIcon]))}}):t._e(),t._v(" "),"__checkbox"==t.extractName(i.name)?n("td",{key:o,class:["vuetable-checkboxes",i.dataClass]},[n("input",{attrs:{type:"checkbox"},domProps:{checked:t.rowSelected(e,i.name)},on:{change:function(n){t.toggleCheckbox(e,i.name,n)}}})]):t._e(),t._v(" "),"__component"===t.extractName(i.name)?n("td",{key:o,class:["vuetable-component",i.dataClass]},[n(t.extractArgs(i.name),{tag:"component",attrs:{"row-data":e,"row-index":r,"row-field":i.sortField}})],1):t._e(),t._v(" "),"__slot"===t.extractName(i.name)?n("td",{key:o,class:["vuetable-slot",i.dataClass]},[t._t(t.extractArgs(i.name),null,{rowData:e,rowIndex:r,rowField:i.sortField})],2):t._e()]:[t.hasCallback(i)?n("td",{key:o,class:i.dataClass,domProps:{innerHTML:t._s(t.callCallback(i,e))},on:{click:function(n){t.onCellClicked(e,i,n)},dblclick:function(n){t.onCellDoubleClicked(e,i,n)},contextmenu:function(n){t.onCellRightClicked(e,i,n)}}}):n("td",{key:o,class:i.dataClass,domProps:{innerHTML:t._s(t.getObjectValue(e,i.name,""))},on:{click:function(n){t.onCellClicked(e,i,n)},dblclick:function(n){t.onCellDoubleClicked(e,i,n)},contextmenu:function(n){t.onCellRightClicked(e,i,n)}}})]]:t._e()]})],2),t._v(" "),t.useDetailRow?[n("transition",{key:r,attrs:{name:t.detailRowTransition}},[t.isVisibleDetailRow(e[t.trackBy])?n("tr",{class:[t.css.detailRowClass],on:{click:function(n){t.onDetailRowClick(e,n)}}},[n("td",{attrs:{colspan:t.countVisibleFields}},[n(t.detailRowComponent,{tag:"component",attrs:{"row-data":e,"row-index":r}})],1)]):t._e()])]:t._e()]}),t._v(" "),t.displayEmptyDataRow?[n("tr",[n("td",{staticClass:"vuetable-empty-result",attrs:{colspan:t.countVisibleFields},domProps:{innerHTML:t._s(t.noDataTemplate)}})])]:t._e(),t._v(" "),t.lessThanMinRows?t._l(t.blankRows,function(e){return n("tr",{key:e,staticClass:"blank-row"},[t._l(t.tableFields,function(e,r){return[e.visible?n("td",{key:r},[t._v(" ")]):t._e()]})],2)}):t._e()],2)])},staticRenderFns:[]}},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{directives:[{name:"show",rawName:"v-show",value:t.tablePagination&&t.tablePagination.last_page>1,expression:"tablePagination && tablePagination.last_page > 1"}],class:t.css.wrapperClass},[n("a",{class:["btn-nav",t.css.linkClass,t.isOnFirstPage?t.css.disabledClass:""],on:{click:function(e){t.loadPage(1)}}},[""!=t.css.icons.first?n("i",{class:[t.css.icons.first]}):n("span",[t._v("«")])]),t._v(" "),n("a",{class:["btn-nav",t.css.linkClass,t.isOnFirstPage?t.css.disabledClass:""],on:{click:function(e){t.loadPage("prev")}}},[""!=t.css.icons.next?n("i",{class:[t.css.icons.prev]}):n("span",[t._v(" ‹")])]),t._v(" "),t.notEnoughPages?[t._l(t.totalPage,function(e){return[n("a",{class:[t.css.pageClass,t.isCurrentPage(e)?t.css.activeClass:""],domProps:{innerHTML:t._s(e)},on:{click:function(n){t.loadPage(e)}}})]})]:[t._l(t.windowSize,function(e){return[n("a",{class:[t.css.pageClass,t.isCurrentPage(t.windowStart+e-1)?t.css.activeClass:""],domProps:{innerHTML:t._s(t.windowStart+e-1)},on:{click:function(n){t.loadPage(t.windowStart+e-1)}}})]})],t._v(" "),n("a",{class:["btn-nav",t.css.linkClass,t.isOnLastPage?t.css.disabledClass:""],on:{click:function(e){t.loadPage("next")}}},[""!=t.css.icons.next?n("i",{class:[t.css.icons.next]}):n("span",[t._v("› ")])]),t._v(" "),n("a",{class:["btn-nav",t.css.linkClass,t.isOnLastPage?t.css.disabledClass:""],on:{click:function(e){t.loadPage(t.totalPage)}}},[""!=t.css.icons.last?n("i",{class:[t.css.icons.last]}):n("span",[t._v("»")])])],2)},staticRenderFns:[]}},function(t,e){t.exports={render:function(){var t=this,e=t.$createElement;return(t._self._c||e)("div",{class:["vuetable-pagination-info",t.css.infoClass],domProps:{innerHTML:t._s(t.paginationInfo)}})},staticRenderFns:[]}},function(t,e,n){var r=n(108);"string"==typeof r&&(r=[[t.i,r,""]]),r.locals&&(t.exports=r.locals);n(117)("764e777c",r,!0)},function(t,e,n){function r(t){for(var e=0;en.parts.length&&(r.parts.length=n.parts.length)}else{for(var a=[],i=0;i h(App), 88 | router, 89 | // vuetify, 90 | // components: {FileUpload: VueUploadComponent}, 91 | // data: function() { 92 | // return { 93 | // showModal: false 94 | // } 95 | // } 96 | }).$mount('#app') 97 | -------------------------------------------------------------------------------- /frontend/src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuetify from 'vuetify/lib'; 3 | 4 | Vue.use(Vuetify); 5 | 6 | export default new Vuetify({ 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/vue.config.js: -------------------------------------------------------------------------------- 1 | // module.exports = { 2 | // "transpileDependencies": [ 3 | // "vuetify" 4 | // ] 5 | // } 6 | -------------------------------------------------------------------------------- /images/GettingStartedWComposer-arch-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/GettingStartedWComposer-arch-diagram.png -------------------------------------------------------------------------------- /images/addtowallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/addtowallet.png -------------------------------------------------------------------------------- /images/admintab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/admintab.png -------------------------------------------------------------------------------- /images/archi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/archi.png -------------------------------------------------------------------------------- /images/checkcompleted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/checkcompleted.png -------------------------------------------------------------------------------- /images/composerplayground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/composerplayground.png -------------------------------------------------------------------------------- /images/createparticipantbtn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/createparticipantbtn.png -------------------------------------------------------------------------------- /images/developer-analytical-dashboard-ai-powerai-flow-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/developer-analytical-dashboard-ai-powerai-flow-11.png -------------------------------------------------------------------------------- /images/developer-analytical-dashboards-ai-powerai-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/developer-analytical-dashboards-ai-powerai-flow.png -------------------------------------------------------------------------------- /images/generateNewId.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/generateNewId.png -------------------------------------------------------------------------------- /images/idstowallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/idstowallet.png -------------------------------------------------------------------------------- /images/importbtn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/importbtn.png -------------------------------------------------------------------------------- /images/productListing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/productListing.png -------------------------------------------------------------------------------- /images/retailer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/retailer.png -------------------------------------------------------------------------------- /images/retailerPL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/retailerPL.png -------------------------------------------------------------------------------- /images/selectid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/maximo-visual-inspector-dashboard/75db468546deae9be025a3d41a634594b8c22c56/images/selectid.png -------------------------------------------------------------------------------- /lib/foodSupply.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Write your transction processor functions here 4 | */ 5 | 6 | /* 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | /** 21 | * Create new product listing contract for the list of products 22 | * @param {composer.food.supply.createProductListing} createProductListing 23 | * @transaction 24 | */ 25 | function createProductListing(listing) { 26 | if(listing.products==null || listing.products.length==0){ 27 | throw new Error('Product list Empty!!'); 28 | } 29 | var factory = getFactory(); 30 | var productListing = factory.newResource('composer.food.supply', 'ProductListingContract',Math.random().toString(36).substring(3)); 31 | productListing.status= 'INITIALREQUEST'; 32 | productListing.supplier=listing.user; 33 | productListing.owner=listing.user; 34 | productListing.products=[]; 35 | listing.products.forEach(function (item) { 36 | var prodInfo = item.split(','); 37 | var product = factory.newConcept('composer.food.supply', 'Product'); 38 | product.productId=prodInfo[0]; 39 | product.countryId=listing.user.countryId; 40 | if(prodInfo.length>1){ 41 | product.quantity=prodInfo[1]; 42 | }else{ 43 | product.quantity='1'; 44 | } 45 | productListing.products.push(product); 46 | }); 47 | return getAssetRegistry('composer.food.supply.ProductListingContract') 48 | .then(function(listingRegistry) { 49 | return listingRegistry.add(productListing); 50 | }); 51 | } 52 | 53 | /** 54 | * Transfer the product listing to new owner 55 | * @param {composer.food.supply.transferListing} transferListing 56 | * @transaction 57 | */ 58 | function transferListing(listing) { 59 | var productListing = listing.productListing; 60 | productListing.owner= listing.newOwner; 61 | if(listing.ownerType.toLowerCase()=='supplier'){ 62 | listing.ownerType='Importer' 63 | productListing.status='EXEMPTCHECKREQ'; 64 | }else if(listing.ownerType.toLowerCase()=='importer'){ 65 | listing.ownerType='Retailer' 66 | if(productListing.status!='CHECKCOMPLETED'){ 67 | throw new Error('Exempt check pending!!'); 68 | } 69 | }else{ 70 | throw new Error('Please provide valid value for owner type'); 71 | } 72 | var newOwnerReg = null; 73 | return getParticipantRegistry('composer.food.supply.'+listing.ownerType) 74 | .then(function(registry) { 75 | newOwnerReg=registry; 76 | return registry.exists(listing.newOwner.getIdentifier()); 77 | }) 78 | .then(function(check) { 79 | if(check){ 80 | return getAssetRegistry('composer.food.supply.ProductListingContract') 81 | } 82 | else{ 83 | throw new Error('Please provide correct details for new owner'); 84 | } 85 | }) 86 | .then(function(listingRegistry) { 87 | return listingRegistry.update(productListing); 88 | }) 89 | .then(function(){ 90 | if(listing.ownerType == 'Retailer'){ 91 | productListing.products.forEach(function (item) { 92 | listing.newOwner.products.push(item); 93 | }); 94 | return newOwnerReg.update(listing.newOwner); 95 | }else{ 96 | return true; 97 | } 98 | }); 99 | } 100 | 101 | 102 | 103 | /** 104 | * Exempt check for the list of products by the regulator 105 | * @param {composer.food.supply.checkProducts} checkProducts 106 | * @transaction 107 | */ 108 | function checkProducts(listing) { 109 | var productListing = listing.productListing; 110 | if(productListing.status!='EXEMPTCHECKREQ' && productListing.status!='HAZARDANALYSISCHECKREQ'){ 111 | throw new Error('UnAuthorized transaction!!'); 112 | } 113 | var check=true; 114 | if(productListing.status=='EXEMPTCHECKREQ'){ 115 | if(listing.regulator.exemptedOrgIds.indexOf(productListing.supplier.orgId)==-1){ 116 | for (index in productListing.products) { 117 | var product =productListing.products[index]; 118 | if(listing.regulator.exemptedProductIds.indexOf(product.productId)==-1){ 119 | check=false; 120 | break; 121 | } 122 | } 123 | } 124 | } 125 | if(check){ 126 | productListing.status='CHECKCOMPLETED'; 127 | } 128 | else{ 129 | productListing.status='HAZARDANALYSISCHECKREQ'; 130 | } 131 | return getAssetRegistry('composer.food.supply.ProductListingContract') 132 | .then(function(listingRegistry) { 133 | return listingRegistry.update(productListing); 134 | }); 135 | 136 | } 137 | 138 | /** 139 | * Update exempted list 140 | * @param {composer.food.supply.updateExemptedList} updateExemptedList 141 | * @transaction 142 | */ 143 | function updateExemptedList(list) { 144 | if(list.newExemptedOrgIds!=null && list.newExemptedOrgIds.length>0){ 145 | list.newExemptedOrgIds.forEach(function (item) { 146 | list.regulator.exemptedOrgIds.push(item); 147 | }); 148 | } 149 | if(list.newExemptedProductIds!=null && list.newExemptedProductIds.length>0){ 150 | list.newExemptedProductIds.forEach(function (item) { 151 | list.regulator.exemptedProductIds.push(item); 152 | }); 153 | } 154 | return getParticipantRegistry('composer.food.supply.Regulator') 155 | .then(function(registry){ 156 | return registry.update(list.regulator); 157 | }); 158 | } 159 | -------------------------------------------------------------------------------- /lib/foodSupplyFabric.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Write your transction processor functions here 4 | */ 5 | 6 | /* 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | /** 21 | * Create new product listing contrat for the list of prodcuts 22 | * @param {composer.food.supply.createProductListing} createProductListing 23 | * @transaction 24 | */ 25 | function createProductListing(listing) { 26 | if(listing.products==null || listing.products.length==0){ 27 | throw new Error('Product list Empty!!'); 28 | } 29 | var factory = getFactory(); 30 | var productListing = factory.newResource('composer.food.supply', 'ProductListingContract',Math.random().toString(36).substring(3)); 31 | productListing.status= 'INITIALREQUEST'; 32 | productListing.supplier=listing.user; 33 | productListing.owner=listing.user; 34 | productListing.products=[]; 35 | listing.products.forEach(function (item) { 36 | var prodInfo = item.split(','); 37 | var product = factory.newConcept('composer.food.supply', 'Product'); 38 | product.productId=prodInfo[0]; 39 | product.countryId=listing.user.countryId; 40 | if(prodInfo.length>1){ 41 | product.quantity=prodInfo[1]; 42 | }else{ 43 | product.quantity='1'; 44 | } 45 | productListing.products.push(product); 46 | }); 47 | return getAssetRegistry('composer.food.supply.ProductListingContract') 48 | .then(function(listingRegistry) { 49 | return listingRegistry.add(productListing); 50 | }); 51 | } 52 | 53 | /** 54 | * Transfer the product listing to new owner 55 | * @param {composer.food.supply.transferListing} transferListing 56 | * @transaction 57 | */ 58 | function transferListing(listing) { 59 | var productListing = listing.productListing; 60 | productListing.owner= listing.newOwner; 61 | if(listing.ownerType.toLowerCase()=='supplier'){ 62 | listing.ownerType='Importer' 63 | productListing.status='EXEMPTCHECKREQ'; 64 | }else if(listing.ownerType.toLowerCase()=='importer'){ 65 | listing.ownerType='Retailer' 66 | if(productListing.status!='CHECKCOMPLETED'){ 67 | throw new Error('Exempt check pending!!'); 68 | } 69 | }else{ 70 | throw new Error('Please provide valid value for owner type'); 71 | } 72 | var newOwnerReg = null; 73 | return getParticipantRegistry('composer.food.supply.'+listing.ownerType) 74 | .then(function(registry) { 75 | newOwnerReg=registry; 76 | return registry.exists(listing.newOwner.getIdentifier()); 77 | }) 78 | .then(function(check) { 79 | if(check){ 80 | return getAssetRegistry('composer.food.supply.ProductListingContract') 81 | } 82 | else{ 83 | throw new Error('Please provide correct details for new owner'); 84 | } 85 | }) 86 | .then(function(listingRegistry) { 87 | return listingRegistry.update(productListing); 88 | }) 89 | .then(function(){ 90 | if(listing.ownerType == 'Retailer'){ 91 | productListing.products.forEach(function (item) { 92 | listing.newOwner.products.push(item); 93 | }); 94 | return newOwnerReg.update(listing.newOwner); 95 | }else{ 96 | return true; 97 | } 98 | }); 99 | } 100 | 101 | 102 | 103 | /** 104 | * Exempt check for the list of products by the regulator 105 | * @param {composer.food.supply.checkProducts} checkProducts 106 | * @transaction 107 | */ 108 | function checkProducts(listing) { 109 | var productListing = listing.productListing; 110 | if(productListing.status!='EXEMPTCHECKREQ' && productListing.status!='HAZARDANALYSISCHECKREQ'){ 111 | throw new Error('UnAuthorized transaction!!'); 112 | } 113 | var check=true; 114 | if(productListing.status=='EXEMPTCHECKREQ'){ 115 | if(listing.regulator.exemptedOrgIds.indexOf(productListing.supplier.orgId)==-1){ 116 | for (index in productListing.products) { 117 | var product =productListing.products[index]; 118 | if(listing.regulator.exemptedProductIds.indexOf(product.productId)==-1){ 119 | check=false; 120 | break; 121 | } 122 | } 123 | } 124 | } 125 | if(check){ 126 | productListing.status='CHECKCOMPLETED'; 127 | } 128 | else{ 129 | productListing.status='HAZARDANALYSISCHECKREQ'; 130 | } 131 | return getAssetRegistry('composer.food.supply.ProductListingContract') 132 | .then(function(listingRegistry) { 133 | return listingRegistry.update(productListing); 134 | }); 135 | 136 | } 137 | 138 | /** 139 | * Update exempted list 140 | * @param {composer.food.supply.updateExemptedList} updateExemptedList 141 | * @transaction 142 | */ 143 | function updateExemptedList(list) { 144 | if(list.newExemptedOrgIds!=null && list.newExemptedOrgIds.length>0){ 145 | list.newExemptedOrgIds.forEach(function (item) { 146 | list.regulator.exemptedOrgIds.push(item); 147 | }); 148 | } 149 | if(list.newExemptedProductIds!=null && list.newExemptedProductIds.length>0){ 150 | list.newExemptedProductIds.forEach(function (item) { 151 | list.regulator.exemptedProductIds.push(item); 152 | }); 153 | } 154 | return getParticipantRegistry('composer.food.supply.Regulator') 155 | .then(function(registry){ 156 | return registry.update(list.regulator); 157 | }); 158 | } 159 | -------------------------------------------------------------------------------- /models/base.cto: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | /** 16 | * A library of standard reusable types 17 | */ 18 | 19 | namespace composer.base 20 | 21 | abstract participant User { 22 | o String firstName optional 23 | o String lastName optional 24 | o String middleName optional 25 | o ContactDetails contactDetails optional 26 | } 27 | 28 | concept ContactDetails { 29 | o String email optional 30 | o String mobilePhone optional 31 | o String office optional 32 | o Address address optional 33 | } 34 | 35 | /** 36 | * A concept for a simple street address 37 | */ 38 | concept Address { 39 | o String city optional 40 | o String country optional 41 | o String locality optional 42 | o String region optional 43 | o String street optional 44 | o String street2 optional 45 | o String street3 optional 46 | o String postalCode optional 47 | o String postOfficeBoxNumber optional 48 | } 49 | -------------------------------------------------------------------------------- /models/foodSupply.cto: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | namespace composer.food.supply 16 | import composer.base.User 17 | 18 | enum Status{ 19 | o INITIALREQUEST 20 | o EXEMPTCHECKREQ 21 | o HAZARDANALYSISCHECKREQ 22 | o CHECKCOMPLETED 23 | } 24 | 25 | concept Product { 26 | o String productId 27 | o String quantity 28 | o String countryId 29 | } 30 | 31 | participant Retailer identified by retailerId extends User { 32 | o String retailerId 33 | o Product[] products 34 | } 35 | 36 | participant Importer identified by importerId extends User { 37 | o String importerId 38 | } 39 | 40 | participant Supplier identified by supplierId extends User { 41 | o String supplierId 42 | o String countryId 43 | o String orgId 44 | } 45 | 46 | participant Regulator identified by regulatorId{ 47 | o String regulatorId 48 | o String location 49 | o String[] exemptedOrgIds 50 | o String[] exemptedProductIds 51 | } 52 | 53 | asset ProductListingContract identified by listingtId { 54 | o String listingtId 55 | o Status status 56 | o Product[] products 57 | --> User owner 58 | --> Supplier supplier 59 | } 60 | 61 | 62 | // Supplier creates a product listing contract for the list of products 63 | transaction createProductListing{ 64 | o String[] products 65 | --> User user 66 | } 67 | 68 | // Supplier --> Importer 69 | // Importer --> Retailer 70 | transaction transferListing{ 71 | o String ownerType 72 | --> User newOwner 73 | --> ProductListingContract productListing 74 | } 75 | 76 | // Importer --> Regulator w/o hazard analysis i.e. iniitial check. onSuccess transfer assests to retailer on failure send back to importer for hazard analysis 77 | // Importer --> Regulator with hazard analysis. Assumes the Importer has sent the hazard analysis report to Regulator. onSuccess transfer assests to retailer 78 | transaction checkProducts{ 79 | --> Regulator regulator 80 | --> ProductListingContract productListing 81 | } 82 | 83 | transaction updateExemptedList{ 84 | o String[] newExemptedOrgIds 85 | o String[] newExemptedProductIds 86 | --> Regulator regulator 87 | } 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "food-supply", 3 | "version": "0.0.1", 4 | "description": "Sample food supplier verification network", 5 | "scripts": { 6 | "prepublish": "mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/food-supply.bna", 7 | "test": "mocha --recursive" 8 | }, 9 | "author": "Admin", 10 | "email": "admin@example.org", 11 | "license": "Apache-2.0", 12 | "devDependencies": { 13 | "composer-admin": "^0.19.1", 14 | "composer-cli": "^0.19.1", 15 | "composer-client": "^0.19.1", 16 | "composer-common": "^0.19.1", 17 | "composer-connector-embedded": "^0.19.1", 18 | "composer-cucumber-steps": "^0.19.1", 19 | "chai": "latest", 20 | "chai-as-promised": "latest", 21 | "cucumber": "^2.2.0", 22 | "eslint": "latest", 23 | "nyc": "latest", 24 | "mkdirp": "latest", 25 | "mocha": "latest" 26 | }, 27 | "dependencies": { 28 | "express": "^4.16.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /permissions.acl: -------------------------------------------------------------------------------- 1 | /** 2 | * Access Control List for the bidding network. 3 | */ 4 | 5 | rule SupplierView { 6 | description: "Allow supplier read access to all importer resources" 7 | participant: "composer.food.supply.Supplier" 8 | operation: READ 9 | resource: "composer.food.supply.Importer" 10 | action: ALLOW 11 | } 12 | 13 | rule SupplierCanViewOwnData { 14 | description: "Allow supplier access to his own data" 15 | participant(m): "composer.food.supply.Supplier" 16 | operation: ALL 17 | resource(v): "composer.food.supply.Supplier" 18 | condition: (v.getIdentifier() == m.getIdentifier()) 19 | action: ALLOW 20 | } 21 | 22 | rule ImporterCanViewRetailerData { 23 | description: "Allow importer read access to Retailer resources" 24 | participant: "composer.food.supply.Importer" 25 | operation: READ 26 | resource: "composer.food.supply.Retailer" 27 | action: ALLOW 28 | } 29 | 30 | rule ImporterCanViewRegulatorData { 31 | description: "Allow importer read access to Regulator resources" 32 | participant: "composer.food.supply.Importer" 33 | operation: READ 34 | resource: "composer.food.supply.Regulator" 35 | action: ALLOW 36 | } 37 | 38 | rule ImporterCanSupplierDataView { 39 | description: "Allow importer read access to Supplier resources" 40 | participant: "composer.food.supply.Importer" 41 | operation: READ 42 | resource: "composer.food.supply.Supplier" 43 | action: ALLOW 44 | } 45 | 46 | rule ImporterCanViewOwnData { 47 | description: "Allow importer access to his own data" 48 | participant(m): "composer.food.supply.Importer" 49 | operation: ALL 50 | resource(v): "composer.food.supply.Importer" 51 | condition: (v.getIdentifier() == m.getIdentifier()) 52 | action: ALLOW 53 | } 54 | 55 | rule RetailerView { 56 | description: "Allow retailer read access to all importer resources" 57 | participant: "composer.food.supply.Retailer" 58 | operation: READ 59 | resource: "composer.food.supply.Importer" 60 | action: ALLOW 61 | } 62 | 63 | rule RetailerCanViewOwnData { 64 | description: "Allow retailer access to his own data" 65 | participant(m): "composer.food.supply.Retailer" 66 | operation: ALL 67 | resource(v): "composer.food.supply.Retailer" 68 | condition: (v.getIdentifier() == m.getIdentifier()) 69 | action: ALLOW 70 | } 71 | 72 | 73 | rule RegulatorView { 74 | description: "Allow regulator read access to all resources" 75 | participant: "composer.food.supply.Regulator" 76 | operation: READ 77 | resource: "composer.food.supply.*" 78 | action: ALLOW 79 | } 80 | 81 | rule CreateProductListing{ 82 | description: "Allow Supplier to create new product listing" 83 | participant(m): "composer.food.supply.Supplier" 84 | operation: CREATE 85 | resource(v): "composer.food.supply.createProductListing" 86 | condition: (v.user.getIdentifier() == m.getIdentifier()) 87 | action: ALLOW 88 | } 89 | 90 | rule ProductListingOwner { 91 | description: "Allow the owner of a product listing total access to their listing" 92 | participant(m): "composer.food.supply.*" 93 | operation: ALL 94 | resource(v): "composer.food.supply.ProductListingContract" 95 | condition: (v.owner.getIdentifier() == m.getIdentifier()) 96 | action: ALLOW 97 | } 98 | 99 | rule TransferListing{ 100 | description: "Allow the owner of a product listing to transfer the listing" 101 | participant(m): "composer.food.supply.*" 102 | operation: CREATE 103 | resource(v): "composer.food.supply.transferListing" 104 | condition: (v.productListing.owner.getIdentifier() == m.getIdentifier()) 105 | action: ALLOW 106 | } 107 | 108 | rule TransferListingUpdateRetailerProducts{ 109 | description: "Allow members to bid for the product" 110 | participant(m): "composer.food.supply.*" 111 | operation: UPDATE 112 | resource(v): "composer.food.supply.Retailer" 113 | transaction(tx): "composer.food.supply.transferListing" 114 | condition: (tx.productListing.owner.getIdentifier() == m.getIdentifier()) 115 | action: ALLOW 116 | } 117 | 118 | 119 | rule CheckProducts{ 120 | description: "Allow members to bid for the product" 121 | participant(m): "composer.food.supply.Importer" 122 | operation: CREATE 123 | resource(v): "composer.food.supply.checkProducts" 124 | condition: (v.productListing.owner.getIdentifier() == m.getIdentifier()) 125 | action: ALLOW 126 | } 127 | 128 | rule UpdateExemptedList{ 129 | description: "Allow members to bid for the product" 130 | participant(m): "composer.food.supply.Regulator" 131 | operation: CREATE 132 | resource(v): "composer.food.supply.updateExemptedList" 133 | condition: (v.regulator.getIdentifier() == m.getIdentifier()) 134 | action: ALLOW 135 | } 136 | 137 | rule SystemACL { 138 | description: "System ACL to permit all access" 139 | participant: "ANY" 140 | operation: ALL 141 | resource: "org.hyperledger.composer.system.**" 142 | action: ALLOW 143 | } 144 | 145 | rule Default { 146 | description: "Allow all participants access to all resources" 147 | participant: "ANY" 148 | operation: ALL 149 | resource: "composer.food.supply.*" 150 | action: ALLOW 151 | } 152 | 153 | rule NetworkAdminSystem { 154 | description: "Grant business network administrators full access to system resources" 155 | participant: "org.hyperledger.composer.system.*" 156 | operation: ALL 157 | resource: "org.hyperledger.composer.system.**" 158 | action: ALLOW 159 | } 160 | --------------------------------------------------------------------------------