├── .gitignore ├── DISCLAIMER ├── LICENSE ├── Makefile ├── README.md ├── apps ├── RC_circuit │ ├── SimpleElectrical_Examples_RC.fmu │ ├── config.json │ └── readme.md ├── README.md ├── config_template.json ├── hello_world │ ├── HelloWorld.fmu │ ├── config.json │ └── readme.md └── simple_building │ ├── SimpleBuilding_Room.fmu │ ├── config.json │ ├── dashboard.json │ ├── inputs │ ├── SFO_weather_data_summer.csv │ └── SFO_weather_data_winter.csv │ └── readme.md ├── configs ├── cors_headers.json ├── integration_options_response.json └── method_options_response.json ├── generate_api_template.py ├── gui ├── .babelrc ├── README.md ├── deploy-gh-pages.js ├── deploy.sh ├── package.json ├── server.js ├── src │ ├── app │ │ ├── Main.js │ │ ├── actions │ │ │ ├── action-types.js │ │ │ ├── api-settings-actions.js │ │ │ ├── config-actions.js │ │ │ ├── dashboard-actions.js │ │ │ ├── error-message-actions.js │ │ │ ├── home-tabs-actions.js │ │ │ ├── loading-actions.js │ │ │ ├── model-actions.js │ │ │ ├── model-input-actions.js │ │ │ ├── model-parameters-actions.js │ │ │ ├── model-simulation-actions.js │ │ │ ├── plot-results-actions.js │ │ │ └── readme-actions.js │ │ ├── api │ │ │ └── lambda-sim-api.js │ │ ├── classes │ │ │ ├── dashboard.js │ │ │ └── model-description.js │ │ ├── components │ │ │ ├── constants.js │ │ │ ├── dashboard-plot.js │ │ │ ├── dashboard-select-field.js │ │ │ ├── dashboard-text-field.js │ │ │ ├── footer.js │ │ │ ├── loading-spinner.js │ │ │ ├── navbar.js │ │ │ ├── plot-selection-menu.js │ │ │ ├── plot.js │ │ │ ├── select-api-form.js │ │ │ ├── simulate-form.js │ │ │ ├── simulation-inputs-dialog.js │ │ │ ├── simulation-parameters-dialog.js │ │ │ ├── simulation-settings-dialog.js │ │ │ ├── tab-dashboard.js │ │ │ ├── tab-info.js │ │ │ ├── tab-model-description.js │ │ │ └── tab-simulation.js │ │ ├── containers │ │ │ ├── dashboard-plot-container.js │ │ │ ├── home-container.js │ │ │ ├── loading-spinner-container.js │ │ │ ├── navbar-container.js │ │ │ ├── plot-container.js │ │ │ ├── plot-selection-menu-container.js │ │ │ ├── simulation-inputs-dialog-container.js │ │ │ ├── simulation-parameters-dialog-container.js │ │ │ ├── tab-dashboard-container.js │ │ │ ├── tab-info-container.js │ │ │ ├── tab-model-description-container.js │ │ │ └── tab-simulation-container.js │ │ ├── index.js │ │ ├── layouts │ │ │ └── main-layout.js │ │ ├── reducers │ │ │ ├── api-settings-reducer.js │ │ │ ├── config-definition-reducer.js │ │ │ ├── dashboard-definition-reducer.js │ │ │ ├── error-message-reducer.js │ │ │ ├── home-tabs-reducer.js │ │ │ ├── index.js │ │ │ ├── loading-reducer.js │ │ │ ├── model-description-reducer.js │ │ │ ├── model-inputs-reducer.js │ │ │ ├── model-parameters-reducer.js │ │ │ ├── model-simulation-reducer.js │ │ │ ├── plot-results-reducer.js │ │ │ └── readme-reducer.js │ │ ├── router.js │ │ ├── store.js │ │ ├── style │ │ │ ├── components │ │ │ │ ├── _color.scss │ │ │ │ ├── _global.scss │ │ │ │ ├── _grid.scss │ │ │ │ ├── _normalize.scss │ │ │ │ ├── _prefixer.scss │ │ │ │ ├── _preloader.scss │ │ │ │ ├── _typography.scss │ │ │ │ └── _variables.scss │ │ │ └── materialize-light.scss │ │ ├── utils │ │ │ └── redirect.js │ │ └── views │ │ │ ├── about.js │ │ │ ├── api-docs.js │ │ │ └── home.js │ └── www │ │ ├── fonts │ │ └── roboto │ │ │ ├── Roboto-Bold.eot │ │ │ ├── Roboto-Bold.ttf │ │ │ ├── Roboto-Bold.woff │ │ │ ├── Roboto-Bold.woff2 │ │ │ ├── Roboto-Light.eot │ │ │ ├── Roboto-Light.ttf │ │ │ ├── Roboto-Light.woff │ │ │ ├── Roboto-Light.woff2 │ │ │ ├── Roboto-Medium.eot │ │ │ ├── Roboto-Medium.ttf │ │ │ ├── Roboto-Medium.woff │ │ │ ├── Roboto-Medium.woff2 │ │ │ ├── Roboto-Regular.eot │ │ │ ├── Roboto-Regular.ttf │ │ │ ├── Roboto-Regular.woff │ │ │ ├── Roboto-Regular.woff2 │ │ │ ├── Roboto-Thin.eot │ │ │ ├── Roboto-Thin.ttf │ │ │ ├── Roboto-Thin.woff │ │ │ └── Roboto-Thin.woff2 │ │ ├── images │ │ ├── Lambda-logo.svg │ │ ├── Lambda-logo@242.png │ │ ├── favicon-32x32.png │ │ ├── lsim-icon-small.png │ │ └── lsim-icon-white-small.png │ │ ├── index.html │ │ └── main.css ├── webpack-dev-server.config.js ├── webpack-production.config.js └── webpack.config.js ├── iam_roles ├── assume_role_policy.json ├── role_access_logs_policy.json └── role_access_s3_policy.json ├── images ├── diagram.png ├── diagram.svg ├── lsim-icon-white.png ├── lsim-icon-white.svg ├── lsim-icon.png ├── lsim-icon.svg ├── lsim.png └── lsim.svg ├── lambda_function.py ├── setup_s3.py └── templates └── swagger_api_template.json /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | .DS_Store 3 | *~ 4 | *.pyc 5 | *.zip 6 | *.json 7 | node_modules/ 8 | build/ 9 | *.log 10 | gui/src/www/*.css -------------------------------------------------------------------------------- /DISCLAIMER: -------------------------------------------------------------------------------- 1 | ## Disclaimer 2 | 3 | λ-Sim allows to create a REST API implemented on top of 4 | Amazon Web Services (AWS) such as 5 | - lambda 6 | - apigateway, 7 | - S3, and 8 | - cloudwatch 9 | 10 | **YOU MAY ENCOUNTER EXPENSES WHEN USING AWS SERVICE** 11 | 12 | > By default λ-Sim makes your model open to the public, anyone will be able to 13 | > access your model and therefore your account will 14 | > be charged for the use of AWS services and resources required to serve the 15 | > requests. I suggest you to carefully read and review AWS charges and policies. 16 | > THE AUTHOR/S OF THIS PROJECT ARE NOT RESPONSIBLE IN ANY WAY FOR THE USE 17 | > AND CHARGES OF AMAZON WEB SERVICES (AWS) ON YOUR ACCOUNT OR ANY THIRD PARTY 18 | > ACCOUNT YOU USE. IT IS YOUR RESPONSIBILITY TO MONITOR THE EXPENSES 19 | > AND TRACK THE USAGE OF YOUR API. USE THIS PROJECT AT YOUR OWN RISK. 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marco Bonvini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This Makefile contains instructions 3 | # that allows to convert a simulation model 4 | # into an REST API service hosted on AWS lambda and 5 | # publicly available trough apigateway. 6 | # 7 | 8 | $(eval CURDIR := $(shell pwd)) 9 | VENV_DIR=$(CURDIR)/venv 10 | PYTHON=$(VENV_DIR)/bin/python 11 | PIP=$(VENV_DIR)/bin/pip 12 | 13 | PROFILE=default 14 | AWS=$(VENV_DIR)/bin/aws --profile=$(PROFILE) 15 | 16 | APP_DIR=$(CURDIR)/apps/hello_world 17 | CONFIG_FILE=config.json 18 | DASHBOARD_FILE=dashboard.json 19 | README_FILE=readme.md 20 | PATH_CONFIG_FILE=$(APP_DIR)/$(CONFIG_FILE) 21 | 22 | VENV_ZIP=LambdaFmiVenvPython27.zip 23 | VENV_ZIP_URL=https://s3-us-west-2.amazonaws.com/lambda-sim/$(VENV_ZIP) 24 | 25 | APP_ZIP_NAME=$(CURDIR)/LambdaSimApp.zip 26 | LAMBDA_FUNCTION=lambda_function.py 27 | HANDLER=lambda_function.lambda_handler 28 | LS_LOG_LEVEL="10" 29 | 30 | IAM_ROLE_NAME=LambdaSimRole 31 | PERMISSION_FOR_LAMBDA_SIM_S3=PermissionForLambdaSimS3 32 | PERMISSION_FOR_LAMBDA_SIM_LOGS=PermissionForLambdaSimLogs 33 | IAM_ROLE_ASSUME_POLICY_DOCUMENT=$(CURDIR)/iam_roles/assume_role_policy.json 34 | IAM_ROLE_ACCESS_S3_POLICY_DOCUMENT=$(CURDIR)/iam_roles/role_access_s3_policy.json 35 | IAM_ROLE_ACCESS_LOGS_POLICY_DOCUMENT_TEMPLATE=$(CURDIR)/iam_roles/role_access_logs_policy.json 36 | IAM_ROLE_ACCESS_LOGS_POLICY_DOCUMENT=role_access_logs_policy.json 37 | 38 | METHOD_OPTIONS_RESPONSE=file://configs/method_options_response.json 39 | INTEGRATION_OPTIONS_RESPONSE=file://configs/integration_options_response.json 40 | CORS_HEADERS=file://configs/cors_headers.json 41 | 42 | SWAGGER_API_TEMPLATE=$(CURDIR)/templates/swagger_api_template.json 43 | SWAGGER_API_TEMPLATE_CORS=$(CURDIR)/templates/swagger_api_template_cors.json 44 | 45 | # Access the data in the config file associated to the app 46 | #-- Account 47 | ACCOUNT_ID := $(shell aws --profile=marco iam list-users | jq '.Users[0].Arn / ":" | .[4]' | sed 's/^"\(.*\)".*/\1/') 48 | AWS_REGION := $(shell more $(PATH_CONFIG_FILE) | jq '.aws.region' | sed 's/^"\(.*\)".*/\1/') 49 | #-- Lambda 50 | FUNCTION_NAME := $(shell more $(PATH_CONFIG_FILE) | jq '.lambda.function_name' | sed 's/^"\(.*\)".*/\1/') 51 | DESCRIPTION := $(shell more $(PATH_CONFIG_FILE) | jq '.lambda.description') 52 | TIMEOUT := $(shell more $(PATH_CONFIG_FILE) | jq '.lambda.timeout') 53 | MEMORY_SIZE := $(shell more $(PATH_CONFIG_FILE) | jq '.lambda.memory_size') 54 | #-- Apigateway 55 | API_NAME := $(shell more $(PATH_CONFIG_FILE) | jq '.api.name') 56 | API_DESCRIPTION := $(shell more $(PATH_CONFIG_FILE) | jq '.api.description') 57 | 58 | define random_uuid 59 | $(shell ./venv/bin/python -c "import sys,uuid; sys.stdout.write(uuid.uuid4().hex)" | pbcopy && pbpaste && echo) 60 | endef 61 | 62 | all: help 63 | .PHONY: all 64 | 65 | 66 | install: ## Install the dependencies needed to run this tool 67 | virtualenv ./venv 68 | $(PIP) install --upgrade pip 69 | $(PIP) install awscli 70 | $(PIP) install boto3 71 | $(PIP) install jinja2 72 | .PHONY: install 73 | 74 | 75 | uninstall: ## Remove the virtual environment 76 | rm -rf $(VENV_DIR) 77 | .PHONY: uninstall 78 | 79 | 80 | create_bucket: ## Create the bucket that stores the FMUs 81 | $(PYTHON) setup_s3.py --create \ 82 | --profile $(PROFILE) \ 83 | --region $(AWS_REGION) 84 | .PHONY: create_bucket 85 | 86 | 87 | get_bucket_name: ## Get the name of the bucket that stores the FMUs 88 | $(PYTHON) setup_s3.py --get_name --profile $(PROFILE) 89 | .PHONY: create_bucket 90 | 91 | 92 | delete_bucket: ## Delete the bucket that stores the FMUs 93 | $(PYTHON) setup_s3.py --delete --profile $(PROFILE) 94 | .PHONY: delete_bucket 95 | 96 | 97 | copy_fmu: create_bucket ## Copies the FMU to S3 98 | $(PYTHON) setup_s3.py --copy $(APP_DIR) --profile $(PROFILE) 99 | .PHONY: copy_fmu 100 | 101 | 102 | build_app: ## Builds the zip file that contains the lambda function 103 | if [ ! -f "./$(VENV_ZIP)" ]; then wget $(VENV_ZIP_URL); fi 104 | cp ./$(VENV_ZIP) $(APP_ZIP_NAME) 105 | zip -g $(APP_ZIP_NAME) $(LAMBDA_FUNCTION) 106 | cd $(APP_DIR) && zip -g $(APP_ZIP_NAME) $(CONFIG_FILE) 107 | if [ -f "$(APP_DIR)/$(DASHBOARD_FILE)" ]; then cd $(APP_DIR) && zip -g $(APP_ZIP_NAME) $(DASHBOARD_FILE); fi 108 | if [ -f "$(APP_DIR)/$(README_FILE)" ]; then cd $(APP_DIR) && zip -g $(APP_ZIP_NAME) $(README_FILE); fi 109 | .PHONY: build_app 110 | 111 | 112 | create_logs_role: ## Create the IAM role specific for the log 113 | more $(IAM_ROLE_ACCESS_LOGS_POLICY_DOCUMENT_TEMPLATE) | jq '\ 114 | .Statement[0].Resource = "arn:aws:logs:$(AWS_REGION):$(ACCOUNT_ID):*" | \ 115 | .Statement[1].Resource[0] = "arn:aws:logs:$(AWS_REGION):$(ACCOUNT_ID):log-group:/aws/lambda/$(FUNCTION_NAME):*"' \ 116 | > $(APP_DIR)/$(IAM_ROLE_ACCESS_LOGS_POLICY_DOCUMENT) 117 | PHONY: create_logs_role 118 | 119 | 120 | create_iam_role: create_logs_role ## Create the IAM role (valid for a specific lambda function) 121 | $(AWS) iam create-role \ 122 | --role-name $(IAM_ROLE_NAME)-$(FUNCTION_NAME) \ 123 | --assume-role-policy-document file://$(IAM_ROLE_ASSUME_POLICY_DOCUMENT) \ 124 | > $(APP_DIR)/role_profile.json 125 | $(AWS) iam put-role-policy \ 126 | --role-name $(IAM_ROLE_NAME)-$(FUNCTION_NAME) \ 127 | --policy-name $(PERMISSION_FOR_LAMBDA_SIM_S3) \ 128 | --policy-document file://$(IAM_ROLE_ACCESS_S3_POLICY_DOCUMENT) 129 | $(AWS) iam put-role-policy \ 130 | --role-name $(IAM_ROLE_NAME)-$(FUNCTION_NAME) \ 131 | --policy-name $(PERMISSION_FOR_LAMBDA_SIM_LOGS) \ 132 | --policy-document file://$(APP_DIR)/$(IAM_ROLE_ACCESS_LOGS_POLICY_DOCUMENT) 133 | .PHONY: create_iam_role 134 | 135 | 136 | create_function: copy_fmu build_app create_iam_role ## Create the lambda function by uploading the zip file 137 | $(eval IAM_ROLE_ARN := $(shell more $(APP_DIR)/role_profile.json | jq '.Role.Arn')) 138 | $(eval BUCKET_NAME := $(shell $(PYTHON) setup_s3.py --get_name --profile $(PROFILE))) 139 | $(AWS) lambda create-function \ 140 | --region $(AWS_REGION) \ 141 | --function-name $(FUNCTION_NAME) \ 142 | --runtime python2.7 \ 143 | --role $(IAM_ROLE_ARN) \ 144 | --handler $(HANDLER) \ 145 | --description $(DESCRIPTION) \ 146 | --timeout $(TIMEOUT) \ 147 | --memory-size $(MEMORY_SIZE) \ 148 | --zip-file fileb://$(APP_ZIP_NAME) \ 149 | --environment '{"Variables": {"USER": "ec2-user", "LS_LOG_LEVEL": $(LS_LOG_LEVEL), "S3_FMU_BUCKET_NAME": "$(BUCKET_NAME)"}}' \ 150 | > $(APP_DIR)/lambda_function.json 151 | .PHONY: create_function 152 | 153 | 154 | update_function_code: build_app ## Command to update the code of an existing lambda function 155 | $(eval IAM_ROLE_ARN := $(shell more $(APP_DIR)/role_profile.json | jq '.Role.Arn')) 156 | $(eval BUCKET_NAME := $(shell $(PYTHON) setup_s3.py --get_name --profile $(PROFILE))) 157 | $(AWS) lambda update-function-code \ 158 | --region $(AWS_REGION) \ 159 | --function-name $(FUNCTION_NAME) \ 160 | --zip-file fileb://$(APP_ZIP_NAME) \ 161 | > $(APP_DIR)/lambda_function.json 162 | .PHONY: redeploy_function 163 | 164 | 165 | prepare_api_spec: 166 | $(PYTHON) ./generate_api_template.py \ 167 | $(APP_DIR)/lambda_function.json \ 168 | $(APP_DIR)/rest_api_spec.json 169 | 170 | submit_api_spec: prepare_api_spec 171 | $(AWS) apigateway import-rest-api \ 172 | --region $(AWS_REGION) \ 173 | --body file://$(APP_DIR)/rest_api_spec.json > $(APP_DIR)/rest_api.json 174 | 175 | get_function_arn_uri: ## Get the ARN and URI of the lambda function 176 | @$(eval FUNCTION_ARN := $(shell more $(APP_DIR)/lambda_function.json | jq '.FunctionArn' | sed 's/^"\(.*\)".*/\1/')) 177 | @$(eval FUNCTION_URI := arn:aws:apigateway:$(AWS_REGION):lambda:path/2015-03-31/functions/$(FUNCTION_ARN)/invocations) 178 | .PHONY: get_function_arn_uri 179 | 180 | get_root_resource_id: ## Get the REST root resource 181 | @$(eval REST_API_ID := $(shell more $(APP_DIR)/rest_api.json | jq '.id')) 182 | @$(AWS) apigateway get-resources \ 183 | --region $(AWS_REGION) \ 184 | --rest-api-id $(REST_API_ID) > $(APP_DIR)/resources.json 185 | .PHONY: get_resource_id 186 | 187 | 188 | get_resource_id: get_root_resource_id ## Get the resource id of the "/" and of "/" 189 | @$(eval REST_API_ID := $(shell more $(APP_DIR)/rest_api.json | jq '.id' | sed 's/^"\(.*\)".*/\1/')) 190 | @$(eval ROOT_RESOURCE_ID := $(shell more $(APP_DIR)/resources.json | jq '.items[] | select(.path == "/") | .id')) 191 | @$(eval RESOURCE_ID := $(shell more $(APP_DIR)/resources.json | jq '.items[] | select(.parentId == $(ROOT_RESOURCE_ID)) | .id')) 192 | .PHONY: get_resource_id 193 | 194 | 195 | enable_api: get_function_arn_uri get_resource_id ## Enable the lambda function to be called by API Gateway 196 | $(AWS) lambda add-permission \ 197 | --region $(AWS_REGION) \ 198 | --function-name $(FUNCTION_ARN) \ 199 | --source-arn 'arn:aws:execute-api:$(AWS_REGION):$(ACCOUNT_ID):$(REST_API_ID)/*/*/$(FUNCTION_NAME)' \ 200 | --principal apigateway.amazonaws.com \ 201 | --statement-id $(call random_uuid) \ 202 | --action lambda:InvokeFunction 203 | $(AWS) lambda add-permission \ 204 | --region $(AWS_REGION) \ 205 | --function-name $(FUNCTION_ARN) \ 206 | --source-arn 'arn:aws:execute-api:$(AWS_REGION):$(ACCOUNT_ID):$(REST_API_ID)/prod/ANY/$(FUNCTION_NAME)' \ 207 | --principal apigateway.amazonaws.com \ 208 | --statement-id $(call random_uuid) \ 209 | --action lambda:InvokeFunction 210 | .PHONY: enable_api 211 | 212 | 213 | deploy_api: get_resource_id ## Deploy the API 214 | $(AWS) apigateway create-deployment \ 215 | --region $(AWS_REGION) \ 216 | --rest-api-id $(REST_API_ID) \ 217 | --stage-name prod \ 218 | --description "Deploy API to production stage" > $(APP_DIR)/deployed.json 219 | .PHONY: deploy_api 220 | 221 | 222 | expose_function: submit_api_spec enable_api deploy_api ## Exposes the lambda function via API Gateway 223 | .PHONY: expose_function 224 | 225 | 226 | get_url: get_resource_id ## Get the URL of the API gateway API endpoint 227 | @echo The URL of the API is: 228 | @echo https://$(REST_API_ID).execute-api.$(AWS_REGION).amazonaws.com/prod/$(FUNCTION_NAME) 229 | .PHONY: get_url 230 | 231 | 232 | help: ## Show this help message 233 | @perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 234 | .PHONY: help 235 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # λ-Sim 2 | 3 | λ-Sim is a tool that converts simulation models into REST APIs. 4 | The figure below gives an idea of what the tool does for you 5 | 6 | 1. It takes a simulation model exported from Matlab or a Modelica 7 | tool and a JSON configuration file, 8 | 3. It automatically generates a Lambda function that includes 9 | a complete REST API to run the simulation model. 10 | 11 | ![λ-Sim Schematic diagram](https://github.com/mbonvini/LambdaSim/raw/master/images/diagram.png) 12 | 13 | With λ-Sim you can build a MaaS (Model as a Service) application where 14 | people can access your model, run simulations and visualize results. 15 | 16 | λ-Sim is built on top of AWS [Lambda](https://aws.amazon.com/lambda/), 17 | [API-gateway](https://aws.amazon.com/api-gateway/), [S3](https://aws.amazon.com/s3/) 18 | and [Cloudwatch](https://aws.amazon.com/cloudwatch/). 19 | These AWS services allows you to build an application that automatically manages 20 | security updates, scale as needed based on the incoming traffic, monitor performances, 21 | and if necessary apply restrictions to users. 22 | And no charges when your code is not running. 23 | 24 | λ-Sim has a GUI available at [https://mbonvini.github.io/LambdaSim/](https://mbonvini.github.io/LambdaSim/). 25 | Here you can load APIs created with λ-Sim, simulate the models and visualize 26 | the results. 27 | 28 | Have a look at these two examples created with λ-Sim 29 | 30 | - [Hello World](https://mbonvini.github.io/LambdaSim/?api=https://09r1151hxj.execute-api.us-west-2.amazonaws.com/prod/hello_world) 31 | - [Building Energy Model](https://mbonvini.github.io/LambdaSim/?api=https://0m43gmgny4.execute-api.us-west-1.amazonaws.com/prod/simple_building) 32 | 33 | For information and documentation check out the [wiki](https://github.com/mbonvini/LambdaSim/wiki). 34 | 35 | For comments and questions you can create an issue, contributions are 36 | welcome. 37 | 38 | ## Aknowledgements 39 | 40 | I's like to thank [Modelon](http://www.modelon.com) for making available their tools, 41 | without them this project wouldn't exist. 42 | 43 | ## Disclaimer 44 | 45 | λ-Sim allows to create a REST API implemented on top of 46 | Amazon Web Services (AWS) such as 47 | - lambda 48 | - apigateway, 49 | - S3, and 50 | - cloudwatch 51 | 52 | **YOU MAY ENCOUNTER EXPENSES WHEN USING AWS SERVICE** 53 | 54 | > By default λ-Sim makes your model open to the public, anyone will be able to 55 | > access your model and therefore your account will 56 | > be charged for the use of AWS services and resources required to serve the 57 | > requests. I suggest you to carefully read and review AWS charges and policies. 58 | > THE AUTHOR/S OF THIS PROJECT ARE NOT RESPONSIBLE IN ANY WAY FOR THE USE 59 | > AND CHARGES OF AMAZON WEB SERVICES (AWS) ON YOUR ACCOUNT OR ANY THIRD PARTY 60 | > ACCOUNT YOU USE. IT IS YOUR RESPONSIBILITY TO MONITOR THE EXPENSES 61 | > AND TRACK THE USAGE OF YOUR API. USE THIS PROJECT AT YOUR OWN RISK. 62 | 63 | If you're interested in limiting access to the REST API please have a 64 | look at the AWS documentation 65 | [apigateway-control-access-to-api](http://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-control-access-to-api.html) 66 | -------------------------------------------------------------------------------- /apps/RC_circuit/SimpleElectrical_Examples_RC.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/apps/RC_circuit/SimpleElectrical_Examples_RC.fmu -------------------------------------------------------------------------------- /apps/RC_circuit/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "aws":{ 3 | "region": "us-west-1" 4 | }, 5 | "lambda":{ 6 | "function_name": "rc_circuit", 7 | "timeout": 60, 8 | "description": "A simple function that simulates an RC circuit", 9 | "memory_size": 256, 10 | "access_control_allow_origin": "*" 11 | }, 12 | "s3": { 13 | "folder": "rc_circuit" 14 | }, 15 | "model":{ 16 | "fmu_name": "SimpleElectrical_Examples_RC.fmu", 17 | "input_files": [], 18 | "options": {}, 19 | "simulation_time": { 20 | "min": 0.0, 21 | "max": 100.0 22 | }, 23 | "n_points": { 24 | "default": 200, 25 | "min": 100, 26 | "max": 500 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /apps/RC_circuit/readme.md: -------------------------------------------------------------------------------- 1 | ## Simple RC circuit 2 | 3 | This is a simple RC circuit. 4 | 5 | The source code of the model is available at 6 | [ModelicaInAction/modelica/SimpleElectrical/Examples/RC.mo](https://github.com/mbonvini/ModelicaInAction/blob/master/modelica/SimpleElectrical/Examples/RC.mo). -------------------------------------------------------------------------------- /apps/README.md: -------------------------------------------------------------------------------- 1 | # Apps 2 | 3 | This folder contains sample model and configuration files 4 | used to generate APIs. 5 | 6 | In order to create an API with λ-Sim a folder must 7 | contain the following files 8 | - `*.fmu` - the model to simulate 9 | - `config.json` - the configuration file that allows to configure 10 | the API and specify default options for the model 11 | 12 | For the `config.json` file please take a look at the `config_template.json` 13 | file located in this folder. 14 | 15 | Optional files that are shipped with the API and are used 16 | to render information using the [λ-Sim web GUI](https://mbonvini.github.io/LambdaSim/) 17 | are 18 | - `readme.md` - a markdown file that describes the model and is 19 | meant to provide basic information about the model and its usage, 20 | - `dashboard.json` - a JSON file that specifies the layout of a 21 | dashboard that allows to visualize simulation results and select or 22 | change model parameters. -------------------------------------------------------------------------------- /apps/config_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "aws":{ 3 | "region": "" 4 | }, 5 | "lambda":{ 6 | "function_name": "", 7 | "timeout": , 8 | "description": "", 9 | "memory_size": , 10 | "access_control_allow_origin": "*" 11 | }, 12 | "s3": { 13 | "folder": "" 14 | }, 15 | "model":{ 16 | "fmu_name": "", 17 | "input_files": [ 18 | "", ..., "" 19 | ], 20 | "options": {}, 21 | "simulation_time": { 22 | "min": 0.0, 23 | "max": 1000.0 24 | }, 25 | "n_points": { 26 | "default": 500, 27 | "min": 300, 28 | "max": 1000 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /apps/hello_world/HelloWorld.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/apps/hello_world/HelloWorld.fmu -------------------------------------------------------------------------------- /apps/hello_world/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "aws":{ 3 | "region": "us-west-2" 4 | }, 5 | "lambda":{ 6 | "function_name": "hello_world", 7 | "timeout": 60, 8 | "description": "A simple function that simulates a first order model", 9 | "memory_size": 128, 10 | "access_control_allow_origin": "*" 11 | }, 12 | "s3": { 13 | "folder": "hello_world" 14 | }, 15 | "api": { 16 | "name": "hello_world_rest_api", 17 | "description": "An API gateway for lambda function hello_world" 18 | }, 19 | "model":{ 20 | "fmu_name": "HelloWorld.fmu", 21 | "input_files": [], 22 | "options": {}, 23 | "simulation_time": { 24 | "min": 0.0, 25 | "max": 1000.0 26 | }, 27 | "n_points": { 28 | "default": 500, 29 | "min": 300, 30 | "max": 1000 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /apps/hello_world/readme.md: -------------------------------------------------------------------------------- 1 | ## Hello World model 2 | 3 | This is a simple model that implements a first order 4 | differential equations 5 | 6 | ``` 7 | model HelloWorld "The simplest differential equation ever" 8 | Real x "The unknown variable"; 9 | constant Real a = -2.0 "Constant that characterizes the model"; 10 | parameter Real x_start = 5.0 "Initial value of the variable x"; 11 | initial equation 12 | // Define initial conditions here... 13 | x = x_start; 14 | equation 15 | // Write the equations here... 16 | der(x) = a*x; 17 | end HelloWorld; 18 | ``` 19 | 20 | The source code of the model is available at 21 | [ModelicaInAction/modelica/HelloWorld.mo](https://github.com/mbonvini/ModelicaInAction/blob/master/modelica/HelloWorld.mo). -------------------------------------------------------------------------------- /apps/simple_building/SimpleBuilding_Room.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/apps/simple_building/SimpleBuilding_Room.fmu -------------------------------------------------------------------------------- /apps/simple_building/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "aws":{ 3 | "region": "us-west-1" 4 | }, 5 | "lambda":{ 6 | "function_name": "simple_building", 7 | "timeout": 60, 8 | "description": "A model of a single room building", 9 | "memory_size": 256, 10 | "access_control_allow_origin": "*" 11 | }, 12 | "s3": { 13 | "folder": "simple_building" 14 | }, 15 | "model":{ 16 | "fmu_name": "SimpleBuilding_Room.fmu", 17 | "input_files": [ 18 | "SFO_weather_data_summer.csv", 19 | "SFO_weather_data_winter.csv" 20 | ], 21 | "options": { 22 | "ncp": 500, 23 | "CVode_options": {"atol": 0.000001, "rtol": 0.0001}, 24 | "filter": ["Tair*", "Troom*", "Twall*", "T_*", "SolRad*", "Q*", "Tsp"] 25 | }, 26 | "simulation_time": { 27 | "min": 0.0, 28 | "max": 2592000.0 29 | }, 30 | "n_points": { 31 | "default": 200, 32 | "min": 100, 33 | "max": 5000 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /apps/simple_building/readme.md: -------------------------------------------------------------------------------- 1 | ## Simple building energy model 2 | 3 | This is a simple building energy model. The building 4 | is located in San Francisco (CA) and the API 5 | provides the ability to simulate the model using two different 6 | input files, one for the winter period and one for the summer. 7 | 8 | The source code of the model is available at 9 | [ModelicaInAction/modelica/HelloWorld.mo](https://github.com/mbonvini/ModelicaInAction/blob/master/modelica/HelloWorld.mo). -------------------------------------------------------------------------------- /configs/cors_headers.json: -------------------------------------------------------------------------------- 1 | { 2 | "method.response.header.Access-Control-Allow-Origin": "'*'" 3 | } -------------------------------------------------------------------------------- /configs/integration_options_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "method.response.header.Access-Control-Allow-Origin": "'*'", 3 | "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS,POST'", 4 | "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" 5 | } -------------------------------------------------------------------------------- /configs/method_options_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "method.response.header.Access-Control-Allow-Origin": false, 3 | "method.response.header.Access-Control-Allow-Methods": false, 4 | "method.response.header.Access-Control-Allow-Headers": false 5 | } -------------------------------------------------------------------------------- /generate_api_template.py: -------------------------------------------------------------------------------- 1 | """ 2 | """ 3 | import os 4 | import sys 5 | import argparse 6 | import json 7 | from jinja2 import Template 8 | 9 | CURDIR = os.path.abspath(os.path.dirname(__file__)) 10 | 11 | TEMPLATE_FILE = os.path.join(CURDIR, "templates", "swagger_api_template_cors.json") 12 | """ 13 | Template for the REST API using Swagger format 14 | """ 15 | 16 | def function_arn_to_uri(function_arn): 17 | """ 18 | This function takes the ARN of a lambda function and 19 | returns its URI that is used by apigateway to call the 20 | function. 21 | 22 | For example 23 | :: 24 | 25 | arn:aws:lambda:us-west-2:756048602902:function:hello_world 26 | 27 | becomes 28 | :: 29 | 30 | arn:aws:apigateway:us-west-2:lambda:path/2015-03-31 31 | /functions/arn:aws:lambda:us-west-2:756048602902:function:hello_world/invocations 32 | 33 | :param str function_arn: The ARN of the lambda function. 34 | :return: The function URI 35 | :rtype: str 36 | """ 37 | region = function_arn.split(":")[3] 38 | return ( 39 | "arn:aws:apigateway:{0}:lambda:path/2015-03-31" 40 | "/functions/{1}/invocations" 41 | ).format( 42 | region, function_arn 43 | ) 44 | 45 | def render_template(lambda_function_output, destination): 46 | """ 47 | Render the API swagger templates using the data contained into 48 | the JSON file ``lambda_function_output`` into file 49 | ``destination``. 50 | 51 | :param str lambda_function_output: The path of the JSON file that 52 | contains the output of the AWS command used to create the lambda 53 | function. 54 | :param str destination: The path of the file where the swagger API 55 | template is rendered. 56 | """ 57 | if not os.path.exists(lambda_function_output): 58 | msg = "The input file {0} does not exist".format( 59 | lambda_function_output 60 | ) 61 | sys.exit(msg) 62 | elif not lambda_function_output.endswith("json"): 63 | msg = "The input file {} must be a valid JSON".format( 64 | lambda_function_output 65 | ) 66 | sys.exit(msg) 67 | elif not destination.endswith("json"): 68 | msg = "The destination file {} must be a valid JSON".format( 69 | destination 70 | ) 71 | sys.exit(msg) 72 | 73 | with open(lambda_function_output, "r") as fi: 74 | 75 | lambda_descr = json.loads(fi.read()) 76 | function_name = lambda_descr["FunctionName"] 77 | function_arn = lambda_descr["FunctionArn"] 78 | function_uri = function_arn_to_uri(function_arn) 79 | 80 | with open(TEMPLATE_FILE, "r") as ft: 81 | template = Template(ft.read()) 82 | 83 | with open(destination, "w+") as fo: 84 | fo.write( 85 | template.render( 86 | function_name=function_name, 87 | function_uri=function_uri 88 | ) 89 | ) 90 | 91 | if __name__ == "__main__": 92 | 93 | description = ( 94 | "Command line utility that generates swagger API templates. " 95 | "The program takes two inputs: " 96 | "(1) The path of the JSON file that contains the response of the" 97 | " AWS when the lambda function is created." 98 | "(2) The path of the JSON file that will contain the description of" 99 | " the API using the swagger format." 100 | ) 101 | 102 | parser = argparse.ArgumentParser( 103 | prog="generate_api_template", 104 | description=description 105 | ) 106 | 107 | parser.add_argument( 108 | 'lambda_function', metavar='lambda_function', type=str, 109 | help=( 110 | ("The path of the JSON file that contains the response " 111 | "of the AWS API") 112 | ) 113 | ) 114 | 115 | parser.add_argument( 116 | 'dest', metavar='dest', type=str, 117 | help="The path of the output file" 118 | ) 119 | 120 | args = parser.parse_args() 121 | 122 | if args.lambda_function and args.dest: 123 | render_template(args.lambda_function, args.dest) 124 | else: 125 | parser.print_help() -------------------------------------------------------------------------------- /gui/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets" : ["es2015", "react", "stage-2"] 3 | } -------------------------------------------------------------------------------- /gui/README.md: -------------------------------------------------------------------------------- 1 | # λ-Sim GUI 2 | 3 | This folder contains the code of the web based GUI to 4 | access and explore the REST API created by λ-Sim. -------------------------------------------------------------------------------- /gui/deploy-gh-pages.js: -------------------------------------------------------------------------------- 1 | var ghpages = require('gh-pages'); 2 | var path = require('path'); 3 | 4 | ghpages.publish(path.join(__dirname, 'build'), function(err){ 5 | console.log(err); 6 | }); -------------------------------------------------------------------------------- /gui/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npm run build 3 | npm run deploy-gh-pages -------------------------------------------------------------------------------- /gui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LambdaSim", 3 | "version": "0.1.0", 4 | "description": "λ-Sim GUI", 5 | "main": "index.js", 6 | "private": true, 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "server": "node server.js", 10 | "dev": "webpack -d --watch & npm run server", 11 | "materialize-sass": "node-sass ./src/app/style/materialize-light.scss ./src/www/materialize-light.css", 12 | "start": "webpack-dev-server --config webpack-dev-server.config.js --define process.env.NODE_ENV='\"local\"' --progress --inline --colors", 13 | "build": "webpack --config webpack-production.config.js --define process.env.NODE_ENV='\"production\"' --progress --colors", 14 | "deploy-gh-pages": "node deploy-gh-pages.js" 15 | }, 16 | "author": "Marco Bonvini", 17 | "license": "All rights reserved", 18 | "dependencies": { 19 | "axios": "^0.15.3", 20 | "classnames": "^2.2.5", 21 | "flexboxgrid": "^6.3.1", 22 | "lodash": "^4.17.2", 23 | "loglevel": "^1.4.1", 24 | "material-ui": "^0.16.5", 25 | "react": "^15.4.1", 26 | "react-dom": "^15.4.1", 27 | "react-flexbox-grid": "^0.10.2", 28 | "react-markdown": "^2.4.6", 29 | "react-redux": "^5.0.1", 30 | "react-router": "^3.0.0", 31 | "react-tap-event-plugin": "^2.0.1", 32 | "redux": "^3.6.0", 33 | "redux-form": "^6.4.1", 34 | "redux-persist": "^4.0.1", 35 | "xml2js": "^0.4.17" 36 | }, 37 | "devDependencies": { 38 | "babel-core": "^6.20.0", 39 | "babel-loader": "^6.2.10", 40 | "babel-polyfill": "^6.0.0", 41 | "babel-preset-es2015": "^6.18.0", 42 | "babel-preset-react": "^6.16.0", 43 | "babel-preset-stage-2": "^6.18.0", 44 | "css-loader": "^0.26.1", 45 | "express": "^4.14.0", 46 | "gh-pages": "^0.12.0", 47 | "html-webpack-plugin": "^2.24.1", 48 | "json-loader": "^0.5.4", 49 | "node": "0.0.0", 50 | "node-sass": "^4.0.0", 51 | "npm": "^4.0.5", 52 | "path": "^0.12.7", 53 | "sass-loader": "^4.1.0", 54 | "style-loader": "^0.13.1", 55 | "transfer-webpack-plugin": "^0.1.4", 56 | "webpack": "^1.14.0", 57 | "webpack-dev-server": "1.16.2" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /gui/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const path = require('path') 3 | const port = process.env.PORT || 4000 4 | const app = express() 5 | 6 | // serve static assets normally 7 | app.use(express.static(__dirname + '/src')) 8 | 9 | // handle every other route with index.html, which will contain 10 | // a script tag to your application's JavaScript file(s). 11 | app.get('*', function (request, response){ 12 | response.sendFile(path.resolve(__dirname, 'src', 'index.html')) 13 | }) 14 | 15 | app.listen(port) 16 | console.log("server started on port " + port) -------------------------------------------------------------------------------- /gui/src/app/Main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * In this file, we create a React component 3 | * which incorporates components provided by Material-UI. 4 | */ 5 | import React, {Component} from 'react'; 6 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 7 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 8 | import { Provider } from 'react-redux'; 9 | import { 10 | grey300, orange300, amber500, amber300, blueGrey100, blueGrey500, 11 | white, darkBlack, fullBlack, deepOrange400, amber600 12 | } from 'material-ui/styles/colors'; 13 | import {fade} from 'material-ui/utils/colorManipulator'; 14 | 15 | import router from './router'; 16 | import store from './store'; 17 | 18 | const muiTheme = getMuiTheme({ 19 | palette: { 20 | primary1Color: orange300, 21 | primary2Color: deepOrange400, 22 | primary3Color: amber300, 23 | accent1Color: blueGrey100, 24 | accent2Color: blueGrey100, 25 | accent3Color: blueGrey500, 26 | textColor: darkBlack, 27 | alternateTextColor: white, 28 | canvasColor: white, 29 | borderColor: grey300, 30 | disabledColor: fade(darkBlack, 0.3), 31 | pickerHeaderColor: grey300, 32 | clockCircleColor: fade(darkBlack, 0.07), 33 | shadowColor: fullBlack, 34 | }, 35 | toolbar: { 36 | height: 60, 37 | textColor: white 38 | } 39 | }); 40 | 41 | class Main extends Component { 42 | 43 | render() { 44 | return ( 45 | 46 | {router} 47 | 48 | ); 49 | } 50 | } 51 | 52 | export default Main; 53 | -------------------------------------------------------------------------------- /gui/src/app/actions/action-types.js: -------------------------------------------------------------------------------- 1 | // API Settings 2 | export const SET_API_SETTINGS_URL = 'SET_API_SETTINGS_URL'; 3 | export const SET_API_SETTINGS_TOKEN = 'SET_API_SETTINGS_TOKEN'; 4 | export const RESET_API_SETTINGS = 'RESET_API_SETTINGS'; 5 | 6 | // Model (description) 7 | export const SET_MODEL_DESCRIPTION = 'SET_MODEL_DESCRIPTION'; 8 | export const RESET_MODEL_DESCRIPTION = 'RESET_MODEL_DESCRIPTION'; 9 | export const UPDATE_FILTER_STRING = 'UPDATE_FILTER_STRING'; 10 | export const UPDATE_FILTER_PARAMETERS = 'UPDATE_FILTER_PARAMETERS'; 11 | export const UPDATE_FILTER_CONSTANTS = 'UPDATE_FILTER_CONSTANTS'; 12 | export const UPDATE_FILTER_INPUTS = 'UPDATE_FILTER_INPUTS'; 13 | export const UPDATE_FILTER_OUTPUTS = 'UPDATE_FILTER_OUTPUTS'; 14 | export const UPDATE_FILTER_CONTINUOUS = 'UPDATE_FILTER_CONTINUOUS'; 15 | export const UPDATE_FILTER_HIDDEN = 'UPDATE_FILTER_HIDDEN'; 16 | 17 | // Model (simulation) 18 | export const SAVE_SIMULATION_RESULTS = 'SAVE_SIMULATION_RESULTS'; 19 | export const UPDATE_PLOT_FILTER_STRING = 'UPDATE_PLOT_FILTER_STRING'; 20 | export const RESET_SIMULATION_RESULTS = 'RESET_SIMULATION_RESULTS'; 21 | 22 | // Model (parameters) 23 | export const UPDATE_PARAMETERS_FILTER_STRING = 'UPDATE_PARAMETERS_FILTER_STRING'; 24 | export const UPDATE_MODEL_PARAMETER = 'UPDATE_MODEL_PARAMETER'; 25 | export const REMOVE_MODEL_PARAMETER = 'REMOVE_MODEL_PARAMETER'; 26 | export const RESET_MODEL_PARAMETERS = 'RESET_MODEL_PARAMETERS'; 27 | 28 | // Model (input) 29 | export const SET_INPUT_FILE = "SET_INPUT_FILE"; 30 | export const RESET_INPUT_FILE = "RESET_INPUT_FILE"; 31 | 32 | // Plot 33 | export const SET_PLOT_VARIABLE = 'SET_PLOT_VARIABLE'; 34 | export const RESET_PLOT_RESULTS = 'RESET_PLOT_RESULTS'; 35 | 36 | // Loading spinner 37 | export const SET_LOADING_MESSAGE = 'SET_LOADING_MESSAGE'; 38 | export const RESET_LOADING_MESSAGE = 'RESET_LOADING_MESSAGE'; 39 | 40 | // Error message 41 | export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE'; 42 | export const RESET_ERROR_MESSAGE = 'RESET_ERROR_MESSAGE'; 43 | 44 | // Home tab 45 | export const SELECT_TAB = 'SELECT_TAB'; 46 | export const RESET_SELECTED_TAB = 'RESET_SELECTED_TAB'; 47 | 48 | // Dashboard 49 | export const SET_DASHBOARD_DEFINITION = 'SET_DASHBOARD_DEFINITION'; 50 | export const RESET_DASHBOARD_DEFINITION = 'RESET_DASHBOARD_DEFINITION'; 51 | 52 | // Config 53 | export const SET_CONFIG_DEFINITION = "SET_CONFIG_DEFINITION"; 54 | export const RESET_CONFIG_DEFINITION = "RESET_CONFIG_DEFINITION"; 55 | 56 | // Readme 57 | export const SET_README = "SET_README"; 58 | export const RESET_README = "RESET_README"; -------------------------------------------------------------------------------- /gui/src/app/actions/api-settings-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | import log from 'loglevel'; 3 | 4 | export function setApiSettingsUrl(url) { 5 | return { 6 | type: types.SET_API_SETTINGS_URL, 7 | url 8 | }; 9 | } 10 | 11 | export function setApiSettingsToken(token) { 12 | return { 13 | type: types.SET_API_SETTINGS_TOKEN, 14 | token 15 | }; 16 | } 17 | 18 | export function resetApiSettings() { 19 | return { 20 | type: types.RESET_API_SETTINGS 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /gui/src/app/actions/config-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | 3 | export function setConfigDefinition(config) { 4 | return { 5 | type: types.SET_CONFIG_DEFINITION, 6 | config 7 | }; 8 | } 9 | 10 | export function resetConfigDefinition() { 11 | return { 12 | type: types.RESET_CONFIG_DEFINITION 13 | }; 14 | } -------------------------------------------------------------------------------- /gui/src/app/actions/dashboard-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | 3 | export function setDashboardDefinition(dashboard) { 4 | return { 5 | type: types.SET_DASHBOARD_DEFINITION, 6 | dashboard 7 | }; 8 | } 9 | 10 | export function resetDashboardDefinition() { 11 | return { 12 | type: types.RESET_DASHBOARD_DEFINITION 13 | }; 14 | } -------------------------------------------------------------------------------- /gui/src/app/actions/error-message-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | 3 | export function setErrorMessage(message) { 4 | return { 5 | type: types.SET_ERROR_MESSAGE, 6 | message 7 | }; 8 | } 9 | 10 | export function resetErrorMessage() { 11 | return { 12 | type: types.RESET_ERROR_MESSAGE 13 | }; 14 | } -------------------------------------------------------------------------------- /gui/src/app/actions/home-tabs-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | 3 | export function selectTab(tabName) { 4 | return { 5 | type: types.SELECT_TAB, 6 | tabName 7 | }; 8 | } 9 | 10 | export function resetSelectedTab() { 11 | return { 12 | type: types.RESET_SELECTED_TAB 13 | }; 14 | } -------------------------------------------------------------------------------- /gui/src/app/actions/loading-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | 3 | export function setLoadingMessage(message) { 4 | return { 5 | type: types.SET_LOADING_MESSAGE, 6 | message 7 | }; 8 | } 9 | 10 | export function resetLoadingMessage() { 11 | return { 12 | type: types.RESET_LOADING_MESSAGE 13 | }; 14 | } -------------------------------------------------------------------------------- /gui/src/app/actions/model-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | 3 | export function setModelDescription(modelDescriptionXml) { 4 | return { 5 | type: types.SET_MODEL_DESCRIPTION, 6 | modelDescriptionXml 7 | }; 8 | } 9 | 10 | export function resetModelDescription() { 11 | return { 12 | type: types.RESET_MODEL_DESCRIPTION 13 | }; 14 | } 15 | 16 | export function updateFilterString(filterStr){ 17 | return { 18 | type: types.UPDATE_FILTER_STRING, 19 | filterStr 20 | }; 21 | } 22 | 23 | export function updateFilterParameters(isChecked){ 24 | return { 25 | type: types.UPDATE_FILTER_PARAMETERS, 26 | isChecked 27 | }; 28 | } 29 | 30 | export function updateFilterConstants(isChecked){ 31 | return { 32 | type: types.UPDATE_FILTER_CONSTANTS, 33 | isChecked 34 | }; 35 | } 36 | 37 | export function updateFilterInputs(isChecked){ 38 | return { 39 | type: types.UPDATE_FILTER_INPUTS, 40 | isChecked 41 | }; 42 | } 43 | 44 | export function updateFilterOutputs(isChecked){ 45 | return { 46 | type: types.UPDATE_FILTER_OUTPUTS, 47 | isChecked 48 | }; 49 | } 50 | 51 | export function updateFilterContinuous(isChecked){ 52 | return { 53 | type: types.UPDATE_FILTER_CONTINUOUS, 54 | isChecked 55 | }; 56 | } 57 | 58 | export function updateFilterHidden(isChecked){ 59 | return { 60 | type: types.UPDATE_FILTER_HIDDEN, 61 | isChecked 62 | }; 63 | } -------------------------------------------------------------------------------- /gui/src/app/actions/model-input-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | 3 | export function setInputFile(fileName) { 4 | return { 5 | type: types.SET_INPUT_FILE, 6 | fileName 7 | }; 8 | } 9 | 10 | export function resetInputFile() { 11 | return { 12 | type: types.RESET_INPUT_FILE 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /gui/src/app/actions/model-parameters-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | 3 | 4 | export function resetModelParameters() { 5 | return { 6 | type: types.RESET_MODEL_PARAMETERS 7 | }; 8 | } 9 | 10 | export function updateParametersFilterString(filterString){ 11 | return { 12 | type: types.UPDATE_PARAMETERS_FILTER_STRING, 13 | filterString 14 | }; 15 | } 16 | 17 | export function updateModelParameter(name, defaultValue, newValue){ 18 | return { 19 | type: types.UPDATE_MODEL_PARAMETER, 20 | name, defaultValue, newValue 21 | }; 22 | } 23 | 24 | export function removeModelParameter(name){ 25 | return { 26 | type: types.REMOVE_MODEL_PARAMETER, 27 | name 28 | } 29 | } -------------------------------------------------------------------------------- /gui/src/app/actions/model-simulation-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | 3 | export function saveSimulationResults(startTime, finalTime, simOptions, parameters, response) { 4 | return { 5 | type: types.SAVE_SIMULATION_RESULTS, 6 | startTime, 7 | finalTime, 8 | simOptions, 9 | parameters, 10 | response 11 | }; 12 | } 13 | 14 | export function resetSimulationResults(){ 15 | return { 16 | type: types.RESET_SIMULATION_RESULTS 17 | }; 18 | } -------------------------------------------------------------------------------- /gui/src/app/actions/plot-results-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | 3 | export function setPlotVariable(varName, show){ 4 | return { 5 | type: types.SET_PLOT_VARIABLE, 6 | varName, 7 | show 8 | }; 9 | } 10 | 11 | export function updatePlotFilterString(filterStr){ 12 | return { 13 | type: types.UPDATE_PLOT_FILTER_STRING, 14 | filterStr 15 | } 16 | } 17 | 18 | export function resetPlotResults(){ 19 | return { 20 | type: types.RESET_PLOT_RESULTS 21 | } 22 | } -------------------------------------------------------------------------------- /gui/src/app/actions/readme-actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './action-types'; 2 | 3 | export function setReadme(readme) { 4 | return { 5 | type: types.SET_README, 6 | readme 7 | }; 8 | } 9 | 10 | export function resetReadme() { 11 | return { 12 | type: types.RESET_README 13 | }; 14 | } -------------------------------------------------------------------------------- /gui/src/app/api/lambda-sim-api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import store from '../store'; 3 | import log from 'loglevel'; 4 | import { setLoadingMessage, resetLoadingMessage } from '../actions/loading-actions'; 5 | import { setErrorMessage } from '../actions/error-message-actions'; 6 | import { setReadme } from '../actions/readme-actions'; 7 | 8 | import { setModelDescription } from '../actions/model-actions'; 9 | import { saveSimulationResults } from '../actions/model-simulation-actions'; 10 | import { selectTab } from '../actions/home-tabs-actions'; 11 | import { setDashboardDefinition } from '../actions/dashboard-actions'; 12 | import { setConfigDefinition } from '../actions/config-actions'; 13 | 14 | /** 15 | * This function generates a GET HTTP request to the API and it 16 | * receives the JSON model config file. 17 | */ 18 | export function getModelConfig() { 19 | const url = store.getState().apiSettings.url; 20 | 21 | store.dispatch(setLoadingMessage('Get config...')); 22 | return axios.get(url+"?config=true") 23 | .then((response) => { 24 | store.dispatch(resetLoadingMessage()); 25 | store.dispatch(setConfigDefinition(response.data)); 26 | return response; 27 | }) 28 | .catch((error) => { 29 | const msg = 'Errors while getting the API config file. '+ 30 | 'Please make sure the API URL is correct, the lambda function and '+ 31 | 'its apigateway are set up correctly.'; 32 | store.dispatch(setErrorMessage(msg)); 33 | store.dispatch(resetLoadingMessage()); 34 | log.info(error); 35 | }); 36 | } 37 | 38 | /** 39 | * This function generates a GET HTTP request to the API and it 40 | * receives the JSON model dashboard definition file. 41 | */ 42 | export function getModelDashboard() { 43 | const url = store.getState().apiSettings.url; 44 | 45 | store.dispatch(setLoadingMessage('Get dashboard...')); 46 | return axios.get(url+"?dashboard=true") 47 | .then((response) => { 48 | store.dispatch(resetLoadingMessage()); 49 | store.dispatch(setDashboardDefinition(response.data)); 50 | store.dispatch(selectTab('dashboard')); 51 | return response; 52 | }) 53 | .catch((error) => { 54 | store.dispatch(resetLoadingMessage()); 55 | log.info(error); 56 | }); 57 | } 58 | 59 | /** 60 | * This function generates a GET HTTP request to the API and it 61 | * receives the markdown readme file. 62 | */ 63 | export function getReadme() { 64 | const url = store.getState().apiSettings.url; 65 | 66 | store.dispatch(setLoadingMessage('Get readme...')); 67 | return axios.get(url+"?readme=true") 68 | .then((response) => { 69 | store.dispatch(resetLoadingMessage()); 70 | store.dispatch(setReadme(response.data)); 71 | store.dispatch(selectTab('info')); 72 | return response; 73 | }) 74 | .catch((error) => { 75 | store.dispatch(resetLoadingMessage()); 76 | log.info(error); 77 | }); 78 | } 79 | 80 | /** 81 | * This function generates a GET HTTP request to the API and it 82 | * receives the XML model description file of the FMU. 83 | */ 84 | export function getModelDescription() { 85 | const url = store.getState().apiSettings.url; 86 | 87 | store.dispatch(setLoadingMessage('Get model description...')); 88 | return axios.get(url) 89 | .then((response) => { 90 | store.dispatch(resetLoadingMessage()); 91 | store.dispatch(setModelDescription(response.data)); 92 | store.dispatch(selectTab('model_description')); 93 | return response; 94 | }) 95 | .catch((error) => { 96 | const msg = 'Errors while getting the model description file. '+ 97 | 'Please make sure the API URL is correct, the lambda function and '+ 98 | 'its apigateway are set up correctly.'; 99 | store.dispatch(setErrorMessage(msg)); 100 | store.dispatch(resetLoadingMessage()); 101 | store.dispatch(selectTab('info')); 102 | log.info(error); 103 | }); 104 | } 105 | 106 | /** 107 | * This function generates a POST HTTP request to the API that 108 | * generates a simulation. The API replies with a JSON object 109 | * that contains the results of the simulation. 110 | */ 111 | export function simulateModel(startTime, finalTime, simOptions, parameters, inputFile) { 112 | const url = store.getState().apiSettings.url; 113 | let data = { 114 | start_time: startTime, 115 | final_time: finalTime, 116 | options: simOptions || {}, 117 | parameters: parameters || {} 118 | }; 119 | if (inputFile){ 120 | data.input_name = inputFile; 121 | } 122 | 123 | store.dispatch(setLoadingMessage('Simulate...')); 124 | return axios.post(url, data) 125 | .then((response) => { 126 | store.dispatch(resetLoadingMessage()); 127 | store.dispatch(saveSimulationResults(startTime, finalTime, simOptions, parameters, response.data)); 128 | }) 129 | .catch((error) => { 130 | store.dispatch(resetLoadingMessage()); 131 | const msg = 'Errors while simulating the model.'; 132 | store.dispatch(setErrorMessage(msg)); 133 | log.info(error); 134 | }); 135 | } 136 | -------------------------------------------------------------------------------- /gui/src/app/classes/dashboard.js: -------------------------------------------------------------------------------- 1 | import log from 'loglevel'; 2 | import React from 'react'; 3 | import DashboardTextField from '../components/dashboard-text-field'; 4 | import DashboardSelectField from '../components/dashboard-select-field'; 5 | import DashboardPlotContainer from '../containers/dashboard-plot-container'; 6 | 7 | export class Dashboard { 8 | 9 | /** 10 | * Contructor for the Dashboard class. 11 | * This method takes a JSON representation of the 12 | * dashboard and it saves it. This will be used 13 | * later to build the components that are part of it. 14 | * @param {*} definition 15 | */ 16 | constructor(definition) { 17 | this.definition = definition; 18 | } 19 | 20 | static getComponentWidget(widget, row, col, itm){ 21 | const key = "w_"+row+"_"+col+"_"+itm; 22 | switch(widget.wtype){ 23 | case "html": 24 | return
25 | case "image": 26 | return 27 | case "text_field": 28 | return ; 29 | case "select_field": 30 | return ; 31 | case "plot": 32 | return ; //
PLOT
; // 33 | default: 34 | return
widget.wtype
35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /gui/src/app/classes/model-description.js: -------------------------------------------------------------------------------- 1 | import { parseString } from 'xml2js'; 2 | import log from 'loglevel'; 3 | 4 | const FMI_MODEL_DESCRIPTION = "fmiModelDescription"; 5 | const VENDOR_ANNOTATIONS = "VendorAnnotations"; 6 | const MODEL_VARIABLES = "ModelVariables"; 7 | const SCALAR_VARIABLE = "ScalarVariable"; 8 | const TOOL = "Tool"; 9 | const OBJECT = "$"; 10 | const REAL = "Real"; 11 | const BOOLEAN = "Boolean"; 12 | const INTEGER = "Integer"; 13 | const STRING = "String"; 14 | const ENUMERATION = "Enumeration" 15 | const VAR_TYPES = [REAL, BOOLEAN, INTEGER, STRING, ENUMERATION]; 16 | 17 | export class ModelDescription { 18 | 19 | constructor() { 20 | this.description = null; 21 | this.typeDefinitions = null; 22 | this.unitDefinitions = null; 23 | this.vendorAnnotations = null; 24 | this.modelVariables = null; 25 | this.modelStructure = null; 26 | } 27 | 28 | loadFromXml(modelDescriptionXml) { 29 | self = this; 30 | parseString(modelDescriptionXml, function (err, modelDescription) { 31 | if (err) { 32 | log.error(err); 33 | } else { 34 | 35 | // High level model description 36 | if (modelDescription.hasOwnProperty(FMI_MODEL_DESCRIPTION)) { 37 | const fmiModelDescription = modelDescription[FMI_MODEL_DESCRIPTION]; 38 | self.description = fmiModelDescription[OBJECT]; 39 | 40 | // Vendor annotations (optional) 41 | if (fmiModelDescription.hasOwnProperty(VENDOR_ANNOTATIONS)) { 42 | self.vendorAnnotations = fmiModelDescription[VENDOR_ANNOTATIONS].map( 43 | function (vendorAnn) { 44 | if (vendorAnn.hasOwnProperty(TOOL)) { 45 | return vendorAnn[TOOL].map( 46 | function (tool) { 47 | return tool[OBJECT]; 48 | } 49 | ); 50 | } else { 51 | return []; 52 | } 53 | } 54 | ) 55 | } else { 56 | log.info("Missing " + VENDOR_ANNOTATIONS); 57 | } 58 | 59 | // Units (optional) 60 | 61 | // Model scalar variables (required) 62 | if (fmiModelDescription.hasOwnProperty(MODEL_VARIABLES)) { 63 | if (fmiModelDescription[MODEL_VARIABLES].length > 0) { 64 | if (fmiModelDescription[MODEL_VARIABLES][0].hasOwnProperty(SCALAR_VARIABLE)) { 65 | const scalarVars = fmiModelDescription[MODEL_VARIABLES][0][SCALAR_VARIABLE]; 66 | self.modelVariables = scalarVars.map( 67 | function (variable) { 68 | let varObj = variable[OBJECT]; 69 | for(let x in VAR_TYPES){ 70 | if(variable.hasOwnProperty(VAR_TYPES[x])){ 71 | varObj['type'] = VAR_TYPES[x]; 72 | varObj['typeAttr'] = variable[VAR_TYPES[x]][0][OBJECT]; 73 | } 74 | } 75 | return varObj; 76 | } 77 | ); 78 | 79 | }else{ 80 | log.error("Missing " + SCALAR_VARIABLE); 81 | } 82 | }else{ 83 | log.error("No model variables"); 84 | } 85 | } else { 86 | log.error("Missing " + MODEL_VARIABLES); 87 | } 88 | 89 | } else { 90 | log.error("Missing " + FMI_MODEL_DESCRIPTION); 91 | } 92 | 93 | } 94 | }); 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /gui/src/app/components/constants.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TextField from 'material-ui/TextField'; 3 | 4 | export const renderTextField = ({ input, label, hintText, meta: { touched, error }, ...custom }) => ( 5 | 11 | ); -------------------------------------------------------------------------------- /gui/src/app/components/dashboard-plot.js: -------------------------------------------------------------------------------- 1 | import log from 'loglevel'; 2 | import React from 'react'; 3 | 4 | class DashboardPlot extends React.Component { 5 | 6 | constructor(props) { 7 | super(props); 8 | this.buildData = this.buildData.bind(this); 9 | this.state = { 10 | layout: this.props.widget.layout, 11 | data: this.buildData(), 12 | readyToReplot: false 13 | }; 14 | } 15 | 16 | componentWillReceiveProps(){ 17 | this.state.readyToReplot = true; 18 | } 19 | 20 | componentDidUpdate(){ 21 | if(this.props.modelSimulation.results && this.state.readyToReplot){ 22 | const update = this.buildData(); 23 | this.state.readyToReplot = false; 24 | this.state.data = update; 25 | 26 | let plotDiv = document.getElementById(this.props.uniqueKey); 27 | plotDiv.data = this.state.data; 28 | Plotly.redraw(plotDiv); 29 | } 30 | } 31 | 32 | /** 33 | * This method looks at the definition of the data in the 34 | * widget.data attribute and fills it with actual data 35 | */ 36 | buildData(){ 37 | let plot_data = []; 38 | this.props.widget.data.map((series) => { 39 | let var_data = Object.assign({}, series); 40 | 41 | // Get the name of the variables that hold the data in the results 42 | const x_var_name = series.x.replace("$result.", ""); 43 | const y_var_name = series.y.replace("$result.", ""); 44 | 45 | // Verify if there's data 46 | if (this.props.modelSimulation.results){ 47 | var_data.x = this.props.modelSimulation.results[x_var_name]; 48 | var_data.y = this.props.modelSimulation.results[y_var_name]; 49 | }else{ 50 | var_data.x = []; 51 | var_data.y = []; 52 | } 53 | 54 | plot_data.push(var_data); 55 | }); 56 | 57 | return plot_data; 58 | } 59 | 60 | componentDidMount(){ 61 | Plotly.newPlot( 62 | this.props.uniqueKey, 63 | this.state.data, 64 | this.state.layout, 65 | {showLink: false} 66 | ); 67 | } 68 | 69 | render() { 70 | return ( 71 |
72 |
73 | ); 74 | } 75 | } 76 | 77 | export default DashboardPlot; 78 | -------------------------------------------------------------------------------- /gui/src/app/components/dashboard-select-field.js: -------------------------------------------------------------------------------- 1 | import log from 'loglevel'; 2 | import React from 'react'; 3 | import SelectField from 'material-ui/SelectField'; 4 | import MenuItem from 'material-ui/MenuItem'; 5 | import { updateModelParameter } from '../actions/model-parameters-actions'; 6 | import store from '../store'; 7 | 8 | class DashboardSelectField extends React.Component { 9 | 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | value: this.props.widget.options[0].params, 14 | }; 15 | this.handleChange = this.handleChange.bind(this); 16 | this.setModelParameter = this.setModelParameter.bind(this); 17 | } 18 | 19 | componentDidMount(){ 20 | this.setModelParameter(); 21 | } 22 | 23 | handleChange(event, index, val){ 24 | this.setState({value: val}); 25 | this.setModelParameter(); 26 | } 27 | 28 | setModelParameter(){ 29 | for(let var_name in this.state.value){ 30 | store.dispatch(updateModelParameter( 31 | var_name, null, String(this.state.value[var_name]) 32 | )); 33 | } 34 | } 35 | 36 | render() { 37 | return ( 38 | 43 | {this.props.widget.options.map( 44 | (opt, idx) => 45 | 48 | ) 49 | } 50 | 51 | ); 52 | } 53 | } 54 | 55 | export default DashboardSelectField; 56 | -------------------------------------------------------------------------------- /gui/src/app/components/dashboard-text-field.js: -------------------------------------------------------------------------------- 1 | import log from 'loglevel'; 2 | import React from 'react'; 3 | import TextField from 'material-ui/TextField'; 4 | import { updateModelParameter } from '../actions/model-parameters-actions'; 5 | import store from '../store'; 6 | 7 | class DashboardTextField extends React.Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | value: String(this.props.widget.param.default), 13 | }; 14 | this.handleChange = this.handleChange.bind(this); 15 | this.setModelParameter = this.setModelParameter.bind(this); 16 | } 17 | 18 | componentDidMount(){ 19 | this.setModelParameter(this.state.value); 20 | } 21 | 22 | handleChange(event, newVal){ 23 | this.setState({value: newVal}); 24 | this.setModelParameter(newVal); 25 | } 26 | 27 | setModelParameter(newVal){ 28 | const var_name = this.props.widget.param.name; 29 | store.dispatch(updateModelParameter( 30 | var_name, null, newVal) 31 | ); 32 | } 33 | 34 | render() { 35 | return ( 36 | 42 | ); 43 | } 44 | } 45 | 46 | export default DashboardTextField; 47 | -------------------------------------------------------------------------------- /gui/src/app/components/footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { basePath } from '../router'; 4 | 5 | class Footer extends React.Component { 6 | 7 | render() { 8 | return ( 9 | 48 | ); 49 | } 50 | } 51 | 52 | export default Footer; 53 | -------------------------------------------------------------------------------- /gui/src/app/components/loading-spinner.js: -------------------------------------------------------------------------------- 1 | /* global Plotly */ 2 | import React from 'react'; 3 | import log from 'loglevel'; 4 | import CircularProgress from 'material-ui/CircularProgress'; 5 | 6 | class LoadingSpinner extends React.Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | render() { 13 | if(this.props.loading.message){ 14 | return ( 15 |
16 | 17 | 18 | {this.props.loading.message} 19 | 20 |
21 | ) 22 | }else{ 23 | return (
); 24 | } 25 | } 26 | } 27 | 28 | export default LoadingSpinner; -------------------------------------------------------------------------------- /gui/src/app/components/navbar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarTitle } from 'material-ui/Toolbar'; 4 | import muiThemeable from 'material-ui/styles/muiThemeable'; 5 | import Dialog from 'material-ui/Dialog'; 6 | import IconButton from 'material-ui/IconButton'; 7 | import RaisedButton from 'material-ui/RaisedButton'; 8 | import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'; 9 | import Drawer from 'material-ui/Drawer'; 10 | import MenuItem from 'material-ui/MenuItem'; 11 | import Divider from 'material-ui/Divider'; 12 | import log from 'loglevel'; 13 | 14 | import store from '../store'; 15 | import SelectApiForm from './select-api-form'; 16 | import { setApiSettingsUrl, resetApiSettings } from '../actions/api-settings-actions'; 17 | import { resetReadme } from '../actions/readme-actions'; 18 | import { getModelDescription, getModelConfig, getModelDashboard, getReadme } from '../api/lambda-sim-api'; 19 | import { resetModelParameters } from '../actions/model-parameters-actions'; 20 | import { resetModelDescription } from '../actions/model-actions'; 21 | import { resetSimulationResults } from '../actions/model-simulation-actions'; 22 | import { resetPlotResults } from '../actions/plot-results-actions'; 23 | import { selectTab } from '../actions/home-tabs-actions'; 24 | import LoadingSpinnerContainer from '../containers/loading-spinner-container'; 25 | import { basePath } from '../router'; 26 | import { resetConfigDefinition } from '../actions/config-actions'; 27 | import { resetDashboardDefinition } from '../actions/dashboard-actions'; 28 | import { resetInputFile } from '../actions/model-input-actions'; 29 | 30 | function handleTouchTap() { 31 | alert('onTouchTap triggered on the title component'); 32 | } 33 | 34 | const style = (palette) => { 35 | return { 36 | backgroundColor: palette.primary1Color, 37 | textColor: palette.alternateTextColor 38 | } 39 | }; 40 | 41 | const titleStyle = (palette) => { 42 | return { 43 | align: 'left', 44 | paddingLeft: 0, 45 | color: palette.textColor 46 | } 47 | }; 48 | 49 | 50 | /** 51 | * This example uses an [IconButton](/#/components/icon-button) on the left, has a clickable `title` 52 | * through the `onTouchTap` property, and a [FlatButton](/#/components/flat-button) on the right. 53 | */ 54 | class Navbar extends React.Component { 55 | 56 | constructor(props) { 57 | super(props); 58 | this.state = { 59 | openMenu: false, 60 | openDialog: false, 61 | }; 62 | this.handleSubmit = this.handleSubmit.bind(this); 63 | } 64 | 65 | handleSubmit = (data) => { 66 | store.dispatch(setApiSettingsUrl(data.url)); 67 | getModelDescription(); 68 | getModelConfig(); 69 | getModelDashboard(); 70 | getReadme(); 71 | this.handleCloseDialog(); 72 | }; 73 | 74 | handleOpenDialog = () => { 75 | this.setState({ openDialog: true }); 76 | }; 77 | 78 | handleCloseDialog = () => { 79 | this.setState({ openDialog: false }); 80 | }; 81 | 82 | clearSettings = () => { 83 | store.dispatch(resetApiSettings()); 84 | store.dispatch(resetModelParameters()); 85 | store.dispatch(resetModelDescription()); 86 | store.dispatch(resetInputFile()); 87 | store.dispatch(resetSimulationResults()); 88 | store.dispatch(resetPlotResults()); 89 | store.dispatch(resetConfigDefinition()); 90 | store.dispatch(resetDashboardDefinition()); 91 | store.dispatch(resetReadme()); 92 | store.dispatch(selectTab('info')); 93 | this.handleCloseDialog(); 94 | }; 95 | 96 | toggleMenu = () => this.setState({ openMenu: !this.state.openMenu }); 97 | 98 | toolBarHeaderMenu = () => ( 99 | 100 | 101 | 102 | 103 | 104 | ); 105 | 106 | render = () => { 107 | 108 | return ( 109 |
110 | 114 | Use this dialog to enter the URL of the REST API. 115 | The URL has the following format 116 |
https://API_ID.execute-api.AWS_REGION.amazonaws.com/prod/FUNCTION_NAME
117 | where 118 |
    119 |
  1. API_ID is the ID of the AWS apigateway REST API,
  2. 120 |
  3. AWS_REGION is the specifier of the AWS region where the API is deployed,
  4. 121 |
  5. FUNCTION_NAME is the name of lambda function exposed through the REST API.
  6. 122 |
