mirror of
https://github.com/ANL-CEEESA/RELOG.git
synced 2025-12-05 23:38:52 -06:00
Create solver page; add Dockerfile
This commit is contained in:
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
build
|
||||||
|
jobs
|
||||||
|
relog-web/node_modules
|
||||||
|
relog-web/build
|
||||||
27
Dockerfile
Normal file
27
Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
FROM julia:1.7-buster
|
||||||
|
|
||||||
|
# Install Node.js & zip
|
||||||
|
RUN apt-get update -yq && \
|
||||||
|
apt-get -yq install curl gnupg ca-certificates && \
|
||||||
|
curl -L https://deb.nodesource.com/setup_18.x | bash && \
|
||||||
|
apt-get update -yq && \
|
||||||
|
apt-get install -yq nodejs zip
|
||||||
|
|
||||||
|
# Install Julia dependencies
|
||||||
|
ADD Project.toml /app/
|
||||||
|
ADD src/RELOG.jl /app/src/
|
||||||
|
RUN julia --project=/app -e 'using Pkg; Pkg.update()'
|
||||||
|
|
||||||
|
# Install JS dependencies
|
||||||
|
ADD relog-web/package*.json /app/relog-web/
|
||||||
|
RUN cd /app/relog-web && npm install
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
ADD . /app
|
||||||
|
RUN julia --project=/app -e 'using Pkg; Pkg.precompile()'
|
||||||
|
|
||||||
|
# Build JS app
|
||||||
|
RUN cd /app/relog-web && npm run build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
CMD julia --project=/app -e 'import RELOG; RELOG.web("0.0.0.0")'
|
||||||
4
Makefile
4
Makefile
@@ -17,6 +17,9 @@ clean:
|
|||||||
docs:
|
docs:
|
||||||
mkdocs build -d ../docs/$(VERSION)/
|
mkdocs build -d ../docs/$(VERSION)/
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
docker build --tag relog:0.6 .
|
||||||
|
|
||||||
format:
|
format:
|
||||||
julia -e 'using JuliaFormatter; format(["src", "test"], verbose=true);'
|
julia -e 'using JuliaFormatter; format(["src", "test"], verbose=true);'
|
||||||
|
|
||||||
@@ -26,3 +29,4 @@ test-watch:
|
|||||||
bash -c "while true; do make test --quiet; sleep 1; done"
|
bash -c "while true; do make test --quiet; sleep 1; done"
|
||||||
|
|
||||||
.PHONY: docs test
|
.PHONY: docs test
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
|
|||||||
Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d"
|
Clp = "e2554f3b-3117-50c0-817c-e040a3ddf72d"
|
||||||
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
|
||||||
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
|
||||||
|
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
|
||||||
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
|
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
|
||||||
GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63"
|
GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63"
|
||||||
Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d"
|
Geodesy = "0ef565a4-170c-5f04-8de2-149903a85f3d"
|
||||||
|
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
|
||||||
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
|
||||||
JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
|
JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
|
||||||
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
|
||||||
@@ -20,8 +22,10 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
|
|||||||
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
|
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
|
||||||
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
||||||
PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
|
PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
|
||||||
|
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
|
||||||
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
|
||||||
ProgressBars = "49802e3a-d2f1-5c88-81d8-b72133a6f568"
|
ProgressBars = "49802e3a-d2f1-5c88-81d8-b72133a6f568"
|
||||||
|
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||||
Shapefile = "8e980c4a-a4fe-5da2-b3a7-4b4b0353a2f4"
|
Shapefile = "8e980c4a-a4fe-5da2-b3a7-4b4b0353a2f4"
|
||||||
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
|
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
|
||||||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||||
@@ -36,6 +40,7 @@ DataFrames = "0.21"
|
|||||||
DataStructures = "0.17"
|
DataStructures = "0.17"
|
||||||
GZip = "0.5"
|
GZip = "0.5"
|
||||||
Geodesy = "0.5"
|
Geodesy = "0.5"
|
||||||
|
HTTP = "0.9"
|
||||||
JSON = "0.21"
|
JSON = "0.21"
|
||||||
JSONSchema = "0.3"
|
JSONSchema = "0.3"
|
||||||
JuMP = "0.21"
|
JuMP = "0.21"
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -13,23 +13,22 @@
|
|||||||
|
|
||||||
**RELOG** is a supply chain optimization package focusing on reverse logistics and reverse manufacturing. For example, the package can be used to determine where to build recycling plants, what sizes should they have and which customers should be served by which plants. The package supports customized reverse logistics pipelines, with multiple types of plants, multiple types of product and multiple time periods.
|
**RELOG** is a supply chain optimization package focusing on reverse logistics and reverse manufacturing. For example, the package can be used to determine where to build recycling plants, what sizes should they have and which customers should be served by which plants. The package supports customized reverse logistics pipelines, with multiple types of plants, multiple types of product and multiple time periods.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<img src="https://anl-ceeesa.github.io/RELOG/0.5/images/ex_transportation.png" width="1000px"/>
|
<img src="https://anl-ceeesa.github.io/RELOG/0.5/images/ex_transportation.png" width="1000px"/>
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
* [Usage](https://anl-ceeesa.github.io/RELOG/0.5/usage)
|
- [Usage](https://anl-ceeesa.github.io/RELOG/0.5/usage)
|
||||||
* [Input and Output Data Formats](https://anl-ceeesa.github.io/RELOG/0.5/format)
|
- [Input and Output Data Formats](https://anl-ceeesa.github.io/RELOG/0.5/format)
|
||||||
* [Simplified Solution Reports](https://anl-ceeesa.github.io/RELOG/0.5/reports)
|
- [Simplified Solution Reports](https://anl-ceeesa.github.io/RELOG/0.5/reports)
|
||||||
* [Optimization Model](https://anl-ceeesa.github.io/RELOG/0.5/model)
|
- [Optimization Model](https://anl-ceeesa.github.io/RELOG/0.5/model)
|
||||||
|
|
||||||
### Authors
|
### Authors
|
||||||
|
|
||||||
* **Alinson S. Xavier** <<axavier@anl.gov>>
|
- **Alinson S. Xavier** <<axavier@anl.gov>>
|
||||||
* **Nwike Iloeje** <<ciloeje@anl.gov>>
|
- **Nwike Iloeje** <<ciloeje@anl.gov>>
|
||||||
* **John Atkins**
|
- **John Atkins**
|
||||||
* **Kyle Sun**
|
- **Kyle Sun**
|
||||||
|
- **Audrey Gallier**
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
|
|||||||
214
relog-web/package-lock.json
generated
214
relog-web/package-lock.json
generated
@@ -18,6 +18,7 @@
|
|||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-flow-renderer": "^9.7.4",
|
"react-flow-renderer": "^9.7.4",
|
||||||
|
"react-router-dom": "^5.3.3",
|
||||||
"react-scripts": "5.0.0",
|
"react-scripts": "5.0.0",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
}
|
}
|
||||||
@@ -8495,6 +8496,19 @@
|
|||||||
"he": "bin/he"
|
"he": "bin/he"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/history": {
|
||||||
|
"version": "4.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||||
|
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.1.2",
|
||||||
|
"loose-envify": "^1.2.0",
|
||||||
|
"resolve-pathname": "^3.0.0",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0",
|
||||||
|
"value-equal": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hoist-non-react-statics": {
|
"node_modules/hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
@@ -11486,6 +11500,19 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mini-create-react-context": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.1",
|
||||||
|
"tiny-warning": "^1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prop-types": "^15.0.0",
|
||||||
|
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mini-css-extract-plugin": {
|
"node_modules/mini-css-extract-plugin": {
|
||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz",
|
||||||
@@ -13769,6 +13796,61 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.13",
|
||||||
|
"history": "^4.9.0",
|
||||||
|
"hoist-non-react-statics": "^3.1.0",
|
||||||
|
"loose-envify": "^1.3.1",
|
||||||
|
"mini-create-react-context": "^0.4.0",
|
||||||
|
"path-to-regexp": "^1.7.0",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-is": "^16.6.0",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.13",
|
||||||
|
"history": "^4.9.0",
|
||||||
|
"loose-envify": "^1.3.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-router": "5.3.3",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router/node_modules/isarray": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
|
||||||
|
},
|
||||||
|
"node_modules/react-router/node_modules/path-to-regexp": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
|
||||||
|
"dependencies": {
|
||||||
|
"isarray": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router/node_modules/react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
|
},
|
||||||
"node_modules/react-scripts": {
|
"node_modules/react-scripts": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
|
||||||
@@ -14083,6 +14165,11 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/resolve-pathname": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
|
||||||
|
},
|
||||||
"node_modules/resolve-url-loader": {
|
"node_modules/resolve-url-loader": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz",
|
||||||
@@ -15365,6 +15452,16 @@
|
|||||||
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
||||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-invariant": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
|
||||||
|
},
|
||||||
|
"node_modules/tiny-warning": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||||
|
},
|
||||||
"node_modules/tmpl": {
|
"node_modules/tmpl": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||||
@@ -15538,6 +15635,19 @@
|
|||||||
"is-typedarray": "^1.0.0"
|
"is-typedarray": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "4.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
|
||||||
|
"integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==",
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unbox-primitive": {
|
"node_modules/unbox-primitive": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
|
||||||
@@ -15703,6 +15813,11 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/value-equal": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
|
||||||
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
@@ -22751,6 +22866,19 @@
|
|||||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
|
||||||
},
|
},
|
||||||
|
"history": {
|
||||||
|
"version": "4.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||||
|
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.1.2",
|
||||||
|
"loose-envify": "^1.2.0",
|
||||||
|
"resolve-pathname": "^3.0.0",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0",
|
||||||
|
"value-equal": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"hoist-non-react-statics": {
|
"hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
@@ -24911,6 +25039,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
|
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="
|
||||||
},
|
},
|
||||||
|
"mini-create-react-context": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.1",
|
||||||
|
"tiny-warning": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mini-css-extract-plugin": {
|
"mini-css-extract-plugin": {
|
||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz",
|
||||||
@@ -26433,6 +26570,57 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
||||||
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
|
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
|
||||||
},
|
},
|
||||||
|
"react-router": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.13",
|
||||||
|
"history": "^4.9.0",
|
||||||
|
"hoist-non-react-statics": "^3.1.0",
|
||||||
|
"loose-envify": "^1.3.1",
|
||||||
|
"mini-create-react-context": "^0.4.0",
|
||||||
|
"path-to-regexp": "^1.7.0",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-is": "^16.6.0",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"isarray": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
|
||||||
|
},
|
||||||
|
"path-to-regexp": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
|
||||||
|
"requires": {
|
||||||
|
"isarray": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-router-dom": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.13",
|
||||||
|
"history": "^4.9.0",
|
||||||
|
"loose-envify": "^1.3.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-router": "5.3.3",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-scripts": {
|
"react-scripts": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.0.tgz",
|
||||||
@@ -26674,6 +26862,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
|
||||||
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="
|
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="
|
||||||
},
|
},
|
||||||
|
"resolve-pathname": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
|
||||||
|
},
|
||||||
"resolve-url-loader": {
|
"resolve-url-loader": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz",
|
||||||
@@ -27619,6 +27812,16 @@
|
|||||||
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
||||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
||||||
},
|
},
|
||||||
|
"tiny-invariant": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg=="
|
||||||
|
},
|
||||||
|
"tiny-warning": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||||
|
},
|
||||||
"tmpl": {
|
"tmpl": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||||
@@ -27753,6 +27956,12 @@
|
|||||||
"is-typedarray": "^1.0.0"
|
"is-typedarray": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "4.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
|
||||||
|
"integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"unbox-primitive": {
|
"unbox-primitive": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
|
||||||
@@ -27877,6 +28086,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"value-equal": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
|
||||||
|
},
|
||||||
"vary": {
|
"vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "relog-web",
|
"name": "relog-web",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "/RELOG/0.6/casebuilder",
|
"homepage": "/",
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"d3": "<rootDir>/node_modules/d3/dist/d3.min.js"
|
"d3": "<rootDir>/node_modules/d3/dist/d3.min.js"
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-flow-renderer": "^9.7.4",
|
"react-flow-renderer": "^9.7.4",
|
||||||
|
"react-router-dom": "^5.3.3",
|
||||||
"react-scripts": "5.0.0",
|
"react-scripts": "5.0.0",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import React, { useState, useRef, useEffect } from "react";
|
import { openDB } from "idb";
|
||||||
import { openDB, deleteDB, wrap, unwrap } from "idb";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import Button from "../common/Button";
|
||||||
import "./index.css";
|
import Footer from "../common/Footer";
|
||||||
import PipelineBlock from "./PipelineBlock";
|
import Header from "../common/Header";
|
||||||
import ParametersBlock from "./ParametersBlock";
|
import "../index.css";
|
||||||
import ProductBlock from "./ProductBlock";
|
|
||||||
import PlantBlock from "./PlantBlock";
|
|
||||||
import Button from "./Button";
|
|
||||||
import Header from "./Header";
|
|
||||||
import Footer from "./Footer";
|
|
||||||
import { defaultData, defaultPlant, defaultProduct } from "./defaults";
|
|
||||||
import { randomPosition } from "./PipelineBlock";
|
|
||||||
import { exportData, importData } from "./export";
|
|
||||||
import { generateFile } from "./csv";
|
import { generateFile } from "./csv";
|
||||||
|
import { defaultData, defaultPlant, defaultProduct } from "./defaults";
|
||||||
|
import { exportData, importData } from "./export";
|
||||||
|
import ParametersBlock from "./ParametersBlock";
|
||||||
|
import PipelineBlock, { randomPosition } from "./PipelineBlock";
|
||||||
|
import PlantBlock from "./PlantBlock";
|
||||||
|
import ProductBlock from "./ProductBlock";
|
||||||
import { validate } from "./validate";
|
import { validate } from "./validate";
|
||||||
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
const setDefaults = (actualDict, defaultDict) => {
|
const setDefaults = (actualDict, defaultDict) => {
|
||||||
for (const [key, defaultValue] of Object.entries(defaultDict)) {
|
for (const [key, defaultValue] of Object.entries(defaultDict)) {
|
||||||
@@ -83,6 +82,8 @@ const InputPage = () => {
|
|||||||
if (data) setData(data);
|
if (data) setData(data);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
const promptName = (prevData) => {
|
const promptName = (prevData) => {
|
||||||
const name = prompt("Name");
|
const name = prompt("Name");
|
||||||
if (!name || name.length === 0) return;
|
if (!name || name.length === 0) return;
|
||||||
@@ -301,6 +302,24 @@ const InputPage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
const exported = exportData(data);
|
||||||
|
const valid = validate(exported);
|
||||||
|
if (valid) {
|
||||||
|
fetch("/submit", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(exported),
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
console.log(data);
|
||||||
|
history.push(`/solver/${data.job_id}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let plantComps = [];
|
let plantComps = [];
|
||||||
for (const [plantName, plant] of Object.entries(data.plants)) {
|
for (const [plantName, plant] of Object.entries(data.plants)) {
|
||||||
plantComps.push(
|
plantComps.push(
|
||||||
@@ -341,6 +360,7 @@ const InputPage = () => {
|
|||||||
<Button label="Clear" onClick={onClear} />
|
<Button label="Clear" onClick={onClear} />
|
||||||
<Button label="Load" onClick={(e) => fileElem.current.click()} />
|
<Button label="Load" onClick={(e) => fileElem.current.click()} />
|
||||||
<Button label="Save" onClick={onSave} />
|
<Button label="Save" onClick={onSave} />
|
||||||
|
<Button label="Submit" onClick={onSubmit} />
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
ref={fileElem}
|
ref={fileElem}
|
||||||
@@ -349,6 +369,7 @@ const InputPage = () => {
|
|||||||
onChange={onFileSelected}
|
onChange={onFileSelected}
|
||||||
/>
|
/>
|
||||||
</Header>
|
</Header>
|
||||||
|
<div id="contentBackground">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<PipelineBlock
|
<PipelineBlock
|
||||||
onAddPlant={onAddPlant}
|
onAddPlant={onAddPlant}
|
||||||
@@ -371,6 +392,7 @@ const InputPage = () => {
|
|||||||
{productComps}
|
{productComps}
|
||||||
{plantComps}
|
{plantComps}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div id="messageTray">{messageComps}</div>
|
<div id="messageTray">{messageComps}</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import Section from "./Section";
|
import Section from "../common/Section";
|
||||||
import Card from "./Card";
|
import Card from "../common/Card";
|
||||||
import Form from "./Form";
|
import Form from "../common/Form";
|
||||||
import TextInputRow from "./TextInputRow";
|
import TextInputRow from "../common/TextInputRow";
|
||||||
|
|
||||||
const ParametersBlock = (props) => {
|
const ParametersBlock = (props) => {
|
||||||
const onChangeField = (field, val) => {
|
const onChangeField = (field, val) => {
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import ReactFlow, { Background, isNode, Controls } from "react-flow-renderer";
|
import ReactFlow, { Background, isNode, Controls } from "react-flow-renderer";
|
||||||
import Section from "./Section";
|
import Section from "../common/Section";
|
||||||
import Card from "./Card";
|
import Card from "../common/Card";
|
||||||
import Button from "./Button";
|
import Button from "../common/Button";
|
||||||
import styles from "./PipelineBlock.module.css";
|
import styles from "./PipelineBlock.module.css";
|
||||||
import dagre from "dagre";
|
import dagre from "dagre";
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import Section from "./Section";
|
import Section from "../common/Section";
|
||||||
import Card from "./Card";
|
import Card from "../common/Card";
|
||||||
import Form from "./Form";
|
import Form from "../common/Form";
|
||||||
import TextInputRow from "./TextInputRow";
|
import TextInputRow from "../common/TextInputRow";
|
||||||
import FileInputRow from "./FileInputRow";
|
import FileInputRow from "../common/FileInputRow";
|
||||||
import DictInputRow from "./DictInputRow";
|
import DictInputRow from "../common/DictInputRow";
|
||||||
import { csvFormat, csvParse, generateFile } from "./csv";
|
import { csvFormat, csvParse, generateFile } from "./csv";
|
||||||
|
|
||||||
const PlantBlock = (props) => {
|
const PlantBlock = (props) => {
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import Section from "./Section";
|
import Section from "../common/Section";
|
||||||
import Card from "./Card";
|
import Card from "../common/Card";
|
||||||
import Form from "./Form";
|
import Form from "../common/Form";
|
||||||
import TextInputRow from "./TextInputRow";
|
import TextInputRow from "../common/TextInputRow";
|
||||||
import FileInputRow from "./FileInputRow";
|
import FileInputRow from "../common/FileInputRow";
|
||||||
import DictInputRow from "./DictInputRow";
|
import DictInputRow from "../common/DictInputRow";
|
||||||
import { csvParse, extractNumericColumns, generateFile } from "./csv";
|
import { csvParse, extractNumericColumns, generateFile } from "./csv";
|
||||||
import { csvFormat } from "d3";
|
import { csvFormat } from "d3";
|
||||||
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
.Footer {
|
.Footer {
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
padding: 12px;
|
||||||
padding: 24px;
|
|
||||||
margin-top: 24px;
|
|
||||||
color: rgba(255, 255, 255, 0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
--box-border: 1px solid rgba(0, 0, 0, 0.2);
|
--box-border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
--box-shadow: 0px 2px 4px -3px rgba(0, 0, 0, 0.2);
|
--box-shadow: 0px 2px 4px -3px rgba(0, 0, 0, 0.2);
|
||||||
--border-radius: 4px;
|
--border-radius: 4px;
|
||||||
|
--primary: #0d6efd;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
@@ -14,15 +15,19 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #f6f6f6;
|
background-color: #333;
|
||||||
color: rgba(0, 0, 0, 0.95);
|
color: rgba(0, 0, 0, 0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#contentBackground {
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
max-width: var(--site-width);
|
max-width: var(--site-width);
|
||||||
min-width: 900px;
|
min-width: 900px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 6px;
|
padding: 1px 6px 32px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-flow__node.selected {
|
.react-flow__node.selected {
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import InputPage from "./InputPage";
|
import InputPage from "./casebuilder/InputPage";
|
||||||
|
import SolverPage from "./solver/SolverPage";
|
||||||
|
import { Route, BrowserRouter, Switch, Redirect } from "react-router-dom";
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
|
<BrowserRouter>
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
<Switch>
|
||||||
|
<Route path="/casebuilder">
|
||||||
<InputPage />
|
<InputPage />
|
||||||
</React.StrictMode>,
|
</Route>
|
||||||
|
<Route path="/solver/:job_id">
|
||||||
|
<SolverPage />
|
||||||
|
</Route>
|
||||||
|
<Route path="/">
|
||||||
|
<Redirect to="/casebuilder" />
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</React.StrictMode>
|
||||||
|
</BrowserRouter>,
|
||||||
document.getElementById("root")
|
document.getElementById("root")
|
||||||
);
|
);
|
||||||
|
|||||||
47
relog-web/src/solver/FilesBlock.js
Normal file
47
relog-web/src/solver/FilesBlock.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import Section from "../common/Section";
|
||||||
|
import Card from "../common/Card";
|
||||||
|
import styles from "./FilesBlock.module.css";
|
||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
|
const FilesBlock = (props) => {
|
||||||
|
const [filesFound, setFilesFound] = useState(false);
|
||||||
|
|
||||||
|
const fetchFiles = async () => {
|
||||||
|
const response = await fetch(`/jobs/${props.job}/output.json`);
|
||||||
|
if (response.ok) {
|
||||||
|
setFilesFound(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch files periodically from the server
|
||||||
|
useEffect(() => {
|
||||||
|
fetchFiles();
|
||||||
|
console.log(filesFound);
|
||||||
|
if (!filesFound) {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
fetchFiles();
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}
|
||||||
|
}, [filesFound]);
|
||||||
|
|
||||||
|
let content = <div className={styles.nodata}>No files available</div>;
|
||||||
|
if (filesFound) {
|
||||||
|
content = (
|
||||||
|
<div className={styles.files}>
|
||||||
|
<a href={`/jobs/${props.job}/output.zip`}>output.zip</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Section title="Output Files" />
|
||||||
|
<Card>{content}</Card>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilesBlock;
|
||||||
19
relog-web/src/solver/FilesBlock.module.css
Normal file
19
relog-web/src/solver/FilesBlock.module.css
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
.files a {
|
||||||
|
display: block;
|
||||||
|
padding: 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.files a:hover {
|
||||||
|
background-color: var(--primary);
|
||||||
|
color: white;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodata {
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px 0;
|
||||||
|
color: #888;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
46
relog-web/src/solver/LogBlock.js
Normal file
46
relog-web/src/solver/LogBlock.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import Section from "../common/Section";
|
||||||
|
import Card from "../common/Card";
|
||||||
|
import styles from "./LogBlock.module.css";
|
||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
|
const LogBlock = (props) => {
|
||||||
|
const [log, setLog] = useState();
|
||||||
|
const preRef = useRef(null);
|
||||||
|
|
||||||
|
const fetchLog = async () => {
|
||||||
|
const response = await fetch(`/jobs/${props.job}/solve.log`);
|
||||||
|
const data = await response.text();
|
||||||
|
if (log !== data) {
|
||||||
|
setLog(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch log periodically from the server
|
||||||
|
useEffect(() => {
|
||||||
|
fetchLog();
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
fetchLog();
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Scroll to bottom whenever the log is updated
|
||||||
|
useEffect(() => {
|
||||||
|
preRef.current.scrollTop = preRef.current.scrollHeight;
|
||||||
|
}, [log]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Section title="Optimization Log" />
|
||||||
|
<Card>
|
||||||
|
<pre ref={preRef} className={styles.log}>
|
||||||
|
{log}
|
||||||
|
</pre>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LogBlock;
|
||||||
8
relog-web/src/solver/LogBlock.module.css
Normal file
8
relog-web/src/solver/LogBlock.module.css
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.log {
|
||||||
|
max-height: 500px;
|
||||||
|
min-height: 500px;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow: auto;
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
||||||
26
relog-web/src/solver/SolverPage.js
Normal file
26
relog-web/src/solver/SolverPage.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import Footer from "../common/Footer";
|
||||||
|
import Header from "../common/Header";
|
||||||
|
import LogBlock from "./LogBlock";
|
||||||
|
import FilesBlock from "./FilesBlock";
|
||||||
|
|
||||||
|
const SolverPage = () => {
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header title="Solver"></Header>
|
||||||
|
<div id="contentBackground">
|
||||||
|
{" "}
|
||||||
|
<div id="content">
|
||||||
|
<LogBlock job={params.job_id} />
|
||||||
|
<FilesBlock job={params.job_id} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SolverPage;
|
||||||
@@ -25,4 +25,5 @@ include("reports/products.jl")
|
|||||||
include("reports/tr_emissions.jl")
|
include("reports/tr_emissions.jl")
|
||||||
include("reports/tr.jl")
|
include("reports/tr.jl")
|
||||||
include("reports/write.jl")
|
include("reports/write.jl")
|
||||||
|
include("web/web.jl")
|
||||||
end
|
end
|
||||||
|
|||||||
126
src/web/run.jl
Normal file
126
src/web/run.jl
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
println("Initializing...")
|
||||||
|
|
||||||
|
using Logging
|
||||||
|
using Cbc
|
||||||
|
using JSON
|
||||||
|
using JuMP
|
||||||
|
using RELOG
|
||||||
|
|
||||||
|
function solve(root, filename)
|
||||||
|
ref_file = "$root/$filename"
|
||||||
|
optimizer = optimizer_with_attributes(
|
||||||
|
Cbc.Optimizer,
|
||||||
|
"seconds" => 900,
|
||||||
|
)
|
||||||
|
ref_solution, ref_model = RELOG.solve(
|
||||||
|
ref_file,
|
||||||
|
optimizer=optimizer,
|
||||||
|
return_model=true,
|
||||||
|
marginal_costs=false,
|
||||||
|
)
|
||||||
|
Libc.flush_cstdio()
|
||||||
|
flush(stdout)
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
if length(ref_solution) == 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
RELOG.write_products_report(
|
||||||
|
ref_solution,
|
||||||
|
replace(ref_file, ".json" => "_products.csv"),
|
||||||
|
)
|
||||||
|
RELOG.write_plants_report(
|
||||||
|
ref_solution,
|
||||||
|
replace(ref_file, ".json" => "_plants.csv"),
|
||||||
|
)
|
||||||
|
RELOG.write_plant_outputs_report(
|
||||||
|
ref_solution,
|
||||||
|
replace(ref_file, ".json" => "_plant_outputs.csv"),
|
||||||
|
)
|
||||||
|
RELOG.write_plant_emissions_report(
|
||||||
|
ref_solution,
|
||||||
|
replace(ref_file, ".json" => "_plant_emissions.csv"),
|
||||||
|
)
|
||||||
|
RELOG.write_transportation_report(
|
||||||
|
ref_solution,
|
||||||
|
replace(ref_file, ".json" => "_tr.csv"),
|
||||||
|
)
|
||||||
|
RELOG.write_transportation_emissions_report(
|
||||||
|
ref_solution,
|
||||||
|
replace(ref_file, ".json" => "_tr_emissions.csv"),
|
||||||
|
)
|
||||||
|
|
||||||
|
isdir("$root/scenarios") || return
|
||||||
|
for filename in readdir("$root/scenarios")
|
||||||
|
scenario = "$root/scenarios/$filename"
|
||||||
|
endswith(filename, ".json") || continue
|
||||||
|
|
||||||
|
sc_solution = RELOG.resolve(
|
||||||
|
ref_model,
|
||||||
|
scenario,
|
||||||
|
optimizer=optimizer,
|
||||||
|
)
|
||||||
|
if length(sc_solution) == 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
RELOG.write_plants_report(
|
||||||
|
sc_solution,
|
||||||
|
replace(scenario, ".json" => "_plants.csv"),
|
||||||
|
)
|
||||||
|
RELOG.write_products_report(
|
||||||
|
sc_solution,
|
||||||
|
replace(scenario, ".json" => "_products.csv"),
|
||||||
|
)
|
||||||
|
RELOG.write_plant_outputs_report(
|
||||||
|
sc_solution,
|
||||||
|
replace(scenario, ".json" => "_plant_outputs.csv"),
|
||||||
|
)
|
||||||
|
RELOG.write_plant_emissions_report(
|
||||||
|
sc_solution,
|
||||||
|
replace(scenario, ".json" => "_plant_emissions.csv"),
|
||||||
|
)
|
||||||
|
RELOG.write_transportation_report(
|
||||||
|
sc_solution,
|
||||||
|
replace(scenario, ".json" => "_tr.csv"),
|
||||||
|
)
|
||||||
|
RELOG.write_transportation_emissions_report(
|
||||||
|
sc_solution,
|
||||||
|
replace(scenario, ".json" => "_tr_emissions.csv"),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function solve_recursive(path)
|
||||||
|
# Solve instances
|
||||||
|
for (root, dirs, files) in walkdir(path)
|
||||||
|
if occursin(r"scenarios"i, root)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
for filename in files
|
||||||
|
endswith(filename, ".json") || continue
|
||||||
|
solve(root, filename)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Collect results
|
||||||
|
results = []
|
||||||
|
for (root, dirs, files) in walkdir(path)
|
||||||
|
for filename in files
|
||||||
|
endswith(filename, "_plants.csv") || continue
|
||||||
|
push!(
|
||||||
|
results,
|
||||||
|
joinpath(
|
||||||
|
replace(root, path => ""),
|
||||||
|
replace(filename, "_plants.csv" => ""),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
open("$path/output.json", "w") do file
|
||||||
|
JSON.print(file, results)
|
||||||
|
end
|
||||||
|
|
||||||
|
run(`zip -r $path/output.zip $path`)
|
||||||
|
end
|
||||||
|
|
||||||
|
solve_recursive(ARGS[1])
|
||||||
66
src/web/web.jl
Normal file
66
src/web/web.jl
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import HTTP
|
||||||
|
import JSON
|
||||||
|
using Random
|
||||||
|
|
||||||
|
const ROUTER = HTTP.Router()
|
||||||
|
const PROJECT_DIR = joinpath(dirname(@__FILE__), "..", "..")
|
||||||
|
const STATIC_DIR = joinpath(PROJECT_DIR, "relog-web", "build", "static")
|
||||||
|
const JOBS_DIR = joinpath(PROJECT_DIR, "jobs")
|
||||||
|
|
||||||
|
function serve_file(req::HTTP.Request, filename)
|
||||||
|
if isfile(filename)
|
||||||
|
open(filename) do file
|
||||||
|
return HTTP.Response(200, read(file))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return HTTP.Response(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function submit(req::HTTP.Request)
|
||||||
|
# Generate random job id
|
||||||
|
job_id = lowercase(randstring(12))
|
||||||
|
|
||||||
|
# Create job folder
|
||||||
|
job_path = joinpath(JOBS_DIR, job_id)
|
||||||
|
mkpath(job_path)
|
||||||
|
|
||||||
|
# Write JSON file
|
||||||
|
case = JSON.parse(String(req.body))
|
||||||
|
open(joinpath(job_path, "case.json"), "w") do file
|
||||||
|
JSON.print(file, case)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run job
|
||||||
|
run(`bash -c "(julia --project=$PROJECT_DIR $PROJECT_DIR/src/web/run.jl $job_path 2>&1 | tee $job_path/solve.log) >/dev/null 2>&1 &"`)
|
||||||
|
|
||||||
|
response = Dict(
|
||||||
|
"job_id" => job_id,
|
||||||
|
)
|
||||||
|
return HTTP.Response(200, body = JSON.json(response))
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_index(req::HTTP.Request)
|
||||||
|
return serve_file(req, joinpath(STATIC_DIR, "..", "index.html"))
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_static(req::HTTP.Request)
|
||||||
|
return serve_file(req, joinpath(STATIC_DIR, req.target[9:end]))
|
||||||
|
end
|
||||||
|
|
||||||
|
function get_jobs(req::HTTP.Request)
|
||||||
|
return serve_file(req, joinpath(JOBS_DIR, req.target[7:end]))
|
||||||
|
end
|
||||||
|
|
||||||
|
HTTP.@register(ROUTER, "GET", "/static", get_static)
|
||||||
|
HTTP.@register(ROUTER, "GET", "/jobs", get_jobs)
|
||||||
|
HTTP.@register(ROUTER, "POST", "/submit", submit)
|
||||||
|
HTTP.@register(ROUTER, "GET", "/", get_index)
|
||||||
|
|
||||||
|
function web(host = "127.0.0.1", port = 8080)
|
||||||
|
@info "Launching web interface: http://$(host):$(port)/"
|
||||||
|
Base.exit_on_sigint(false)
|
||||||
|
HTTP.serve(ROUTER, host, port)
|
||||||
|
Base.exit_on_sigint(true)
|
||||||
|
end
|
||||||
|
|
||||||
@@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
using RELOG
|
using RELOG
|
||||||
|
|
||||||
|
BASEDIR = dirname(@__FILE__)
|
||||||
|
|
||||||
@testset "Resolve" begin
|
@testset "Resolve" begin
|
||||||
# Shoud not crash
|
# Shoud not crash
|
||||||
filename = "$(pwd())/../instances/s1.json"
|
filename = joinpath(BASEDIR, "..", "..", "instances", "s1.json")
|
||||||
solution_old, model_old = RELOG.solve(filename, return_model = true)
|
solution_old, model_old = RELOG.solve(filename, return_model = true)
|
||||||
solution_new = RELOG.resolve(model_old, filename)
|
solution_new = RELOG.resolve(model_old, filename)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
|
|
||||||
using RELOG, JSON, GZip
|
using RELOG, JSON, GZip
|
||||||
|
|
||||||
|
BASEDIR = dirname(@__FILE__)
|
||||||
|
|
||||||
@testset "Reports" begin
|
@testset "Reports" begin
|
||||||
@testset "from solve" begin
|
@testset "from solve" begin
|
||||||
solution = RELOG.solve("$(pwd())/../instances/s1.json")
|
solution = RELOG.solve(joinpath(BASEDIR, "..", "instances", "s1.json"))
|
||||||
tmp_filename = tempname()
|
tmp_filename = tempname()
|
||||||
# The following should not crash
|
# The following should not crash
|
||||||
RELOG.write_plant_emissions_report(solution, tmp_filename)
|
RELOG.write_plant_emissions_report(solution, tmp_filename)
|
||||||
|
|||||||
Reference in New Issue
Block a user