123 | 124 | 129 | 130 |
131 | 132 | 133 | 134 | {this.toolBarHeaderMenu()} 135 | 136 | 137 | 138 | {this.props.apiSettings.url && ( 139 | this.props.apiSettings.url 140 | )} 141 | 142 | 143 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | {this.toolBarHeaderMenu()} 155 | 156 | 157 | 158 | 159 |
160 | ); 161 | } 162 | } 163 | 164 | export default muiThemeable()(Navbar); -------------------------------------------------------------------------------- /gui/src/app/components/plot-selection-menu.js: -------------------------------------------------------------------------------- 1 | /* global Plotly */ 2 | import React from 'react'; 3 | import log from 'loglevel'; 4 | import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'; 5 | import TextField from 'material-ui/TextField'; 6 | import Checkbox from 'material-ui/Checkbox'; 7 | 8 | import { setPlotVariable, updatePlotFilterString } from '../actions/plot-results-actions'; 9 | import store from '../store'; 10 | 11 | const menuItemsStyle = { 12 | paddingLeft: "20px" 13 | }; 14 | 15 | class PlotSelectionMenu extends React.Component { 16 | 17 | constructor(props) { 18 | super(props); 19 | } 20 | 21 | filterVariable = (variable) => { 22 | const nameCondition = this.props.filterString === null || 23 | this.props.filterString === '' || 24 | variable.includes(this.props.filterString); 25 | return this.props.plotVariables[variable] || nameCondition; 26 | } 27 | 28 | render() { 29 | if (!this.props.plotVariables) { 30 | return ( 31 |
32 | ); 33 | } else { 34 | return ( 35 |
36 |
37 | store.dispatch(updatePlotFilterString(e.target.value))} 43 | /> 44 |
45 | 50 | 51 | {Object.keys(this.props.plotVariables).filter( 52 | name => this.filterVariable(name) 53 | ).map((name, j) => 54 | 55 | 56 | store.dispatch(setPlotVariable(name, isChecked)) 58 | } 59 | checked={this.props.plotVariables[name]}/> 60 | 61 | {name} 62 | 63 | )} 64 | 65 |
66 |
67 | ); 68 | } 69 | } 70 | } 71 | 72 | export default PlotSelectionMenu; -------------------------------------------------------------------------------- /gui/src/app/components/plot.js: -------------------------------------------------------------------------------- 1 | /* global Plotly */ 2 | import React from 'react'; 3 | import log from 'loglevel'; 4 | 5 | const layout = { 6 | margin: {t: 30}, 7 | showlegend: true, 8 | xaxis: {title: 'Time [s]'}, 9 | legend:{orientation:'h', x: 0.02, y: 1.0} 10 | }; 11 | 12 | const data = [{ 13 | x: [], 14 | y: [], 15 | mode: 'lines', 16 | name: 'A' 17 | }]; 18 | 19 | class Plot extends React.Component { 20 | 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | readyToReplot: false, 25 | data: data 26 | }; 27 | } 28 | 29 | componentDidMount(){ 30 | Plotly.newPlot('plot', this.state.data, layout, {showLink: false}); 31 | } 32 | 33 | componentWillReceiveProps(){ 34 | this.state.readyToReplot = true; 35 | } 36 | 37 | componentDidUpdate(){ 38 | if(this.props.plotVariables && this.props.modelSimulation.results && this.state.readyToReplot){ 39 | const time = this.props.modelSimulation.results['time']; 40 | let newData = []; 41 | for(let varName in this.props.plotVariables){ 42 | if(this.props.plotVariables[varName]){ 43 | newData.push({ 44 | x: time, 45 | y: this.props.modelSimulation.results[varName], 46 | mode: 'lines', 47 | name: varName 48 | }); 49 | } 50 | } 51 | this.state.data = newData; 52 | this.state.readyToReplot = false; 53 | 54 | let plotDiv = document.getElementById('plot'); 55 | plotDiv.data = this.state.data; 56 | Plotly.redraw(plotDiv); 57 | } 58 | } 59 | 60 | render() { 61 | return ( 62 |
63 |
64 | ); 65 | } 66 | } 67 | 68 | export default Plot; -------------------------------------------------------------------------------- /gui/src/app/components/select-api-form.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import log from 'loglevel'; 4 | import RaisedButton from 'material-ui/RaisedButton'; 5 | import { Field, reduxForm } from 'redux-form'; 6 | import store from '../store'; 7 | import { renderTextField } from './constants'; 8 | 9 | const btnStyle = { 10 | marginTop: 10, 11 | marginBottom: 10, 12 | marginRight: 10 13 | }; 14 | 15 | const validate = values => { 16 | const errors = {}; 17 | const requiredFields = [ 'url' ]; 18 | requiredFields.forEach(field => { 19 | if (!values[ field ]) { 20 | errors[ field ] = 'Required' 21 | } 22 | }) 23 | 24 | if (values.url && !/^https\:\/\/[A-Za-z0-9]{10,11}\.execute-api\.[A-Za-z0-9\-]+\.amazonaws\.com\/prod\/[a-zA-Z_\-0-9]+$/i.test(values.url)) { 25 | errors.url = 'Invalid API URL'; 26 | } 27 | return errors; 28 | } 29 | 30 | const fieldStyle = { 31 | width: '100%' 32 | } 33 | 34 | class SelectApiForm extends React.Component { 35 | 36 | constructor(props) { 37 | super(props); 38 | } 39 | 40 | componentDidMount = () => { 41 | this.handleInitialize(); 42 | } 43 | 44 | handleInitialize = () => { 45 | const initData = { 46 | url: store.getState().apiSettings.url 47 | }; 48 | if(initData.url !== null){ 49 | this.props.initialize(initData); 50 | }else{ 51 | this.props.reset() 52 | } 53 | } 54 | 55 | render = () => { 56 | const { handleSubmit, pristine, reset, submitting, invalid, close, clear } = this.props; 57 | return ( 58 |
59 |
60 | 64 |
65 |
66 | 69 | 72 | 75 |
76 |
77 | ) 78 | } 79 | } 80 | 81 | export default reduxForm({ 82 | form: 'SelectApiForm', 83 | validate 84 | })(SelectApiForm); -------------------------------------------------------------------------------- /gui/src/app/components/simulate-form.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import log from 'loglevel'; 3 | import RaisedButton from 'material-ui/RaisedButton'; 4 | import { Field, SelectField, reduxForm } from 'redux-form'; 5 | import store from '../store'; 6 | import { renderTextField, renderSelectField } from './constants'; 7 | import SimulationInputsDialogContainer from '../containers/simulation-inputs-dialog-container'; 8 | 9 | const btnStyle = { 10 | marginTop: 0, 11 | marginLeft: 10, 12 | marginRight: 10 13 | }; 14 | 15 | const validate = values => { 16 | const errors = {}; 17 | const requiredFields = ['startTime', 'finalTime']; 18 | requiredFields.forEach(field => { 19 | if (!values[field]) { 20 | errors[field] = 'Required' 21 | } 22 | }) 23 | if (isNaN(+values.startTime)) { 24 | errors.startTime = 'Invalid start time'; 25 | } 26 | if (isNaN(+values.finalTime)) { 27 | errors.finalTime = 'Invalid stop time'; 28 | } 29 | if (+values.finalTime <= +values.startTime){ 30 | errors.finalTime = 'Stop time must be after start time'; 31 | } 32 | return errors; 33 | } 34 | 35 | 36 | class SimulateForm extends React.Component { 37 | 38 | constructor(props) { 39 | super(props); 40 | } 41 | 42 | render = () => { 43 | const { 44 | handleSubmit, pristine, reset, submitting, 45 | invalid, close, clear, openSettingsDialog, 46 | openParametersDialog 47 | } = this.props; 48 | return ( 49 |
50 |
51 | 57 | 63 | 68 | 72 | { this.props.showParametersButton && 73 | 76 | } 77 | 78 | 79 |
80 |
81 | ) 82 | } 83 | } 84 | 85 | export default reduxForm({ 86 | form: 'SimulateForm', 87 | validate 88 | })(SimulateForm); -------------------------------------------------------------------------------- /gui/src/app/components/simulation-inputs-dialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import log from 'loglevel'; 3 | import Dialog from 'material-ui/Dialog'; 4 | import RaisedButton from 'material-ui/RaisedButton'; 5 | import SelectField from 'material-ui/SelectField'; 6 | import MenuItem from 'material-ui/MenuItem'; 7 | import FlatButton from 'material-ui/FlatButton'; 8 | import { setInputFile } from '../actions/model-input-actions'; 9 | 10 | import store from '../store'; 11 | 12 | const btnStyle = { 13 | marginTop: 0, 14 | marginLeft: 10, 15 | marginRight: 10 16 | }; 17 | 18 | class SimulationInputsDialog extends React.Component { 19 | 20 | constructor(props) { 21 | super(props); 22 | let fileName = null; 23 | if(this.props.config && this.props.config.model.input_files.length > 0){ 24 | fileName = this.props.config.model.input_files[0]; 25 | } 26 | this.state = { 27 | open: false, 28 | fileName: fileName 29 | } 30 | } 31 | 32 | handleOpenDialog = () => { 33 | this.setState({ open: true }); 34 | }; 35 | 36 | handleCloseDialog = () => { 37 | this.setState({ open: false }); 38 | }; 39 | 40 | handleCloseAndSubmit = () => { 41 | store.dispatch(setInputFile(this.state.fileName)); 42 | this.setState({ open: false }); 43 | }; 44 | 45 | handleInputFileChange = (event, index, value) => this.setState({fileName: value}); 46 | 47 | render = () => { 48 | const showButton = this.props.config && this.props.config.model.input_files.length > 0; 49 | const actions = [ 50 | , 55 | , 61 | ]; 62 | 63 | return ( 64 |
65 | { showButton && 66 |
67 | 70 | 77 | 83 | {this.props.config.model.input_files.map((fileName, i) => { 84 | return 85 | }) 86 | } 87 | 88 | 89 |
90 | } 91 |
92 | ); 93 | } 94 | } 95 | 96 | export default SimulationInputsDialog; 97 | -------------------------------------------------------------------------------- /gui/src/app/components/simulation-parameters-dialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import log from 'loglevel'; 3 | import TextField from 'material-ui/TextField'; 4 | import Dialog from 'material-ui/Dialog'; 5 | import Chip from 'material-ui/Chip'; 6 | import RaisedButton from 'material-ui/RaisedButton'; 7 | import FlatButton from 'material-ui/FlatButton'; 8 | import { 9 | Table, TableBody, TableRow, TableRowColumn, 10 | TableHeader, TableHeaderColumn 11 | } from 'material-ui/Table'; 12 | import { 13 | updateParametersFilterString, updateModelParameter, 14 | removeModelParameter, resetModelParameters 15 | } from '../actions/model-parameters-actions'; 16 | import store from '../store'; 17 | 18 | const styles = { 19 | chip: { 20 | margin: 4, 21 | }, 22 | wrapper: { 23 | display: 'flex', 24 | flexWrap: 'wrap', 25 | }, 26 | }; 27 | 28 | class SimulationParametersDialog extends React.Component { 29 | 30 | constructor(props) { 31 | super(props); 32 | } 33 | 34 | filterVariable = (variable) => { 35 | const isHidden = variable.name[0] === '_'; 36 | const isParameter = variable.variability === 'parameter'; 37 | const isSelected = variable.name in this.props.modelParameters.parameters; 38 | const nameCondition = this.props.modelParameters.filterString === null || 39 | this.props.modelParameters.filterString === '' || 40 | variable.name.includes(this.props.modelParameters.filterString); 41 | return isParameter && !isHidden && (nameCondition || isSelected); 42 | }; 43 | 44 | render = () => { 45 | const actions = [ 46 | 52 | ]; 53 | return ( 54 | 61 |
62 | { 63 | Object.keys(this.props.modelParameters.parameters).map( 64 | (name) => 65 | store.dispatch(removeModelParameter(name))} 67 | style={styles.chip} 68 | > 69 | {name} 70 | 71 | ) 72 | } 73 |
74 | store.dispatch(resetModelParameters())} 76 | /> 77 | store.dispatch(updateParametersFilterString(e.target.value))} 83 | /> 84 | 85 | 86 | 87 | Name 88 | Default 89 | Value 90 | 91 | 92 | 93 | {this.props.modelDescription.modelVariables.filter( 94 | e => this.filterVariable(e) 95 | ).map( 96 | (variable) => 97 | 98 | {variable.name} 99 | {variable.typeAttr.start} 100 | 101 | store.dispatch(updateModelParameter( 107 | variable.name, variable.typeAttr.start, e.target.value 108 | ) 109 | )}/> 110 | 111 | 112 | )} 113 | 114 |
115 |
116 | ); 117 | } 118 | } 119 | 120 | export default SimulationParametersDialog; 121 | -------------------------------------------------------------------------------- /gui/src/app/components/simulation-settings-dialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import log from 'loglevel'; 3 | import Dialog from 'material-ui/Dialog'; 4 | 5 | class SimulationSettingsDialog extends React.Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | } 10 | 11 | render = () => { 12 | return ( 13 | 19 | 123 20 | 21 | ); 22 | } 23 | } 24 | 25 | export default SimulationSettingsDialog; 26 | -------------------------------------------------------------------------------- /gui/src/app/components/tab-dashboard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import log from 'loglevel'; 3 | import RaisedButton from 'material-ui/RaisedButton'; 4 | import FlatButton from 'material-ui/FlatButton'; 5 | import TextField from 'material-ui/TextField'; 6 | import { simulateModel } from '../api/lambda-sim-api'; 7 | 8 | import store from '../store'; 9 | import SimulateForm from '../components/simulate-form'; 10 | import PlotSelectionMenuContainer from '../containers/plot-selection-menu-container'; 11 | import SimulationSettingsDialog from './simulation-settings-dialog'; 12 | 13 | import { Dashboard } from '../classes/dashboard'; 14 | 15 | const styles = { 16 | headline: { 17 | fontSize: 24, 18 | paddingTop: 16, 19 | marginBottom: 12, 20 | fontWeight: 400, 21 | }, 22 | }; 23 | 24 | 25 | // For file inputs see 26 | // https://github.com/callemall/material-ui/issues/3689 27 | 28 | class TabDashboard extends React.Component { 29 | 30 | constructor(props) { 31 | super(props); 32 | this.state = { 33 | showSettingsDialog: false, 34 | showParametersDialog: false 35 | } 36 | } 37 | 38 | openSettingsDialog = () => { 39 | this.setState({ showSettingsDialog: true }); 40 | } 41 | 42 | closeSettingsDialog = () => { 43 | this.setState({ showSettingsDialog: false }); 44 | } 45 | 46 | runSimulation = (data) => { 47 | const simOptions = {}; 48 | const parameters = this.props.modelParameters.parameters; 49 | const inputFileName = this.props.inputFileName; 50 | const startTime = +data.startTime; 51 | const finalTime = +data.finalTime; 52 | simulateModel(startTime, finalTime, simOptions, parameters, inputFileName); 53 | }; 54 | 55 | render = () => { 56 | return ( 57 | this.props.modelDescription ? ( 58 | this.props.dashboard ? ( 59 |
60 |
61 |
62 | 67 |
68 | 69 | 73 | 74 |
75 | {this.props.dashboard.definition.map( 76 | (row, i) => 77 |
78 | {row.map( 79 | (col, j) => 80 |
81 | {col.widgets.map( 82 | (widget, k) => 83 | Dashboard.getComponentWidget(widget, i, j, k) 84 | ) 85 | } 86 |
87 | ) 88 | } 89 |
90 | ) 91 | } 92 |
93 | ) : ( 94 |
95 |

No Dashboard

96 |

97 | This model doesn't have a dashboard associated to it. 98 |

99 |
100 | ) 101 | ) : ( 102 |
103 |

No model selected

104 |

105 | Please select the REST API of your model. 106 |

107 |
108 | ) 109 | ); 110 | } 111 | } 112 | 113 | export default TabDashboard; -------------------------------------------------------------------------------- /gui/src/app/components/tab-info.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import log from 'loglevel'; 3 | import store from '../store'; 4 | import ReactMarkdown from 'react-markdown'; 5 | 6 | const defaultContent = ( 7 |
8 |
9 |

10 | This web app allows you to interact with an API created 11 | by λ-Sim. 12 |

13 |
    14 |
  1. Start by selecting the API,
  2. 15 |
  3. browse the model variables and parameters,
  4. 16 |
  5. run simulations and visualize the data
  6. 17 |
18 |
19 |
20 | 22 |
23 |
24 | ); 25 | 26 | class TabInfo extends React.Component { 27 | 28 | constructor(props) { 29 | super(props); 30 | } 31 | 32 | render() { 33 | let content; 34 | if(this.props.readme.content !== ''){ 35 | content = ; 36 | }else{ 37 | content = defaultContent; 38 | } 39 | 40 | return ( 41 |
42 | {content} 43 |
44 | ); 45 | } 46 | 47 | } 48 | 49 | export default TabInfo; -------------------------------------------------------------------------------- /gui/src/app/components/tab-model-description.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import log from 'loglevel'; 3 | import TextField from 'material-ui/TextField'; 4 | import Checkbox from 'material-ui/Checkbox'; 5 | import { 6 | updateFilterString, updateFilterParameters, updateFilterConstants, 7 | updateFilterInputs, updateFilterOutputs, updateFilterContinuous, 8 | updateFilterHidden 9 | } from '../actions/model-actions'; 10 | import { 11 | Table, TableBody, TableRow, TableRowColumn, 12 | TableHeader, TableHeaderColumn 13 | } from 'material-ui/Table'; 14 | 15 | import store from '../store'; 16 | 17 | const styles = { 18 | headline: { 19 | fontSize: 24, 20 | paddingTop: 16, 21 | marginBottom: 12, 22 | fontWeight: 400, 23 | }, 24 | }; 25 | 26 | class TabModelDescription extends React.Component { 27 | 28 | constructor(props) { 29 | super(props); 30 | } 31 | 32 | filterVariable = (variable) => { 33 | const isHidden = variable.name[0] === '_'; 34 | const preCondition = (this.props.filterParameters && variable.variability === 'parameter') || 35 | (this.props.filterConstants && variable.variability === 'constant') || 36 | (this.props.filterInputs && variable.causality === 'input') || 37 | (this.props.filterOutputs && variable.causality === 'output') || 38 | (this.props.filterContinuous && variable.variability === 'continuous'); 39 | 40 | const nameCondition = this.props.filterStr === null || 41 | this.props.filterStr === '' || 42 | variable.name.includes(this.props.filterStr); 43 | 44 | const hiddenCond = (isHidden && this.props.filterHidden) || (!isHidden); 45 | return preCondition && nameCondition && hiddenCond; 46 | } 47 | 48 | render = () => { 49 | return ( 50 | this.props.modelDescription ? ( 51 |
52 |

Description

53 | 54 | 55 | 56 | Name 57 | Value 58 | 59 | 60 | 61 | {Object.keys(this.props.modelDescription.description).map( 62 | (name, value) => 63 | 64 | {name} 65 | {this.props.modelDescription.description[name]} 66 | 67 | )} 68 | 69 |
70 | 71 |

Variables, constants, parameters, inputs and outputs

72 | store.dispatch(updateFilterString(e.target.value))} 77 | /> 78 |
79 |
80 | store.dispatch(updateFilterParameters(isChecked))}/> 82 | store.dispatch(updateFilterConstants(isChecked))}/> 84 | store.dispatch(updateFilterInputs(isChecked))}/> 86 |
87 |
88 | store.dispatch(updateFilterOutputs(isChecked))}/> 90 | store.dispatch(updateFilterContinuous(isChecked))}/> 92 | store.dispatch(updateFilterHidden(isChecked))}/> 94 |
95 |
96 | 97 | 98 | 99 | 100 | Name 101 | Variability 102 | Causality 103 | Type 104 | Value 105 | 106 | 107 | 108 | {this.props.modelDescription.modelVariables.filter( 109 | e => this.filterVariable(e) 110 | ).map( 111 | (variable) => 112 | 113 | {variable.name} 114 | {variable.variability} 115 | {variable.causality} 116 | {variable.type} 117 | {variable.typeAttr.start} 118 | 119 | )} 120 | 121 |
122 |
123 | ) : ( 124 |
125 |

No model selected

126 |

127 | Please select the REST API of your model. 128 |

129 |
130 | ) 131 | ); 132 | } 133 | } 134 | 135 | export default TabModelDescription; 136 | -------------------------------------------------------------------------------- /gui/src/app/components/tab-simulation.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import log from 'loglevel'; 3 | import RaisedButton from 'material-ui/RaisedButton'; 4 | import FlatButton from 'material-ui/FlatButton'; 5 | import TextField from 'material-ui/TextField'; 6 | import { simulateModel } from '../api/lambda-sim-api'; 7 | 8 | import store from '../store'; 9 | import PlotContainer from '../containers/plot-container'; 10 | import SimulateForm from '../components/simulate-form'; 11 | import PlotSelectionMenuContainer from '../containers/plot-selection-menu-container'; 12 | import SimulationParametersDialogContainer from '../containers/simulation-parameters-dialog-container'; 13 | import SimulationSettingsDialog from './simulation-settings-dialog'; 14 | 15 | const styles = { 16 | headline: { 17 | fontSize: 24, 18 | paddingTop: 16, 19 | marginBottom: 12, 20 | fontWeight: 400, 21 | }, 22 | }; 23 | 24 | 25 | // For file inputs see 26 | // https://github.com/callemall/material-ui/issues/3689 27 | 28 | class TabSimulation extends React.Component { 29 | 30 | constructor(props) { 31 | super(props); 32 | this.state = { 33 | showSettingsDialog: false, 34 | showParametersDialog: false 35 | } 36 | } 37 | 38 | openSettingsDialog = () => { 39 | this.setState({ showSettingsDialog: true }); 40 | } 41 | 42 | closeSettingsDialog = () => { 43 | this.setState({ showSettingsDialog: false }); 44 | } 45 | 46 | openParametersDialog = () => { 47 | this.setState({ showParametersDialog: true }); 48 | } 49 | 50 | closeParametersDialog = () => { 51 | this.setState({ showParametersDialog: false }); 52 | } 53 | 54 | runSimulation = (data) => { 55 | const simOptions = {}; 56 | const parameters = this.props.modelParameters.parameters; 57 | const inputFileName = this.props.inputFileName; 58 | const startTime = +data.startTime; 59 | const finalTime = +data.finalTime; 60 | simulateModel(startTime, finalTime, simOptions, parameters, inputFileName); 61 | }; 62 | 63 | render = () => { 64 | return ( 65 | this.props.modelDescription ? ( 66 |
67 |
68 | 69 |
70 |
71 | 76 | 77 |
78 | 79 | 83 | 84 | 88 |
89 | ) : ( 90 |
91 |

No model selected

92 |

93 | Please select the REST API of your model. 94 |

95 |
96 | ) 97 | ); 98 | } 99 | } 100 | 101 | export default TabSimulation; -------------------------------------------------------------------------------- /gui/src/app/containers/dashboard-plot-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import DashboardPlot from '../components/dashboard-plot'; 4 | import log from 'loglevel'; 5 | import store from '../store'; 6 | 7 | class DashboardPlotContainer extends React.Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | } 12 | 13 | render() { 14 | return ( 15 | 19 | ); 20 | } 21 | 22 | } 23 | 24 | const mapStateToProps = function(store) { 25 | return { 26 | modelSimulation: store.modelSimulation 27 | }; 28 | }; 29 | 30 | export default connect(mapStateToProps)(DashboardPlotContainer); -------------------------------------------------------------------------------- /gui/src/app/containers/home-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import HomeView from '../views/home'; 4 | import log from 'loglevel'; 5 | import store from '../store'; 6 | 7 | const HomeContainer = React.createClass({ 8 | 9 | render: function() { 10 | return ( 11 | 12 | ); 13 | } 14 | 15 | }); 16 | 17 | const mapStateToProps = function(store) { 18 | return { 19 | error: store.error, 20 | homeTabs: store.homeTabs 21 | }; 22 | }; 23 | 24 | export default connect(mapStateToProps)(HomeContainer); -------------------------------------------------------------------------------- /gui/src/app/containers/loading-spinner-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import LoadingSpinner from '../components/loading-spinner'; 4 | import log from 'loglevel'; 5 | 6 | const LoadingSpinnerContainer = React.createClass({ 7 | 8 | render: function() { 9 | return ( 10 | 11 | ); 12 | } 13 | 14 | }); 15 | 16 | const mapStateToProps = function(store) { 17 | return { 18 | loading: store.loading 19 | }; 20 | }; 21 | 22 | export default connect(mapStateToProps)(LoadingSpinnerContainer); -------------------------------------------------------------------------------- /gui/src/app/containers/navbar-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Navbar from '../components/navbar'; 4 | import log from 'loglevel'; 5 | 6 | const NavbarContainer = React.createClass({ 7 | 8 | render: function() { 9 | return ( 10 | 11 | ); 12 | } 13 | 14 | }); 15 | 16 | const mapStateToProps = function(store) { 17 | return { 18 | apiSettings: store.apiSettings, 19 | loading: store.loading 20 | }; 21 | }; 22 | 23 | export default connect(mapStateToProps)(NavbarContainer); -------------------------------------------------------------------------------- /gui/src/app/containers/plot-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Plot from '../components/plot'; 4 | import log from 'loglevel'; 5 | import store from '../store'; 6 | 7 | class PlotContainer extends React.Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | } 12 | 13 | render() { 14 | return ( 15 | 20 | ); 21 | } 22 | 23 | } 24 | 25 | const mapStateToProps = function(store) { 26 | return { 27 | modelSimulation: store.modelSimulation, 28 | plotVariables: store.plotVariables.plotVariables 29 | }; 30 | }; 31 | 32 | export default connect(mapStateToProps)(PlotContainer); -------------------------------------------------------------------------------- /gui/src/app/containers/plot-selection-menu-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PlotSelectionMenu from '../components/plot-selection-menu'; 4 | import log from 'loglevel'; 5 | import store from '../store'; 6 | 7 | const PlotSelectionMenuContainer = React.createClass({ 8 | 9 | render: function() { 10 | return ( 11 | 13 | ); 14 | } 15 | 16 | }); 17 | 18 | const mapStateToProps = function(store) { 19 | return { 20 | plotVariables: store.plotVariables.plotVariables, 21 | filterString: store.plotVariables.filterString 22 | }; 23 | }; 24 | 25 | export default connect(mapStateToProps)(PlotSelectionMenuContainer); -------------------------------------------------------------------------------- /gui/src/app/containers/simulation-inputs-dialog-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import SimulationInputsDialog from '../components/simulation-inputs-dialog'; 4 | import log from 'loglevel'; 5 | import store from '../store'; 6 | 7 | class SimulationInputsDialogContainer extends React.Component { 8 | 9 | constructor (props) { 10 | super(props); 11 | } 12 | 13 | componentDidMount () { 14 | } 15 | 16 | render() { 17 | return ( 18 | 21 | ); 22 | } 23 | 24 | } 25 | 26 | const mapStateToProps = function(store) { 27 | return { 28 | config: store.configDefinition.config 29 | }; 30 | }; 31 | 32 | export default connect(mapStateToProps)(SimulationInputsDialogContainer); -------------------------------------------------------------------------------- /gui/src/app/containers/simulation-parameters-dialog-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import SimulationParametersDialog from '../components/simulation-parameters-dialog'; 4 | import log from 'loglevel'; 5 | import store from '../store'; 6 | 7 | class SimulationParametersDialogContainer extends React.Component { 8 | 9 | constructor (props) { 10 | super(props); 11 | } 12 | 13 | componentDidMount () { 14 | } 15 | 16 | render() { 17 | return ( 18 | 22 | ); 23 | } 24 | 25 | } 26 | 27 | const mapStateToProps = function(store) { 28 | return { 29 | modelDescription: store.modelDescription.description, 30 | modelParameters: store.modelParameters 31 | }; 32 | }; 33 | 34 | export default connect(mapStateToProps)(SimulationParametersDialogContainer); -------------------------------------------------------------------------------- /gui/src/app/containers/tab-dashboard-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import TabDashboard from '../components/tab-dashboard'; 4 | import log from 'loglevel'; 5 | import store from '../store'; 6 | 7 | class TabDashboardContainer extends React.Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | } 12 | 13 | render() { 14 | return ( 15 | 23 | ); 24 | } 25 | 26 | } 27 | 28 | const mapStateToProps = function(store) { 29 | return { 30 | modelDescription: store.modelDescription.description, 31 | modelSimulation: store.modelSimulation, 32 | plotVariables: store.plotVariables.plotVariables, 33 | modelParameters: store.modelParameters, 34 | dashboard: store.dashboardDefinition.dashboard, 35 | inputFileName: store.modelInput.fileName 36 | }; 37 | }; 38 | 39 | export default connect(mapStateToProps)(TabDashboardContainer); -------------------------------------------------------------------------------- /gui/src/app/containers/tab-info-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import TabInfo from '../components/tab-info'; 4 | import log from 'loglevel'; 5 | import store from '../store'; 6 | 7 | class TabInfoContainer extends React.Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | } 12 | 13 | render() { 14 | return ( 15 | 19 | ); 20 | } 21 | 22 | } 23 | 24 | const mapStateToProps = function(store) { 25 | return { 26 | readme: store.readme 27 | }; 28 | }; 29 | 30 | export default connect(mapStateToProps)(TabInfoContainer); -------------------------------------------------------------------------------- /gui/src/app/containers/tab-model-description-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import TabModelDescription from '../components/tab-model-description'; 4 | import log from 'loglevel'; 5 | import store from '../store'; 6 | 7 | class TabModelDescriptionContainer extends React.Component { 8 | 9 | constructor (props) { 10 | super(props); 11 | } 12 | 13 | componentDidMount () { 14 | } 15 | 16 | render() { 17 | return ( 18 | 28 | ); 29 | } 30 | 31 | } 32 | 33 | const mapStateToProps = function(store) { 34 | return { 35 | modelDescription: store.modelDescription.description, 36 | filterStr: store.modelDescription.filterStr, 37 | filterParameters: store.modelDescription.filterParameters, 38 | filterConstants: store.modelDescription.filterConstants, 39 | filterInputs: store.modelDescription.filterInputs, 40 | filterOutputs: store.modelDescription.filterOutputs, 41 | filterContinuous: store.modelDescription.filterContinuous, 42 | filterHidden: store.modelDescription.filterHidden 43 | }; 44 | }; 45 | 46 | export default connect(mapStateToProps)(TabModelDescriptionContainer); -------------------------------------------------------------------------------- /gui/src/app/containers/tab-simulation-container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import TabSimulation from '../components/tab-simulation'; 4 | import log from 'loglevel'; 5 | import store from '../store'; 6 | 7 | class TabSimulationContainer extends React.Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | } 12 | 13 | render() { 14 | return ( 15 | 22 | ); 23 | } 24 | 25 | } 26 | 27 | const mapStateToProps = function(store) { 28 | return { 29 | modelDescription: store.modelDescription.description, 30 | modelSimulation: store.modelSimulation, 31 | plotVariables: store.plotVariables.plotVariables, 32 | modelParameters: store.modelParameters, 33 | inputFileName: store.modelInput.fileName 34 | }; 35 | }; 36 | 37 | export default connect(mapStateToProps)(TabSimulationContainer); -------------------------------------------------------------------------------- /gui/src/app/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {render} from 'react-dom'; 3 | import injectTapEventPlugin from 'react-tap-event-plugin'; 4 | import Main from './Main'; 5 | 6 | import log from 'loglevel'; 7 | 8 | log.setLevel(0); 9 | 10 | 11 | // Needed for onTouchTap 12 | // http://stackoverflow.com/a/34015469/988941 13 | injectTapEventPlugin(); 14 | 15 | // Render the main app react component into the app div. 16 | // For more details see: https://facebook.github.io/react/docs/top-level-api.html#react.render 17 | render(
, document.getElementById('app')); 18 | -------------------------------------------------------------------------------- /gui/src/app/layouts/main-layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import NavbarContainer from '../containers/navbar-container.js'; 3 | import Footer from '../components/footer.js'; 4 | 5 | const MainLayout = React.createClass({ 6 | render: function() { 7 | return ( 8 |
9 |
10 | 11 |
12 |
13 | {this.props.children} 14 |
15 |
16 |
17 | ); 18 | } 19 | }); 20 | 21 | export default MainLayout; 22 | -------------------------------------------------------------------------------- /gui/src/app/reducers/api-settings-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | 5 | const initialState = { 6 | url: null, 7 | token: null 8 | }; 9 | 10 | const apiSettingsReducer = function(state = initialState, action) { 11 | switch(action.type) { 12 | 13 | case types.SET_API_SETTINGS_URL: 14 | return Object.assign({}, state, { url: action.url }); 15 | 16 | case types.SET_API_SETTINGS_TOKEN: 17 | return Object.assign({}, state, { token: action.token}); 18 | 19 | case types.RESET_API_SETTINGS: 20 | return Object.assign({}, state, initialState); 21 | 22 | default: 23 | return Object.assign({}, state); 24 | 25 | } 26 | return state; 27 | 28 | } 29 | 30 | export default apiSettingsReducer; -------------------------------------------------------------------------------- /gui/src/app/reducers/config-definition-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | import { Dashboard } from '../classes/dashboard'; 5 | 6 | const initialState = { 7 | config: null 8 | }; 9 | 10 | const DashboardDefinitionReducer = function(state = initialState, action) { 11 | switch(action.type) { 12 | 13 | case types.SET_CONFIG_DEFINITION: 14 | return Object.assign({}, state, { config: action.config}); 15 | 16 | case types.RESET_CONFIG_DEFINITION: 17 | return Object.assign({}, state, initialState); 18 | 19 | default: 20 | return Object.assign({}, state); 21 | 22 | } 23 | return state; 24 | 25 | } 26 | 27 | export default DashboardDefinitionReducer; -------------------------------------------------------------------------------- /gui/src/app/reducers/dashboard-definition-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | import { Dashboard } from '../classes/dashboard'; 5 | 6 | const initialState = { 7 | dashboard: null 8 | }; 9 | 10 | const DashboardDefinitionReducer = function(state = initialState, action) { 11 | switch(action.type) { 12 | 13 | case types.SET_DASHBOARD_DEFINITION: 14 | const d = new Dashboard(action.dashboard) 15 | return Object.assign({}, state, { dashboard: d}); 16 | 17 | case types.RESET_DASHBOARD_DEFINITION: 18 | return Object.assign({}, state, initialState); 19 | 20 | default: 21 | return Object.assign({}, state); 22 | 23 | } 24 | return state; 25 | 26 | } 27 | 28 | export default DashboardDefinitionReducer; -------------------------------------------------------------------------------- /gui/src/app/reducers/error-message-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | 5 | const initialState = { 6 | message: null 7 | }; 8 | 9 | const ErrorMessageReducer = function(state = initialState, action) { 10 | switch(action.type) { 11 | 12 | case types.SET_ERROR_MESSAGE: 13 | return Object.assign({}, state, { message: action.message }); 14 | 15 | case types.RESET_ERROR_MESSAGE: 16 | return Object.assign({}, state, initialState); 17 | 18 | default: 19 | return Object.assign({}, state); 20 | 21 | } 22 | return state; 23 | 24 | } 25 | 26 | export default ErrorMessageReducer; -------------------------------------------------------------------------------- /gui/src/app/reducers/home-tabs-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | 5 | const initialState = { 6 | tabSelected: 'info' 7 | }; 8 | 9 | const HomeTabsReducer = function(state = initialState, action) { 10 | switch(action.type) { 11 | 12 | case types.SELECT_TAB: 13 | return Object.assign({}, state, { tabSelected: action.tabName }); 14 | 15 | case types.RESET_SELECTED_TAB: 16 | return Object.assign({}, state, initialState); 17 | 18 | default: 19 | return Object.assign({}, state); 20 | 21 | } 22 | return state; 23 | 24 | } 25 | 26 | export default HomeTabsReducer; -------------------------------------------------------------------------------- /gui/src/app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { reducer as formReducer } from 'redux-form' 3 | 4 | // Reducers 5 | import apiSettingsReducer from './api-settings-reducer'; 6 | import modelDescriptionReducer from './model-description-reducer'; 7 | import modelSimulationReducer from './model-simulation-reducer'; 8 | import loadingReducer from './loading-reducer'; 9 | import errorMessageReducer from './error-message-reducer'; 10 | import homeTabsReducer from './home-tabs-reducer'; 11 | import plotResultsReducer from './plot-results-reducer'; 12 | import modelParametersReducer from './model-parameters-reducer'; 13 | import dashboardDefinitionReducer from './dashboard-definition-reducer'; 14 | import configDefinitionreducer from './config-definition-reducer'; 15 | import modelInputReducer from './model-inputs-reducer'; 16 | import readmeReducer from './readme-reducer'; 17 | 18 | // Combine Reducers 19 | var reducers = combineReducers({ 20 | apiSettings: apiSettingsReducer, 21 | modelDescription: modelDescriptionReducer, 22 | modelSimulation: modelSimulationReducer, 23 | modelInput: modelInputReducer, 24 | form: formReducer, 25 | loading: loadingReducer, 26 | error: errorMessageReducer, 27 | homeTabs: homeTabsReducer, 28 | plotVariables: plotResultsReducer, 29 | modelParameters: modelParametersReducer, 30 | dashboardDefinition: dashboardDefinitionReducer, 31 | configDefinition: configDefinitionreducer, 32 | readme: readmeReducer 33 | }); 34 | 35 | export default reducers; -------------------------------------------------------------------------------- /gui/src/app/reducers/loading-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | 5 | const initialState = { 6 | message: null 7 | }; 8 | 9 | const LoadingReducer = function(state = initialState, action) { 10 | switch(action.type) { 11 | 12 | case types.SET_LOADING_MESSAGE: 13 | return Object.assign({}, state, { message: action.message }); 14 | 15 | case types.RESET_LOADING_MESSAGE: 16 | return Object.assign({}, state, initialState); 17 | 18 | default: 19 | return Object.assign({}, state); 20 | 21 | } 22 | return state; 23 | 24 | } 25 | 26 | export default LoadingReducer; -------------------------------------------------------------------------------- /gui/src/app/reducers/model-description-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | import { ModelDescription } from '../classes/model-description'; 5 | 6 | const initialState = { 7 | description: null, 8 | filterStr: null, 9 | filterParameters: true, 10 | filterConstants: true, 11 | filterInputs: true, 12 | filterOutputs: true, 13 | filterContinuous: true, 14 | filterHidden: false 15 | }; 16 | 17 | const parse = new DOMParser(); 18 | 19 | const modelDescriptionReducer = function(state = initialState, action) { 20 | switch(action.type) { 21 | 22 | case types.SET_MODEL_DESCRIPTION: 23 | const md = new ModelDescription(); 24 | md.loadFromXml(action.modelDescriptionXml); 25 | return Object.assign({}, state, { description: md}); 26 | 27 | case types.RESET_MODEL_DESCRIPTION: 28 | return Object.assign({}, state, { description: null}); 29 | 30 | case types.UPDATE_FILTER_STRING: 31 | return Object.assign({}, state, { filterStr: action.filterStr}); 32 | 33 | case types.UPDATE_FILTER_PARAMETERS: 34 | return Object.assign({}, state, { filterParameters: action.isChecked}); 35 | 36 | case types.UPDATE_FILTER_CONSTANTS: 37 | return Object.assign({}, state, { filterConstants: action.isChecked}); 38 | 39 | case types.UPDATE_FILTER_INPUTS: 40 | return Object.assign({}, state, { filterInputs: action.isChecked}); 41 | 42 | case types.UPDATE_FILTER_OUTPUTS: 43 | return Object.assign({}, state, { filterOutputs: action.isChecked}); 44 | 45 | case types.UPDATE_FILTER_CONTINUOUS: 46 | return Object.assign({}, state, { filterContinuous: action.isChecked}); 47 | 48 | case types.UPDATE_FILTER_HIDDEN: 49 | return Object.assign({}, state, { filterHidden: action.isChecked}); 50 | 51 | default: 52 | return Object.assign({}, state); 53 | 54 | } 55 | return state; 56 | 57 | } 58 | 59 | export default modelDescriptionReducer; -------------------------------------------------------------------------------- /gui/src/app/reducers/model-inputs-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | 5 | const initialState = { 6 | fileName: null 7 | }; 8 | 9 | const modelInputReducer = function(state = initialState, action) { 10 | switch(action.type) { 11 | 12 | case types.SET_INPUT_FILE: 13 | return Object.assign({}, state, { fileName: action.fileName}); 14 | 15 | case types.RESET_INPUT_FILE: 16 | return Object.assign({}, initialState); 17 | 18 | default: 19 | return Object.assign({}, state); 20 | 21 | } 22 | return state; 23 | 24 | } 25 | 26 | export default modelInputReducer; -------------------------------------------------------------------------------- /gui/src/app/reducers/model-parameters-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | 5 | const initialState = { 6 | parameters: {}, 7 | filterString: null 8 | }; 9 | 10 | const ModelParametersReducer = function(state = initialState, action) { 11 | switch(action.type) { 12 | 13 | case types.UPDATE_PARAMETERS_FILTER_STRING: 14 | return Object.assign({}, state, { filterString: action.filterString }); 15 | 16 | case types.UPDATE_MODEL_PARAMETER: 17 | const voidValue = action.newValue === null || action.newValue === undefined || 18 | action.newValue.replace(/ /g,'') === ''; 19 | const validNumber = !voidValue && !isNaN(+action.newValue); 20 | const areEqual = +action.defaultValue === +action.newValue; 21 | 22 | if(voidValue){ 23 | const isPresent = action.name in state.parameters; 24 | if(isPresent){ 25 | // Remove the parameter 26 | let res = Object.assign({}, state); 27 | delete res.parameters[action.name]; 28 | return res; 29 | } 30 | }else if (validNumber){ 31 | // Add/modify parameter to the state 32 | let newState = { 33 | ...state, 34 | parameters: { 35 | ...state.parameters, 36 | [action.name]: action.newValue, 37 | } 38 | } 39 | return newState; 40 | } 41 | 42 | case types.REMOVE_MODEL_PARAMETER: 43 | let res = Object.assign({}, state); 44 | if(action.name in state.parameters){ 45 | // Remove the parameter 46 | delete res.parameters[action.name]; 47 | } 48 | return res; 49 | 50 | case types.RESET_MODEL_PARAMETERS: 51 | return Object.assign({}, state, initialState); 52 | 53 | default: 54 | return Object.assign({}, state); 55 | 56 | } 57 | return state; 58 | 59 | } 60 | 61 | export default ModelParametersReducer; -------------------------------------------------------------------------------- /gui/src/app/reducers/model-simulation-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | 5 | const initialState = { 6 | startTime: null, 7 | finalTime: null, 8 | options: null, 9 | parameters: null, 10 | results: null, 11 | }; 12 | 13 | const modelSimulationReducer = function(state = initialState, action) { 14 | switch(action.type) { 15 | 16 | case types.SAVE_SIMULATION_RESULTS: 17 | return Object.assign( 18 | {}, state, { 19 | startTime: action.startTime, 20 | finalTime: action.finalTime, 21 | options: action.simOptions, 22 | parameters: action.parameters, 23 | results: action.response 24 | }); 25 | 26 | case types.RESET_SIMULATION_RESULTS: 27 | return Object.assign({}, initialState); 28 | 29 | default: 30 | return Object.assign({}, state); 31 | 32 | } 33 | return state; 34 | 35 | } 36 | 37 | export default modelSimulationReducer; -------------------------------------------------------------------------------- /gui/src/app/reducers/plot-results-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | 5 | const initialState = { 6 | plotVariables: null, 7 | filterString: null 8 | }; 9 | 10 | const plotResultsReducer = function(state = initialState, action) { 11 | switch(action.type) { 12 | 13 | case types.SAVE_SIMULATION_RESULTS: 14 | if(state.plotVariables){ 15 | return Object.assign({}, state); 16 | }else{ 17 | let plotVars = {}; 18 | for(let resName in action.response){ 19 | if(resName !== 'time'){ 20 | plotVars[resName] = false; 21 | } 22 | } 23 | return Object.assign({}, state, {plotVariables: plotVars}); 24 | } 25 | 26 | case types.SET_PLOT_VARIABLE: 27 | if(action.varName){ 28 | return { 29 | ...state, 30 | plotVariables: { 31 | ...state.plotVariables, 32 | [action.varName]: action.show 33 | } 34 | } 35 | }else{ 36 | return Object.assign({}, state); 37 | } 38 | 39 | case types.UPDATE_PLOT_FILTER_STRING: 40 | return Object.assign({}, state, {filterString: action.filterStr}); 41 | 42 | case types.RESET_PLOT_RESULTS: 43 | return Object.assign({}, initialState); 44 | 45 | default: 46 | return Object.assign({}, state); 47 | 48 | } 49 | return state; 50 | 51 | } 52 | 53 | export default plotResultsReducer; -------------------------------------------------------------------------------- /gui/src/app/reducers/readme-reducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/action-types'; 2 | import _ from 'lodash'; 3 | import log from 'loglevel'; 4 | 5 | const initialState = { 6 | content: '' 7 | }; 8 | 9 | const readmeReducer = function(state = initialState, action) { 10 | switch(action.type) { 11 | 12 | case types.SET_README: 13 | return Object.assign({}, state, { content: action.readme }); 14 | 15 | case types.RESET_README: 16 | return Object.assign({}, state, initialState); 17 | 18 | default: 19 | return Object.assign({}, state); 20 | 21 | } 22 | return state; 23 | 24 | } 25 | 26 | export default readmeReducer; -------------------------------------------------------------------------------- /gui/src/app/router.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Router, Route, browserHistory, Redirect } from 'react-router'; 3 | 4 | import MainLayout from './layouts/main-layout'; 5 | import HomeContainer from './containers/home-container'; 6 | import AboutView from './views/about.js'; 7 | import ApiDocsView from './views/api-docs.js'; 8 | 9 | export const basePath = process.env.NODE_ENV === 'production' ? '/LambdaSim' : ''; 10 | 11 | // Set the routes associated to the layouts and pages 12 | // Look at this when deploying to S3 13 | // http://aserafin.pl/2016/03/23/react-router-on-amazon-s3/ 14 | export default ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | -------------------------------------------------------------------------------- /gui/src/app/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import {persistStore, autoRehydrate} from 'redux-persist'; 3 | import reducers from './reducers'; 4 | 5 | const store = createStore(reducers, undefined, autoRehydrate()) 6 | persistStore(store); 7 | export default store; 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /gui/src/app/style/components/_global.scss: -------------------------------------------------------------------------------- 1 | //Default styles 2 | 3 | html { 4 | box-sizing: border-box; 5 | font-family: 'Roboto', sans-serif; 6 | } 7 | *, *:before, *:after { 8 | box-sizing: inherit; 9 | } 10 | 11 | body { 12 | // display: flex; 13 | // min-height: 100vh; 14 | // flex-direction: column; 15 | } 16 | 17 | main { 18 | // flex: 1 0 auto; 19 | } 20 | 21 | ul { 22 | &:not(.browser-default) { 23 | padding-left: 0; 24 | list-style-type: none; 25 | 26 | li { 27 | list-style-type: none; 28 | } 29 | } 30 | } 31 | 32 | a { 33 | color: $link-color; 34 | text-decoration: none; 35 | 36 | // Gets rid of tap active state 37 | -webkit-tap-highlight-color: transparent; 38 | } 39 | 40 | 41 | // Positioning 42 | .valign-wrapper { 43 | display: flex; 44 | align-items: center; 45 | 46 | .valign { 47 | display: block; 48 | } 49 | } 50 | 51 | 52 | // classic clearfix 53 | .clearfix { 54 | clear: both; 55 | } 56 | 57 | // Dividers 58 | .divider { 59 | height: 1px; 60 | overflow: hidden; 61 | background-color: color("grey", "lighten-2"); 62 | } 63 | 64 | 65 | // Icon Styles 66 | i { 67 | line-height: inherit; 68 | 69 | &.left { 70 | float: left; 71 | margin-right: 15px; 72 | } 73 | &.right { 74 | float: right; 75 | margin-left: 15px; 76 | } 77 | &.tiny { 78 | font-size: 1rem; 79 | } 80 | &.small { 81 | font-size: 2rem; 82 | } 83 | &.medium { 84 | font-size: 4rem; 85 | } 86 | &.large { 87 | font-size: 6rem; 88 | } 89 | } 90 | 91 | // Images 92 | img.responsive-img, 93 | video.responsive-video { 94 | max-width: 100%; 95 | height: auto; 96 | } 97 | 98 | 99 | /********************* 100 | Media Query Classes 101 | **********************/ 102 | .hide-on-small-only, .hide-on-small-and-down { 103 | @media #{$small-and-down} { 104 | display: none !important; 105 | } 106 | } 107 | .hide-on-med-and-down { 108 | @media #{$medium-and-down} { 109 | display: none !important; 110 | } 111 | } 112 | .hide-on-med-and-up { 113 | @media #{$medium-and-up} { 114 | display: none !important; 115 | } 116 | } 117 | .hide-on-med-only { 118 | @media only screen and (min-width: $small-screen) and (max-width: $medium-screen) { 119 | display: none !important; 120 | } 121 | } 122 | .hide-on-large-only { 123 | @media #{$large-and-up} { 124 | display: none !important; 125 | } 126 | } 127 | .show-on-large { 128 | @media #{$large-and-up} { 129 | display: block !important; 130 | } 131 | } 132 | .show-on-medium { 133 | @media only screen and (min-width: $small-screen) and (max-width: $medium-screen) { 134 | display: block !important; 135 | } 136 | } 137 | .show-on-small { 138 | @media #{$small-and-down} { 139 | display: block !important; 140 | } 141 | } 142 | .show-on-medium-and-up { 143 | @media #{$medium-and-up} { 144 | display: block !important; 145 | } 146 | } 147 | .show-on-medium-and-down { 148 | @media #{$medium-and-down} { 149 | display: block !important; 150 | } 151 | } 152 | 153 | 154 | // Center text on mobile 155 | .center-on-small-only { 156 | @media #{$small-and-down} { 157 | text-align: center; 158 | } 159 | } 160 | 161 | // Footer 162 | footer.page-footer { 163 | margin-top: 20px; 164 | padding-top: 20px; 165 | background-color: $footer-bg-color; 166 | 167 | .footer-copyright { 168 | overflow: hidden; 169 | height: 50px; 170 | line-height: 50px; 171 | color: rgba(255,255,255,.8); 172 | background-color: rgba(51,51,51,.08); 173 | @extend .light; 174 | } 175 | } 176 | 177 | /******************* 178 | Utility Classes 179 | *******************/ 180 | 181 | .hide { 182 | display: none !important; 183 | } 184 | 185 | // Text Align 186 | .left-align { 187 | text-align: left; 188 | } 189 | .right-align { 190 | text-align: right 191 | } 192 | .center, .center-align { 193 | text-align: center; 194 | } 195 | 196 | .left { 197 | float: left !important; 198 | } 199 | .right { 200 | float: right !important; 201 | } 202 | 203 | // No Text Select 204 | .no-select { 205 | -webkit-touch-callout: none; 206 | -webkit-user-select: none; 207 | -khtml-user-select: none; 208 | -moz-user-select: none; 209 | -ms-user-select: none; 210 | user-select: none; 211 | } 212 | 213 | .circle { 214 | border-radius: 50%; 215 | } 216 | 217 | .center-block { 218 | display: block; 219 | margin-left: auto; 220 | margin-right: auto; 221 | } 222 | 223 | .truncate { 224 | display: block; 225 | white-space: nowrap; 226 | overflow: hidden; 227 | text-overflow: ellipsis; 228 | } 229 | 230 | .no-padding { 231 | padding: 0 !important; 232 | } 233 | -------------------------------------------------------------------------------- /gui/src/app/style/components/_grid.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 0 auto; 3 | max-width: 1280px; 4 | width: 100%; 5 | } 6 | @media #{$medium-and-up} { 7 | .container { 8 | width: 100%; 9 | } 10 | } 11 | @media #{$large-and-up} { 12 | .container { 13 | width: 80%; 14 | } 15 | } 16 | .container .row { 17 | margin-left: (-1 * $gutter-width / 2); 18 | margin-right: (-1 * $gutter-width / 2); 19 | } 20 | 21 | .section { 22 | padding-top: 1rem; 23 | padding-bottom: 1rem; 24 | 25 | &.no-pad { 26 | padding: 0; 27 | } 28 | &.no-pad-bot { 29 | padding-bottom: 0; 30 | } 31 | &.no-pad-top { 32 | padding-top: 0; 33 | } 34 | } 35 | 36 | 37 | .row { 38 | margin-left: auto; 39 | margin-right: auto; 40 | margin-bottom: 20px; 41 | 42 | // Clear floating children 43 | &:after { 44 | content: ""; 45 | display: table; 46 | clear: both; 47 | } 48 | 49 | .col { 50 | float: left; 51 | box-sizing: border-box; 52 | padding: 0 $gutter-width / 2; 53 | min-height: 1px; 54 | 55 | &[class*="push-"], 56 | &[class*="pull-"] { 57 | position: relative; 58 | } 59 | 60 | $i: 1; 61 | @while $i <= $num-cols { 62 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 63 | &.s#{$i} { 64 | width: $perc; 65 | margin-left: auto; 66 | left: auto; 67 | right: auto; 68 | } 69 | $i: $i + 1; 70 | } 71 | 72 | $i: 1; 73 | @while $i <= $num-cols { 74 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 75 | &.offset-s#{$i} { 76 | margin-left: $perc; 77 | } 78 | &.pull-s#{$i} { 79 | right: $perc; 80 | } 81 | &.push-s#{$i} { 82 | left: $perc; 83 | } 84 | $i: $i + 1; 85 | } 86 | 87 | @media #{$medium-and-up} { 88 | 89 | $i: 1; 90 | @while $i <= $num-cols { 91 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 92 | &.m#{$i} { 93 | width: $perc; 94 | margin-left: auto; 95 | left: auto; 96 | right: auto; 97 | } 98 | $i: $i + 1 99 | } 100 | 101 | $i: 1; 102 | @while $i <= $num-cols { 103 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 104 | &.offset-m#{$i} { 105 | margin-left: $perc; 106 | } 107 | &.pull-m#{$i} { 108 | right: $perc; 109 | } 110 | &.push-m#{$i} { 111 | left: $perc; 112 | } 113 | $i: $i + 1; 114 | } 115 | } 116 | 117 | @media #{$large-and-up} { 118 | 119 | $i: 1; 120 | @while $i <= $num-cols { 121 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 122 | &.l#{$i} { 123 | width: $perc; 124 | margin-left: auto; 125 | left: auto; 126 | right: auto; 127 | } 128 | $i: $i + 1; 129 | } 130 | 131 | $i: 1; 132 | @while $i <= $num-cols { 133 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 134 | &.offset-l#{$i} { 135 | margin-left: $perc; 136 | } 137 | &.pull-l#{$i} { 138 | right: $perc; 139 | } 140 | &.push-l#{$i} { 141 | left: $perc; 142 | } 143 | $i: $i + 1; 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /gui/src/app/style/components/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS and IE text size adjust after device orientation change, 6 | * without disabling user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability of focused elements when they are also in an 95 | * active/hover state. 96 | */ 97 | 98 | a:active, 99 | a:hover { 100 | outline: 0; 101 | } 102 | 103 | /* Text-level semantics 104 | ========================================================================== */ 105 | 106 | /** 107 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 108 | */ 109 | 110 | abbr[title] { 111 | border-bottom: 1px dotted; 112 | } 113 | 114 | /** 115 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 116 | */ 117 | 118 | b, 119 | strong { 120 | font-weight: bold; 121 | } 122 | 123 | /** 124 | * Address styling not present in Safari and Chrome. 125 | */ 126 | 127 | dfn { 128 | font-style: italic; 129 | } 130 | 131 | /** 132 | * Address variable `h1` font-size and margin within `section` and `article` 133 | * contexts in Firefox 4+, Safari, and Chrome. 134 | */ 135 | 136 | h1 { 137 | font-size: 2em; 138 | margin: 0.67em 0; 139 | } 140 | 141 | /** 142 | * Address styling not present in IE 8/9. 143 | */ 144 | 145 | mark { 146 | background: #ff0; 147 | color: #000; 148 | } 149 | 150 | /** 151 | * Address inconsistent and variable font size in all browsers. 152 | */ 153 | 154 | small { 155 | font-size: 80%; 156 | } 157 | 158 | /** 159 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 160 | */ 161 | 162 | sub, 163 | sup { 164 | font-size: 75%; 165 | line-height: 0; 166 | position: relative; 167 | vertical-align: baseline; 168 | } 169 | 170 | sup { 171 | top: -0.5em; 172 | } 173 | 174 | sub { 175 | bottom: -0.25em; 176 | } 177 | 178 | /* Embedded content 179 | ========================================================================== */ 180 | 181 | /** 182 | * Remove border when inside `a` element in IE 8/9/10. 183 | */ 184 | 185 | img { 186 | border: 0; 187 | } 188 | 189 | /** 190 | * Correct overflow not hidden in IE 9/10/11. 191 | */ 192 | 193 | svg:not(:root) { 194 | overflow: hidden; 195 | } 196 | 197 | /* Grouping content 198 | ========================================================================== */ 199 | 200 | /** 201 | * Address margin not present in IE 8/9 and Safari. 202 | */ 203 | 204 | figure { 205 | margin: 1em 40px; 206 | } 207 | 208 | /** 209 | * Address differences between Firefox and other browsers. 210 | */ 211 | 212 | hr { 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. 354 | */ 355 | 356 | input[type="search"] { 357 | -webkit-appearance: textfield; /* 1 */ 358 | box-sizing: content-box; /* 2 */ 359 | } 360 | 361 | /** 362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 363 | * Safari (but not Chrome) clips the cancel button when the search input has 364 | * padding (and `textfield` appearance). 365 | */ 366 | 367 | input[type="search"]::-webkit-search-cancel-button, 368 | input[type="search"]::-webkit-search-decoration { 369 | -webkit-appearance: none; 370 | } 371 | 372 | /** 373 | * Define consistent border, margin, and padding. 374 | */ 375 | 376 | fieldset { 377 | border: 1px solid #c0c0c0; 378 | margin: 0 2px; 379 | padding: 0.35em 0.625em 0.75em; 380 | } 381 | 382 | /** 383 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 385 | */ 386 | 387 | legend { 388 | border: 0; /* 1 */ 389 | padding: 0; /* 2 */ 390 | } 391 | 392 | /** 393 | * Remove default vertical scrollbar in IE 8/9/10/11. 394 | */ 395 | 396 | textarea { 397 | overflow: auto; 398 | } 399 | 400 | /** 401 | * Don't inherit the `font-weight` (applied by a rule above). 402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 403 | */ 404 | 405 | optgroup { 406 | font-weight: bold; 407 | } 408 | 409 | /* Tables 410 | ========================================================================== */ 411 | 412 | /** 413 | * Remove most spacing between table cells. 414 | */ 415 | 416 | table { 417 | border-collapse: collapse; 418 | border-spacing: 0; 419 | } 420 | 421 | td, 422 | th { 423 | padding: 0; 424 | } 425 | -------------------------------------------------------------------------------- /gui/src/app/style/components/_typography.scss: -------------------------------------------------------------------------------- 1 | 2 | a { 3 | text-decoration: none; 4 | } 5 | 6 | html{ 7 | line-height: 1.5; 8 | 9 | @media only screen and (min-width: 0) { 10 | font-size: 14px; 11 | } 12 | 13 | @media only screen and (min-width: $medium-screen) { 14 | font-size: 14.5px; 15 | } 16 | 17 | @media only screen and (min-width: $large-screen) { 18 | font-size: 15px; 19 | } 20 | 21 | font-family: "Roboto", sans-serif; 22 | font-weight: normal; 23 | color: $off-black; 24 | } 25 | h1, h2, h3, h4, h5, h6 { 26 | font-weight: 400; 27 | line-height: 1.1; 28 | } 29 | 30 | // Header Styles 31 | h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; } 32 | h1 { font-size: $h1-fontsize; line-height: 110%; margin: ($h1-fontsize / 2) 0 ($h1-fontsize / 2.5) 0;} 33 | h2 { font-size: $h2-fontsize; line-height: 110%; margin: ($h2-fontsize / 2) 0 ($h2-fontsize / 2.5) 0;} 34 | h3 { font-size: $h3-fontsize; line-height: 110%; margin: ($h3-fontsize / 2) 0 ($h3-fontsize / 2.5) 0;} 35 | h4 { font-size: $h4-fontsize; line-height: 110%; margin: ($h4-fontsize / 2) 0 ($h4-fontsize / 2.5) 0;} 36 | h5 { font-size: $h5-fontsize; line-height: 110%; margin: ($h5-fontsize / 2) 0 ($h5-fontsize / 2.5) 0;} 37 | h6 { font-size: $h6-fontsize; line-height: 110%; margin: ($h6-fontsize / 2) 0 ($h6-fontsize / 2.5) 0;} 38 | 39 | // Text Styles 40 | em { font-style: italic; } 41 | strong { font-weight: 500; } 42 | small { font-size: 75%; } 43 | .light { font-weight: 300; } 44 | .thin { font-weight: 200; } 45 | 46 | 47 | .flow-text{ 48 | font-weight: 300; 49 | $i: 0; 50 | @while $i <= $intervals { 51 | @media only screen and (min-width : 360 + ($i * $interval-size)) { 52 | font-size: 1.2rem * (1 + (.02 * $i)); 53 | } 54 | $i: $i + 1; 55 | } 56 | 57 | // Handle below 360px screen 58 | @media only screen and (max-width: 360px) { 59 | font-size: 1.2rem; 60 | } 61 | } -------------------------------------------------------------------------------- /gui/src/app/style/components/_variables.scss: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Materialize variables 3 | // ========================================================================== 4 | // 5 | // Table of Contents: 6 | // 7 | // 1. Colors 8 | // 2. Badges 9 | // 3. Buttons 10 | // 4. Cards 11 | // 5. Collapsible 12 | // 6. Chips 13 | // 7. Date Picker 14 | // 8. Dropdown 15 | // 10. Forms 16 | // 11. Global 17 | // 12. Grid 18 | // 13. Navigation Bar 19 | // 14. Side Navigation 20 | // 15. Photo Slider 21 | // 16. Spinners | Loaders 22 | // 17. Tabs 23 | // 18. Tables 24 | // 19. Toasts 25 | // 20. Typography 26 | // 21. Footer 27 | // 22. Flow Text 28 | // 23. Collections 29 | // 24. Progress Bar 30 | 31 | 32 | 33 | // 1. Colors 34 | // ========================================================================== 35 | 36 | $primary-color: color("orange", "lighten-2") !default; 37 | $primary-color-light: lighten($primary-color, 15%) !default; 38 | $primary-color-dark: darken($primary-color, 15%) !default; 39 | 40 | $secondary-color: color("orange", "base") !default; 41 | $success-color: color("teal", "accent-3") !default; 42 | $error-color: color("red", "lighten-2") !default; 43 | $link-color: color("grey", "darken-4") !default; 44 | 45 | // 11. Global 46 | // ========================================================================== 47 | 48 | // Media Query Ranges 49 | $small-screen-up: 601px !default; 50 | $medium-screen-up: 993px !default; 51 | $large-screen-up: 1201px !default; 52 | $small-screen: 600px !default; 53 | $medium-screen: 992px !default; 54 | $large-screen: 1200px !default; 55 | 56 | $medium-and-up: "only screen and (min-width : #{$small-screen-up})" !default; 57 | $large-and-up: "only screen and (min-width : #{$medium-screen-up})" !default; 58 | $small-and-down: "only screen and (max-width : #{$small-screen})" !default; 59 | $medium-and-down: "only screen and (max-width : #{$medium-screen})" !default; 60 | $medium-only: "only screen and (min-width : #{$small-screen-up}) and (max-width : #{$medium-screen})" !default; 61 | 62 | 63 | // 12. Grid 64 | // ========================================================================== 65 | 66 | $num-cols: 12 !default; 67 | $gutter-width: 1.5rem !default; 68 | $element-top-margin: $gutter-width/3 !default; 69 | $element-bottom-margin: ($gutter-width*2)/3 !default; 70 | 71 | 72 | // 20. Typography 73 | // ========================================================================== 74 | 75 | $off-black: rgba(0, 0, 0, 0.87) !default; 76 | // Header Styles 77 | $h1-fontsize: 4.2rem !default; 78 | $h2-fontsize: 3.56rem !default; 79 | $h3-fontsize: 2.92rem !default; 80 | $h4-fontsize: 2.28rem !default; 81 | $h5-fontsize: 1.64rem !default; 82 | $h6-fontsize: 1rem !default; 83 | 84 | 85 | // 21. Footer 86 | // ========================================================================== 87 | $footer-bg-color: $primary-color !default; 88 | 89 | 90 | // 22. Flow Text 91 | // ========================================================================== 92 | $range : $large-screen - $small-screen !default; 93 | $intervals: 20 !default; 94 | $interval-size: $range / $intervals !default; 95 | -------------------------------------------------------------------------------- /gui/src/app/style/materialize-light.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import "components/color"; 4 | 5 | // Variables; 6 | @import "components/variables"; 7 | 8 | // Reset 9 | @import "components/normalize"; 10 | 11 | // components 12 | @import "components/global"; 13 | @import "components/grid"; 14 | @import "components/typography"; 15 | -------------------------------------------------------------------------------- /gui/src/app/utils/redirect.js: -------------------------------------------------------------------------------- 1 | import { browserHistory } from 'react-router'; 2 | import store from '../store'; 3 | import log from 'loglevel'; 4 | 5 | /** 6 | * Redirect to path if the profile property has an undefined username 7 | * associated to it. 8 | */ 9 | export default function redirectIfNotLogged(path, next) { 10 | if(store.getState().user.userProfile.username === undefined){ 11 | if(next === undefined || next === null){ 12 | browserHistory.push(path); 13 | }else{ 14 | browserHistory.push(path+"?next="+next); 15 | } 16 | 17 | } 18 | } -------------------------------------------------------------------------------- /gui/src/app/views/about.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import log from 'loglevel'; 3 | 4 | const containerStyle = { 5 | marginTop: 20, 6 | marginLeft: 10, 7 | marginRight: 10, 8 | minHeight: 400 9 | }; 10 | 11 | class AboutView extends React.Component { 12 | 13 | constructor(props) { 14 | super(props); 15 | } 16 | 17 | 18 | render() { 19 | return ( 20 |
21 |
22 |

About

23 |

24 | λ-Sim is a tool that converts simulation models into REST APIs. 25 | The figure below gives an idea of what the tool does for you 26 |

27 |
    28 |
  1. 29 | It takes a simulation model exported from Matlab or a Modelica 30 | tool and a JSON configuration file, 31 |
  2. 32 |
  3. 33 | it automatically generates a lambda function that simulates the model 34 | and it's exposed via a REST API that is available to any application 35 | that talks HTTP. 36 |
  4. 37 |
38 |
39 | 41 |
42 |
43 |

44 | With λ-Sim you can build a MaaS (Model as a Service) application where 45 | people can access your model, run simulations and visualize the results. 46 |

47 |

48 | λ-Sim is built on top of AWS Lambda 49 | API-gateway, 50 | S3, and 51 | Cloudwatch. 52 | These AWS services allows you to build an application that automatically manages 53 | security updates, handle incoming traffic and scale as needed, monitor performances, 54 | and if necessary applies restrictions and limits to users. 55 | And for all of this there is no charge when your code is not running. 56 |

57 |
58 |
59 | ) 60 | } 61 | } 62 | 63 | export default AboutView; -------------------------------------------------------------------------------- /gui/src/app/views/api-docs.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React from 'react'; 3 | import log from 'loglevel'; 4 | import ReactMarkdown from 'react-markdown'; 5 | 6 | import store from '../store'; 7 | import { setLoadingMessage, resetLoadingMessage } from '../actions/loading-actions'; 8 | import { setErrorMessage } from '../actions/error-message-actions'; 9 | 10 | const containerStyle = { 11 | marginTop: 20, 12 | marginLeft: 10, 13 | marginRight: 10, 14 | minHeight: 400 15 | }; 16 | 17 | const API_DOCS_URL = "https://raw.githubusercontent.com/wiki/mbonvini/LambdaSim/LambdaSim-API.md"; 18 | 19 | class ApiDocs extends React.Component { 20 | 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | content: '' 25 | }; 26 | } 27 | 28 | componentWillMount(){ 29 | store.dispatch(setLoadingMessage('Load API Docs')); 30 | const self = this; 31 | return axios.get(API_DOCS_URL) 32 | .then((response) => { 33 | self.setState({content: response.data}); 34 | store.dispatch(resetLoadingMessage()); 35 | return response; 36 | }) 37 | .catch((error) => { 38 | const msg = 'Errors while getting the API Docs. '+ 39 | 'Please make your internet connection is working'; 40 | store.dispatch(setErrorMessage(msg)); 41 | store.dispatch(resetLoadingMessage()); 42 | log.info(error); 43 | }); 44 | } 45 | 46 | render() { 47 | return ( 48 |
49 |
50 | 51 |
52 |
53 | ) 54 | } 55 | } 56 | 57 | export default ApiDocs; -------------------------------------------------------------------------------- /gui/src/app/views/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import store from '../store'; 4 | import log from 'loglevel'; 5 | import { Tabs, Tab } from 'material-ui/Tabs'; 6 | import TabSimulationContainer from '../containers/tab-simulation-container'; 7 | import TabDashboardContainer from '../containers/tab-dashboard-container'; 8 | import TabModelDescriptionContainer from '../containers/tab-model-description-container'; 9 | import TabInfoContainer from '../containers/tab-info-container'; 10 | import Snackbar from 'material-ui/Snackbar'; 11 | 12 | import { resetErrorMessage } from '../actions/error-message-actions'; 13 | import { selectTab } from '../actions/home-tabs-actions'; 14 | import { setApiSettingsUrl } from '../actions/api-settings-actions'; 15 | import { getModelDescription, getModelConfig, getModelDashboard, getReadme } from '../api/lambda-sim-api'; 16 | 17 | const containerStyle = { 18 | marginTop: 20, 19 | marginLeft: 10, 20 | marginRight: 10, 21 | minHeight: 400 22 | }; 23 | 24 | class HomeView extends React.Component { 25 | 26 | constructor(props) { 27 | super(props); 28 | } 29 | 30 | componentDidMount(){ 31 | if(Object.assign({}, self.props.location.query).hasOwnProperty('api')){ 32 | const url = self.props.location.query.api; 33 | store.dispatch(setApiSettingsUrl(url)); 34 | getModelDescription(); 35 | getModelConfig(); 36 | getModelDashboard(); 37 | getReadme(); 38 | } 39 | } 40 | 41 | render() { 42 | self = this; 43 | return ( 44 |
45 |
46 | {store.dispatch(selectTab(value));}}> 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
63 | {store.dispatch(resetErrorMessage());}} 70 | /> 71 |
72 | ) 73 | } 74 | } 75 | 76 | export default HomeView; -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /gui/src/www/fonts/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/fonts/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /gui/src/www/images/Lambda-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | 25 | 26 | 28 | image/svg+xml 29 | 31 | 32 | 33 | 34 | 54 | 56 | 59 | 62 | 66 | 70 | 74 | 75 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /gui/src/www/images/Lambda-logo@242.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/images/Lambda-logo@242.png -------------------------------------------------------------------------------- /gui/src/www/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/images/favicon-32x32.png -------------------------------------------------------------------------------- /gui/src/www/images/lsim-icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/images/lsim-icon-small.png -------------------------------------------------------------------------------- /gui/src/www/images/lsim-icon-white-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/gui/src/www/images/lsim-icon-white-small.png -------------------------------------------------------------------------------- /gui/src/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | λ-Sim 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /gui/src/www/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: 'Roboto', sans-serif; 3 | } 4 | 5 | body { 6 | font-size: 13px; 7 | line-height: 20px; 8 | margin: 0px; 9 | } -------------------------------------------------------------------------------- /gui/webpack-dev-server.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const buildPath = path.resolve(__dirname, 'build'); 4 | const nodeModulesPath = path.resolve(__dirname, 'node_modules'); 5 | const TransferWebpackPlugin = require('transfer-webpack-plugin'); 6 | 7 | const config = { 8 | // Entry points to the project 9 | entry: [ 10 | 'webpack/hot/dev-server', 11 | 'webpack/hot/only-dev-server', 12 | path.join(__dirname, '/src/app/index.js'), 13 | ], 14 | // Server Configuration options 15 | devServer: { 16 | contentBase: 'src/www', // Relative directory for base of server 17 | devtool: 'eval', 18 | hot: true, // Live-reload 19 | inline: true, 20 | port: 3000, // Port Number 21 | host: 'localhost', // Change to '0.0.0.0' for external facing server 22 | historyApiFallback: true 23 | }, 24 | devtool: 'eval', 25 | output: { 26 | path: buildPath, // Path of output file 27 | filename: 'index.js', 28 | }, 29 | plugins: [ 30 | // Enables Hot Modules Replacement 31 | new webpack.HotModuleReplacementPlugin(), 32 | // Allows error warnings but does not stop compiling. 33 | new webpack.NoErrorsPlugin(), 34 | // Moves files 35 | new TransferWebpackPlugin([ 36 | { from: 'www' }, 37 | ], path.resolve(__dirname, 'src/')), 38 | ], 39 | module: { 40 | loaders: [ 41 | { 42 | test: /\.js$/, // All .js files 43 | loaders: ['babel-loader'], 44 | exclude: [nodeModulesPath], 45 | }, 46 | { 47 | test: /\.css$/, 48 | loader: 'style!css?modules', 49 | include: /flexboxgrid/, 50 | }, 51 | { 52 | test: /\.json$/, 53 | loader: 'json-loader' 54 | } 55 | ], 56 | }, 57 | }; 58 | 59 | module.exports = config; 60 | -------------------------------------------------------------------------------- /gui/webpack-production.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const buildPath = path.resolve(__dirname, 'build'); 4 | const nodeModulesPath = path.resolve(__dirname, 'node_modules'); 5 | const TransferWebpackPlugin = require('transfer-webpack-plugin'); 6 | 7 | const config = { 8 | entry: [path.join(__dirname, '/src/app/index.js')], 9 | // Render source-map file for final build 10 | devtool: 'source-map', 11 | // output config 12 | output: { 13 | path: buildPath, // Path of output file 14 | filename: 'index.js', // Name of output file 15 | }, 16 | plugins: [ 17 | // Define production build to allow React to strip out unnecessary checks 18 | new webpack.DefinePlugin({ 19 | 'process.env':{ 20 | 'NODE_ENV': JSON.stringify('production') 21 | } 22 | }), 23 | new webpack.optimize.DedupePlugin(), 24 | // Minify the bundle 25 | new webpack.optimize.UglifyJsPlugin({ 26 | compress: { 27 | // suppresses warnings, usually from module minification 28 | warnings: false, 29 | }, 30 | }), 31 | new webpack.optimize.AggressiveMergingPlugin(), 32 | // Allows error warnings but does not stop compiling. 33 | new webpack.NoErrorsPlugin(), 34 | // Transfer Files 35 | new TransferWebpackPlugin([ 36 | {from: 'www'}, 37 | ], path.resolve(__dirname, 'src')), 38 | ], 39 | module: { 40 | loaders: [ 41 | { 42 | test: /\.js$/, // All .js files 43 | loaders: ['babel-loader'], // react-hot is like browser sync and babel loads jsx and es6-7 44 | exclude: [nodeModulesPath], 45 | }, 46 | { 47 | test: /\.css$/, 48 | loader: 'style!css?modules', 49 | include: /flexboxgrid/, 50 | }, 51 | { 52 | test: /\.json$/, 53 | loader: 'json-loader' 54 | } 55 | ], 56 | }, 57 | }; 58 | 59 | module.exports = config; 60 | -------------------------------------------------------------------------------- /gui/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | var BUILD_DIR = path.resolve(__dirname, 'src/public'); 5 | var APP_DIR = path.resolve(__dirname, 'src/app'); 6 | 7 | var config = { 8 | entry: APP_DIR + '/index.js', 9 | output: { 10 | path: BUILD_DIR, 11 | filename: 'bundle.js' 12 | }, 13 | module : { 14 | loaders : [ 15 | { 16 | test : /\.jsx?/, 17 | include : APP_DIR, 18 | loader : 'babel' 19 | }, 20 | { 21 | test: /\.json$/, 22 | loader: 'json-loader' 23 | } 24 | ] 25 | } 26 | }; 27 | 28 | module.exports = config; 29 | -------------------------------------------------------------------------------- /iam_roles/assume_role_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": { 4 | "Effect": "Allow", 5 | "Principal": {"Service": "lambda.amazonaws.com"}, 6 | "Action": "sts:AssumeRole" 7 | } 8 | } -------------------------------------------------------------------------------- /iam_roles/role_access_logs_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": "logs:CreateLogGroup", 7 | "Resource": "" 8 | }, 9 | { 10 | "Effect": "Allow", 11 | "Action": [ 12 | "logs:CreateLogStream", 13 | "logs:PutLogEvents" 14 | ], 15 | "Resource": [ 16 | "" 17 | ] 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /iam_roles/role_access_s3_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": ["s3:GetObject"], 7 | "Resource": "arn:aws:s3:::*" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /images/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/images/diagram.png -------------------------------------------------------------------------------- /images/lsim-icon-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/images/lsim-icon-white.png -------------------------------------------------------------------------------- /images/lsim-icon-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 76 | 77 | 82 | 87 | SIM 101 | 102 | -------------------------------------------------------------------------------- /images/lsim-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/images/lsim-icon.png -------------------------------------------------------------------------------- /images/lsim-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 76 | 77 | 80 | 85 | 90 | 95 | 96 | SIM 110 | 111 | 112 | -------------------------------------------------------------------------------- /images/lsim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbonvini/LambdaSim/9170e0b5c620ada11ad5246ae4eaf7a8d4162c5f/images/lsim.png -------------------------------------------------------------------------------- /images/lsim.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 62 | 67 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /setup_s3.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import string 4 | import random 5 | import boto3 6 | import re 7 | import argparse 8 | import json 9 | 10 | BUCKET_PREFIX = "lambda-simulation" 11 | DEFAULT_AWS_REGION = "us-west-2" 12 | 13 | def random_bucket_name(prefix=BUCKET_PREFIX, size=8): 14 | size = 8 15 | chars = string.ascii_lowercase + string.digits 16 | rnd_str = ''.join(random.choice(chars) for _ in range(size)) 17 | return "{}-{}".format(prefix, rnd_str) 18 | 19 | def get_bucket_names(prefix=BUCKET_PREFIX, profile_name=None): 20 | """ 21 | This function returns the name of the S3 bucket where 22 | the FMU are saved. 23 | 24 | :param str prefix: The prefix for the S3 bucket. 25 | :param str profile_name: The profile to use from the AWS 26 | confign file. 27 | :rtype: list[str] 28 | :return: The function returns a list of bucket names that have the 29 | prexix `BUCKET_PREFIX` that could be used to store FMUs. 30 | """ 31 | session = boto3.Session(profile_name=profile_name) 32 | s3 = session.resource('s3') 33 | 34 | regex = r'{}-[a-z0-9]+'.format(prefix) 35 | buckets = [bucket.name for bucket in s3.buckets.all() if re.match(regex, bucket.name)] 36 | 37 | return buckets 38 | 39 | def create_bucket(prefix=BUCKET_PREFIX, profile_name=None, region_name=DEFAULT_AWS_REGION): 40 | """ 41 | This function creates a bucket with a specified prefix 42 | that will contain the FMU models. 43 | If a bucket with a similar name exists the function return 44 | that bucket. 45 | 46 | :param str prefix: The prefix for the S3 bucket. 47 | :param str profile_name: The profile to use from the AWS 48 | confign file. 49 | :param str region_name: AWS region when creating the new bucket. 50 | :rtype: str 51 | :return: The function returns the name of the bucket where 52 | storing the FMU models. 53 | """ 54 | session = boto3.Session(profile_name=profile_name, region_name=region_name) 55 | s3 = session.resource('s3') 56 | buckets = get_bucket_names(BUCKET_PREFIX, profile_name) 57 | 58 | if len(buckets) == 0: 59 | bucket_name = random_bucket_name() 60 | s3.create_bucket( 61 | Bucket=bucket_name, 62 | CreateBucketConfiguration=dict( 63 | LocationConstraint=region_name 64 | ) 65 | ) 66 | print "Created S3 bucket {}".format(bucket_name) 67 | elif len(buckets) == 1: 68 | bucket_name = buckets[0] 69 | print "Use existing S3 bucket {}".format(bucket_name) 70 | else: 71 | msg = "There must be only 1 bucket with the prefix {}".format(prefix) 72 | msg += "\nThere are more instead: {}".format(buckets) 73 | raise Exception(msg) 74 | 75 | return bucket_name 76 | 77 | def delete_bucket(name=None, prefix=BUCKET_PREFIX, profile_name=None): 78 | """ 79 | This function delete the bucket specified by the parameter name. 80 | If the parameter is None the function deletes the buckets that match 81 | the prefix. 82 | 83 | :param str name: The name of the S3 bucket. 84 | :param str prefix: The prefix for the S3 bucket to delete. 85 | :param str profile_name: The profile to use from the AWS 86 | confign file. 87 | :rtype: NoneType 88 | """ 89 | session = boto3.Session(profile_name=profile_name) 90 | s3 = session.resource('s3') 91 | 92 | if name is None: 93 | regex = r'{}-[a-z0-9]+'.format(prefix) 94 | buckets = [bucket for bucket in s3.buckets.all() if re.match(regex, bucket.name)] 95 | 96 | for bucket in buckets: 97 | for key in bucket.objects.all(): 98 | key.delete() 99 | bucket.delete() 100 | else: 101 | bucket = s3.Bucket(name) 102 | for key in bucket.objects.all(): 103 | key.delete() 104 | bucket.delete() 105 | 106 | 107 | def copy_fmu(dir_path, bucket_name, profile_name=None): 108 | """ 109 | This function copies the FMUs in the directory specified by 110 | the parameter dir_path to the S3 bucket specified by the 111 | parameter bucket_name. 112 | 113 | :param str dir_path: The name of the directory that contains 114 | the FMU to copy, the config.json file, and the input folder 115 | with all the csv files. 116 | :param str bucket_name: The name of the S3 bucket. 117 | :param str prefix: The prefix for the S3 bucket to delete. 118 | :param str profile_name: The profile to use from the AWS 119 | confign file. 120 | :rtype: NoneType 121 | """ 122 | session = boto3.Session(profile_name=profile_name) 123 | s3 = session.resource('s3') 124 | 125 | if dir_path is None: 126 | msg = ("Please specify a path for the directory containing the" 127 | " directory that contains the FMU to be copied") 128 | raise ValueError(msg) 129 | elif not os.path.exists(dir_path): 130 | msg = "The directory {} does not exists".format(dir_path) 131 | raise ValueError(msg) 132 | elif not os.path.isdir(dir_path): 133 | msg = "The path {} is not a directory".format(dir_path) 134 | raise ValueError(msg) 135 | 136 | config_file_path = os.path.join(os.path.abspath(dir_path), "config.json") 137 | if not os.path.exists(config_file_path): 138 | msg = "The folder {} does not contain the 'config.json' file".format( 139 | dir_path 140 | ) 141 | raise ValueError(msg) 142 | 143 | with open(config_file_path, "r") as c_file: 144 | config = json.loads(c_file.read()) 145 | 146 | try: 147 | s3_sub_folder = config["s3"]["folder"] 148 | except KeyError: 149 | msg = "The 'config.json' file does not contain the s3 folder name" 150 | raise ValueError(msg) 151 | 152 | if not re.match(r"^[\w|\d]+", s3_sub_folder): 153 | msg = "The s3 folder name '{}' is not valid (only letters, underscores and numbers)." 154 | raise ValueError(msg) 155 | 156 | fmus = [f for f in os.listdir(dir_path) if f.endswith("fmu")] 157 | 158 | for fmu in fmus: 159 | fmu_path = os.path.join(os.path.abspath(dir_path), fmu) 160 | bucket_file = "{}/{}".format(s3_sub_folder, fmu) 161 | print "Copy {}... to s3://{}/{}".format(fmu, bucket_name, bucket_file) 162 | s3.Object(bucket_name, bucket_file).put(Body=open(fmu_path, 'rb')) 163 | print "Done" 164 | 165 | input_files_folder = os.path.join(os.path.abspath(dir_path), "inputs") 166 | if not os.path.exists(input_files_folder): 167 | print "No input files, skip" 168 | return 169 | elif not os.path.isdir(input_files_folder): 170 | msg = "The input files folder path {} is not a directory".format(input_files_folder) 171 | raise ValueError(msg) 172 | 173 | input_csv_files = [f for f in os.listdir(input_files_folder) if f.endswith("csv")] 174 | for csv_file in input_csv_files: 175 | csv_file_path = os.path.join(os.path.abspath(input_files_folder), csv_file) 176 | bucket_file = "{}/inputs/{}".format(s3_sub_folder, csv_file) 177 | print "Copy {}... to s3://{}/{}".format(csv_file, bucket_name, bucket_file) 178 | s3.Object(bucket_name, bucket_file).put(Body=open(csv_file_path, 'rb')) 179 | print "Done" 180 | 181 | 182 | if __name__ == "__main__": 183 | 184 | description = "Command line utility to create or delete S3 buckets for FMUs" 185 | parser = argparse.ArgumentParser( 186 | prog="setup_s3", 187 | description=description) 188 | 189 | parser.add_argument( 190 | '-c', '--create', const=True, default=False, 191 | nargs='?', help="Create a S3 bucket for storing FMUs") 192 | 193 | parser.add_argument( 194 | '-n', '--get_name', const=True, default=False, 195 | nargs='?', help="Get the name of the S3 buckets that stores the FMUs") 196 | 197 | parser.add_argument( 198 | '-d', '--delete', const=True, default=False, 199 | nargs='?', help="Delete the S3 bucket that stores the FMUs") 200 | 201 | parser.add_argument( 202 | '-cp', '--copy', const=None, default=None, 203 | type=str, nargs='?', 204 | help="The name of folder containing the FMU to be copied" 205 | ) 206 | 207 | parser.add_argument( 208 | '-p', '--profile', const=None, default=None, 209 | type=str, nargs='?', 210 | help="The name of the AWS IAM profile to use (see ~/.aws/credentials)" 211 | ) 212 | 213 | parser.add_argument( 214 | '-r', '--region', default=DEFAULT_AWS_REGION, 215 | type=str, nargs='?', 216 | help="The name of the AWS region (by default us-west-2)" 217 | ) 218 | 219 | args = parser.parse_args() 220 | 221 | if args.get_name: 222 | buckets = get_bucket_names( 223 | BUCKET_PREFIX, 224 | profile_name=args.profile 225 | ) 226 | if len(buckets) == 0: 227 | sys.exit("No buckets") 228 | elif len(buckets) == 1: 229 | sys.stdout.write(buckets[0]) 230 | else: 231 | sys.exit("More than one S3 bucket, please remove one") 232 | 233 | elif args.create: 234 | create_bucket( 235 | BUCKET_PREFIX, 236 | profile_name=args.profile, 237 | region_name=args.region 238 | ) 239 | 240 | elif args.delete: 241 | delete_bucket( 242 | prefix=BUCKET_PREFIX, 243 | profile_name=args.profile 244 | ) 245 | 246 | elif args.copy: 247 | bucket_name = create_bucket( 248 | BUCKET_PREFIX, 249 | profile_name=args.profile 250 | ) 251 | copy_fmu(args.copy, bucket_name, profile_name=args.profile) 252 | 253 | else: 254 | parser.print_help() 255 | 256 | -------------------------------------------------------------------------------- /templates/swagger_api_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "{{function_name}}" 5 | }, 6 | "basePath": "/prod", 7 | "schemes": [ 8 | "https" 9 | ], 10 | "paths": { 11 | "/{{function_name}}": { 12 | "x-amazon-apigateway-any-method": { 13 | "responses": { 14 | "200": { 15 | "description": "200 response" 16 | } 17 | }, 18 | "x-amazon-apigateway-integration": { 19 | "responses": { 20 | ".*": { 21 | "statusCode": "200" 22 | } 23 | }, 24 | "uri": "{{function_uri}}", 25 | "passthroughBehavior": "when_no_match", 26 | "httpMethod": "POST", 27 | "type": "aws_proxy" 28 | } 29 | } 30 | } 31 | } 32 | } --------------------------------------------------------------------------------