Explorar el Código

assignment2 task4 finished

wangyu
wangyu hace 4 años
padre
commit
5a2225074b
Se han modificado 52 ficheros con 10182 adiciones y 3 borrados
  1. +70
    -3
      Assignment2.md
  2. BIN
      file/assignment2/.DS_Store
  3. +6
    -0
      file/assignment2/FoodTrucks/.gitignore
  4. +25
    -0
      file/assignment2/FoodTrucks/Dockerfile
  5. +30
    -0
      file/assignment2/FoodTrucks/aws-ecs/docker-compose.yml
  6. +23
    -0
      file/assignment2/FoodTrucks/docker-compose.yml
  7. +3
    -0
      file/assignment2/FoodTrucks/flask-app/.babelrc
  8. +122
    -0
      file/assignment2/FoodTrucks/flask-app/app.py
  9. +3243
    -0
      file/assignment2/FoodTrucks/flask-app/package-lock.json
  10. +26
    -0
      file/assignment2/FoodTrucks/flask-app/package.json
  11. +3
    -0
      file/assignment2/FoodTrucks/flask-app/requirements.txt
  12. +94
    -0
      file/assignment2/FoodTrucks/flask-app/static/build/main.js
  13. +1
    -0
      file/assignment2/FoodTrucks/flask-app/static/build/main.js.map
  14. BIN
      file/assignment2/FoodTrucks/flask-app/static/favicon.ico
  15. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-144x144.png
  16. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-192x192.png
  17. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-36x36.png
  18. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-48x48.png
  19. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-72x72.png
  20. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-96x96.png
  21. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-114x114.png
  22. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-120x120.png
  23. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-144x144.png
  24. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-152x152.png
  25. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-180x180.png
  26. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-57x57.png
  27. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-60x60.png
  28. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-72x72.png
  29. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-76x76.png
  30. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-precomposed.png
  31. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon.png
  32. +2
    -0
      file/assignment2/FoodTrucks/flask-app/static/icons/browserconfig.xml
  33. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/favicon-16x16.png
  34. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/favicon-32x32.png
  35. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/favicon-96x96.png
  36. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/favicon.ico
  37. +41
    -0
      file/assignment2/FoodTrucks/flask-app/static/icons/manifest.json
  38. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/ms-icon-144x144.png
  39. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/ms-icon-150x150.png
  40. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/ms-icon-310x310.png
  41. BIN
      file/assignment2/FoodTrucks/flask-app/static/icons/ms-icon-70x70.png
  42. +68
    -0
      file/assignment2/FoodTrucks/flask-app/static/src/app.js
  43. +36
    -0
      file/assignment2/FoodTrucks/flask-app/static/src/components/Intro.js
  44. +205
    -0
      file/assignment2/FoodTrucks/flask-app/static/src/components/Sidebar.js
  45. +70
    -0
      file/assignment2/FoodTrucks/flask-app/static/src/components/Vendor.js
  46. +202
    -0
      file/assignment2/FoodTrucks/flask-app/static/styles/main.css
  47. +50
    -0
      file/assignment2/FoodTrucks/flask-app/templates/index.html
  48. +19
    -0
      file/assignment2/FoodTrucks/flask-app/webpack.config.js
  49. +13
    -0
      file/assignment2/FoodTrucks/setup-aws-ecs.sh
  50. +13
    -0
      file/assignment2/FoodTrucks/setup-docker.sh
  51. +46
    -0
      file/assignment2/FoodTrucks/utils/generate_geojson.py
  52. +5771
    -0
      file/assignment2/FoodTrucks/utils/trucks.geojson

+ 70
- 3
Assignment2.md Ver fichero

@ -308,8 +308,75 @@ $ docker port static-site
#### 到现在为止,同学可能会疑惑,这种docker的镜像到底是如何创建的呢?这里就要使用到一个概念: `Dockerfile`
#### 1.从repo中下载
#### 1.从repo中下载[附件](/file/assignment2/FoodTrucks.zip),通过`scp`命令上传到云服务器并解压
[附件]: /file/assignment2/FoodTrucks.zip
```bash
$ scp FoodTrucks.zip root@113.31.112.166:~
root@113.31.112.166's password:
/etc/profile.d/lang.sh: line 19: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory
FoodTrucks.zip 0% 0 0.0KB/s --:-- ETAsFoodTrucks.zip 100% 1097KB 488.5KB/s 00:02
$ unzip FoodTrucks.zip
Archive: FoodTrucks.zip
creating: FoodTrucks/
inflating: __MACOSX/._FoodTrucks
inflating: FoodTrucks/.DS_Store
...
```
#### 2.查看Dockerfile
#### 这是一个`Flask App`,是一个用`python`写的网页应用,我们使用`cat Dockerfile`命令查看`Dockerfile的内容`,显示如下:
```dockerfile
# 基于ubuntu:18.04容器
FROM ubuntu:18.04
# 安装相关依赖
RUN apt-get -yqq update
RUN apt-get -yqq install python3-pip python3-dev curl gnupg
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash
RUN apt-get install -yq nodejs
# 将本地目录拷贝到container并且设置为工作目录
ADD flask-app /opt/flask-app
WORKDIR /opt/flask-app
# 安装node和python的相关依赖
RUN npm install
RUN npm run build
RUN pip3 install -r requirements.txt
# 将5000端口暴露出来
EXPOSE 5000
# 容器启动的执行命令
CMD [ "python3", "./app.py" ]
```
#### 可以看出`Dockerfile`的书写非常简单易懂,涉及到几个命令
| FROM | 指定容器来自的镜像 |
| :-----: | :----------------------------------------------------------: |
| RUN | 容器中运行shell命令 |
| ADD | 将本地文件传送到容器指定位置 |
| WORKDIR | 将容器中某个目录设置为工作目录(当前目录) |
| EXPOSE | 将容器中某个端口暴露出来(如上述flask工作在5000端口,将其暴露出来) |
| CMD | 容器启动自动执行的shell命令 |
#### 通过`dockerfile`的方式,我们就可以将各种各样的环境打包成镜像上传到`docker hub`上供他人使用
#### 3.打包镜像
#### 在当前目录(`Dockerfile`同级目录),执行如下命令,`docker`会自动将当前环境部署打包为一个`image`
```bash
$ docker build -t user-name/image-name
```
#### 这一步可能耗时较长,请耐心等待。。。
,通过`scp`命令上传到云服务器并解压
**************作业4:利用dockerfile将当前环境打包为 `英文姓名`/food` 的`image`,并通过`docker`命令将创建该镜像的`container`,将`5000`端口映射到本地的`8080`端口。将打包命令和浏览器页面截图,并插入实验报告中***************

BIN
file/assignment2/.DS_Store Ver fichero


+ 6
- 0
file/assignment2/FoodTrucks/.gitignore Ver fichero

@ -0,0 +1,6 @@
*.swp
node_modules
venv
.DS_Store
elasticsearch
*.pem

+ 25
- 0
file/assignment2/FoodTrucks/Dockerfile Ver fichero

@ -0,0 +1,25 @@
# start from base
FROM ubuntu:18.04
LABEL maintainer="Prakhar Srivastav <prakhar@prakhar.me>"
# install system-wide deps for python and node
RUN apt-get -yqq update
RUN apt-get -yqq install python3-pip python3-dev curl gnupg
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash
RUN apt-get install -yq nodejs
# copy our application code
ADD flask-app /opt/flask-app
WORKDIR /opt/flask-app
# fetch app specific deps
RUN npm install
RUN npm run build
RUN pip3 install -r requirements.txt
# expose port
EXPOSE 5000
# start app
CMD [ "python3", "./app.py" ]

+ 30
- 0
file/assignment2/FoodTrucks/aws-ecs/docker-compose.yml Ver fichero

@ -0,0 +1,30 @@
version: '2'
services:
es:
image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
cpu_shares: 100
mem_limit: 3621440000
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
logging:
driver: awslogs
options:
awslogs-group: foodtrucks
awslogs-region: us-east-1
awslogs-stream-prefix: es
web:
image: prakhar1989/foodtrucks-web
cpu_shares: 100
mem_limit: 262144000
ports:
- "80:5000"
links:
- es
logging:
driver: awslogs
options:
awslogs-group: foodtrucks
awslogs-region: us-east-1
awslogs-stream-prefix: web

+ 23
- 0
file/assignment2/FoodTrucks/docker-compose.yml Ver fichero

@ -0,0 +1,23 @@
version: "3"
services:
es:
image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
container_name: es
environment:
- discovery.type=single-node
ports:
- 9200:9200
volumes:
- esdata1:/usr/share/elasticsearch/data
web:
image: prakhar1989/foodtrucks-web
command: python3 app.py
depends_on:
- es
ports:
- 5000:5000
volumes:
- ./flask-app:/opt/flask-app
volumes:
esdata1:
driver: local

+ 3
- 0
file/assignment2/FoodTrucks/flask-app/.babelrc Ver fichero

@ -0,0 +1,3 @@
{
"presets": ["env"]
}

+ 122
- 0
file/assignment2/FoodTrucks/flask-app/app.py Ver fichero

@ -0,0 +1,122 @@
from elasticsearch import Elasticsearch, exceptions
import os
import time
from flask import Flask, jsonify, request, render_template
import sys
import requests
es = Elasticsearch(host='es')
app = Flask(__name__)
def load_data_in_es():
""" creates an index in elasticsearch """
url = "http://data.sfgov.org/resource/rqzj-sfat.json"
r = requests.get(url)
data = r.json()
print("Loading data in elasticsearch ...")
for id, truck in enumerate(data):
res = es.index(index="sfdata", doc_type="truck", id=id, body=truck)
print("Total trucks loaded: ", len(data))
def safe_check_index(index, retry=3):
""" connect to ES with retry """
if not retry:
print("Out of retries. Bailing out...")
sys.exit(1)
try:
status = es.indices.exists(index)
return status
except exceptions.ConnectionError as e:
print("Unable to connect to ES. Retrying in 5 secs...")
time.sleep(5)
safe_check_index(index, retry-1)
def format_fooditems(string):
items = [x.strip().lower() for x in string.split(":")]
return items[1:] if items[0].find("cold truck") > -1 else items
def check_and_load_index():
""" checks if index exits and loads the data accordingly """
if not safe_check_index('sfdata'):
print("Index not found...")
load_data_in_es()
###########
### APP ###
###########
@app.route('/')
def index():
return render_template('index.html')
@app.route('/debug')
def test_es():
resp = {}
try:
msg = es.cat.indices()
resp["msg"] = msg
resp["status"] = "success"
except:
resp["status"] = "failure"
resp["msg"] = "Unable to reach ES"
return jsonify(resp)
@app.route('/search')
def search():
key = request.args.get('q')
if not key:
return jsonify({
"status": "failure",
"msg": "Please provide a query"
})
try:
res = es.search(
index="sfdata",
body={
"query": {"match": {"fooditems": key}},
"size": 750 # max document size
})
except Exception as e:
return jsonify({
"status": "failure",
"msg": "error in reaching elasticsearch"
})
# filtering results
vendors = set([x["_source"]["applicant"] for x in res["hits"]["hits"]])
temp = {v: [] for v in vendors}
fooditems = {v: "" for v in vendors}
for r in res["hits"]["hits"]:
applicant = r["_source"]["applicant"]
if "location" in r["_source"]:
truck = {
"hours" : r["_source"].get("dayshours", "NA"),
"schedule" : r["_source"].get("schedule", "NA"),
"address" : r["_source"].get("address", "NA"),
"location" : r["_source"]["location"]
}
fooditems[applicant] = r["_source"]["fooditems"]
temp[applicant].append(truck)
# building up results
results = {"trucks": []}
for v in temp:
results["trucks"].append({
"name": v,
"fooditems": format_fooditems(fooditems[v]),
"branches": temp[v],
"drinks": fooditems[v].find("COLD TRUCK") > -1
})
hits = len(results["trucks"])
locations = sum([len(r["branches"]) for r in results["trucks"]])
return jsonify({
"trucks": results["trucks"],
"hits": hits,
"locations": locations,
"status": "success"
})
if __name__ == "__main__":
ENVIRONMENT_DEBUG = os.environ.get("DEBUG", False)
check_and_load_index()
app.run(host='0.0.0.0', port=5000, debug=ENVIRONMENT_DEBUG)

+ 3243
- 0
file/assignment2/FoodTrucks/flask-app/package-lock.json
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 26
- 0
file/assignment2/FoodTrucks/flask-app/package.json Ver fichero

@ -0,0 +1,26 @@
{
"name": "sf-food",
"version": "0.0.1",
"description": "SF food app",
"main": "index.js",
"scripts": {
"start": "webpack --progress --colors --watch",
"build": "NODE_ENV='production' webpack -p",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Prakhar Srivastav",
"license": "MIT",
"dependencies": {
"react": "^16.13.1",
"react-dom": "^16.13.1",
"superagent": "^5.2.2"
},
"devDependencies": {
"babel-core": "^6.3.26",
"babel-loader": "^6.2.0",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"webpack": "^1.12.9"
}
}

+ 3
- 0
file/assignment2/FoodTrucks/flask-app/requirements.txt Ver fichero

@ -0,0 +1,3 @@
elasticsearch>=7.0.0,<8.0.0
Flask==1.1.2
requests==2.23.0

+ 94
- 0
file/assignment2/FoodTrucks/flask-app/static/build/main.js
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 1
- 0
file/assignment2/FoodTrucks/flask-app/static/build/main.js.map
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


BIN
file/assignment2/FoodTrucks/flask-app/static/favicon.ico Ver fichero

Antes Después

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-144x144.png Ver fichero

Antes Después
Anchura: 144  |  Altura: 144  |  Tamaño: 8.2 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-192x192.png Ver fichero

Antes Después
Anchura: 192  |  Altura: 192  |  Tamaño: 11 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-36x36.png Ver fichero

Antes Después
Anchura: 36  |  Altura: 36  |  Tamaño: 1.8 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-48x48.png Ver fichero

Antes Después
Anchura: 48  |  Altura: 48  |  Tamaño: 2.8 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-72x72.png Ver fichero

Antes Después
Anchura: 72  |  Altura: 72  |  Tamaño: 3.8 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/android-icon-96x96.png Ver fichero

Antes Después
Anchura: 96  |  Altura: 96  |  Tamaño: 5.2 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-114x114.png Ver fichero

Antes Después
Anchura: 114  |  Altura: 114  |  Tamaño: 6.2 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-120x120.png Ver fichero

Antes Después
Anchura: 120  |  Altura: 120  |  Tamaño: 6.6 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-144x144.png Ver fichero

Antes Después
Anchura: 144  |  Altura: 144  |  Tamaño: 8.2 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-152x152.png Ver fichero

Antes Después
Anchura: 152  |  Altura: 152  |  Tamaño: 8.8 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-180x180.png Ver fichero

Antes Después
Anchura: 180  |  Altura: 180  |  Tamaño: 11 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-57x57.png Ver fichero

Antes Después
Anchura: 57  |  Altura: 57  |  Tamaño: 3.2 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-60x60.png Ver fichero

Antes Después
Anchura: 60  |  Altura: 60  |  Tamaño: 3.4 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-72x72.png Ver fichero

Antes Después
Anchura: 72  |  Altura: 72  |  Tamaño: 3.8 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-76x76.png Ver fichero

Antes Después
Anchura: 76  |  Altura: 76  |  Tamaño: 4.0 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon-precomposed.png Ver fichero

Antes Después
Anchura: 192  |  Altura: 192  |  Tamaño: 11 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/apple-icon.png Ver fichero

Antes Después
Anchura: 192  |  Altura: 192  |  Tamaño: 11 KiB

+ 2
- 0
file/assignment2/FoodTrucks/flask-app/static/icons/browserconfig.xml Ver fichero

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/favicon-16x16.png Ver fichero

Antes Después
Anchura: 16  |  Altura: 16  |  Tamaño: 1.2 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/favicon-32x32.png Ver fichero

Antes Después
Anchura: 32  |  Altura: 32  |  Tamaño: 1.7 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/favicon-96x96.png Ver fichero

Antes Después
Anchura: 96  |  Altura: 96  |  Tamaño: 5.2 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/favicon.ico Ver fichero

Antes Después

+ 41
- 0
file/assignment2/FoodTrucks/flask-app/static/icons/manifest.json Ver fichero

@ -0,0 +1,41 @@
{
"name": "App",
"icons": [
{
"src": "\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/ms-icon-144x144.png Ver fichero

Antes Después
Anchura: 144  |  Altura: 144  |  Tamaño: 8.2 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/ms-icon-150x150.png Ver fichero

Antes Después
Anchura: 150  |  Altura: 150  |  Tamaño: 8.5 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/ms-icon-310x310.png Ver fichero

Antes Después
Anchura: 310  |  Altura: 310  |  Tamaño: 22 KiB

BIN
file/assignment2/FoodTrucks/flask-app/static/icons/ms-icon-70x70.png Ver fichero

Antes Después
Anchura: 70  |  Altura: 70  |  Tamaño: 3.8 KiB

+ 68
- 0
file/assignment2/FoodTrucks/flask-app/static/src/app.js Ver fichero

@ -0,0 +1,68 @@
import React from "react";
import ReactDOM from "react-dom";
import Sidebar from "./components/Sidebar";
// setting up mapbox
mapboxgl.accessToken =
"pk.eyJ1IjoicHJha2hhciIsImEiOiJjaWZlbzQ1M2I3Nmt2cnhrbnlxcTQyN3VkIn0.uOaUAUqN2VS7dC7XKS0KkQ";
var map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/prakhar/cij2cpsn1004p8ykqqir34jm8",
center: [-122.44, 37.77],
zoom: 12,
});
ReactDOM.render(<Sidebar map={map} />, document.getElementById("sidebar"));
function formatHTMLforMarker(props) {
var { name, hours, address } = props;
var html =
'<div class="marker-title">' +
name +
"</div>" +
"<h4>Operating Hours</h4>" +
"<span>" +
hours +
"</span>" +
"<h4>Address</h4>" +
"<span>" +
address +
"</span>";
return html;
}
// setup popup display on the marker
map.on("click", function (e) {
map.featuresAt(
e.point,
{ layer: "trucks", radius: 10, includeGeometry: true },
function (err, features) {
if (err || !features.length) return;
var feature = features[0];
new mapboxgl.Popup()
.setLngLat(feature.geometry.coordinates)
.setHTML(formatHTMLforMarker(feature.properties))
.addTo(map);
}
);
});
map.on("click", function (e) {
map.featuresAt(
e.point,
{ layer: "trucks-highlight", radius: 10, includeGeometry: true },
function (err, features) {
if (err || !features.length) return;
var feature = features[0];
new mapboxgl.Popup()
.setLngLat(feature.geometry.coordinates)
.setHTML(formatHTMLforMarker(feature.properties))
.addTo(map);
}
);
});

+ 36
- 0
file/assignment2/FoodTrucks/flask-app/static/src/components/Intro.js Ver fichero

@ -0,0 +1,36 @@
import React from "react";
export default function Intro() {
return (
<div className="intro">
<h3>About</h3>
<p>
This is a fun application built to accompany the{" "}
<a href="http://prakhar.me/docker-curriculum">docker curriculum</a> - a
comprehensive tutorial on getting started with Docker targeted
especially at beginners.
</p>
<p>
The app is built with Flask on the backend and Elasticsearch is the
engine powering the search.
</p>
<p>
The frontend is hand-crafted with React and the beautiful maps are
courtesy of Mapbox.
</p>
<p>
If you find the design a bit ostentatious, blame{" "}
<a href="http://genius.com/Justin-bieber-baby-lyrics">Genius</a> for
giving me the idea of using this color scheme. If you love it, I smugly
take all the credit. (¯)
</p>
<p>
Lastly, the data for the food trucks is made available in public domain
by{" "}
<a href="https://data.sfgov.org/Economy-and-Community/Mobile-Food-Facility-Permit/rqzj-sfat">
SF Data
</a>
</p>
</div>
);
}

+ 205
- 0
file/assignment2/FoodTrucks/flask-app/static/src/components/Sidebar.js Ver fichero

@ -0,0 +1,205 @@
import React from "react";
import request from "superagent";
import Intro from "./Intro";
import Vendor from "./Vendor";
class Sidebar extends React.Component {
constructor(props) {
super(props);
this.state = {
results: [],
query: "",
firstLoad: true,
};
this.onChange = this.onChange.bind(this);
this.handleSearch = this.handleSearch.bind(this);
this.handleHover = this.handleHover.bind(this);
}
fetchResults() {
request.get("/search?q=" + this.state.query).end((err, res) => {
if (err) {
alert("error in fetching response");
} else {
this.setState({
results: res.body,
firstLoad: false,
});
this.plotOnMap();
}
});
}
generateGeoJSON(markers) {
return {
type: "FeatureCollection",
features: markers.map((p) => ({
type: "Feature",
properties: {
name: p.name,
hours: p.hours,
address: p.address,
"point-color": "253,237,57,1",
},
geometry: {
type: "Point",
coordinates: [
parseFloat(p.location.coordinates[0]),
parseFloat(p.location.coordinates[1]),
],
},
})),
};
}
plotOnMap(vendor) {
const map = this.props.map;
const results = this.state.results;
const markers = [].concat.apply(
[],
results.trucks.map((t) =>
t.branches.map((b) => ({
location: b.location,
name: t.name,
schedule: b.schedule,
hours: b.hours,
address: b.address,
}))
)
);
var highlightMarkers, usualMarkers, usualgeoJSON, highlightgeoJSON;
if (vendor) {
highlightMarkers = markers.filter(
(m) => m.name.toLowerCase() === vendor.toLowerCase()
);
usualMarkers = markers.filter(
(m) => m.name.toLowerCase() !== vendor.toLowerCase()
);
} else {
usualMarkers = markers;
}
usualgeoJSON = this.generateGeoJSON(usualMarkers);
if (highlightMarkers) {
highlightgeoJSON = this.generateGeoJSON(highlightMarkers);
}
// clearing layers
if (map.getLayer("trucks")) {
map.removeLayer("trucks");
}
if (map.getSource("trucks")) {
map.removeSource("trucks");
}
if (map.getLayer("trucks-highlight")) {
map.removeLayer("trucks-highlight");
}
if (map.getSource("trucks-highlight")) {
map.removeSource("trucks-highlight");
}
map
.addSource("trucks", {
type: "geojson",
data: usualgeoJSON,
})
.addLayer({
id: "trucks",
type: "circle",
interactive: true,
source: "trucks",
paint: {
"circle-radius": 8,
"circle-color": "rgba(253,237,57,1)",
},
});
if (highlightMarkers) {
map
.addSource("trucks-highlight", {
type: "geojson",
data: highlightgeoJSON,
})
.addLayer({
id: "trucks-highlight",
type: "circle",
interactive: true,
source: "trucks-highlight",
paint: {
"circle-radius": 8,
"circle-color": "rgba(164,65,99,1)",
},
});
}
}
handleSearch(e) {
e.preventDefault();
this.fetchResults();
}
onChange(e) {
this.setState({ query: e.target.value });
}
handleHover(vendorName) {
this.plotOnMap(vendorName);
}
render() {
if (this.state.firstLoad) {
return (
<div>
<div id="search-area">
<form onSubmit={this.handleSearch}>
<input
type="text"
value={this.state.query}
onChange={this.onChange}
placeholder="Burgers, Tacos or Wraps?"
/>
<button>Search!</button>
</form>
</div>
<Intro />
</div>
);
}
const query = this.state.query;
const resultsCount = this.state.results.hits || 0;
const locationsCount = this.state.results.locations || 0;
const results = this.state.results.trucks || [];
const renderedResults = results.map((r, i) => (
<Vendor key={i} data={r} handleHover={this.handleHover} />
));
return (
<div>
<div id="search-area">
<form onSubmit={this.handleSearch}>
<input
type="text"
value={query}
onChange={this.onChange}
placeholder="Burgers, Tacos or Wraps?"
/>
<button>Search!</button>
</form>
</div>
{resultsCount > 0 ? (
<div id="results-area">
<h5>
Found <span className="highlight">{resultsCount}</span> vendors in{" "}
<span className="highlight">{locationsCount}</span> different
locations
</h5>
<ul> {renderedResults} </ul>
</div>
) : null}
</div>
);
}
}
export default Sidebar;

+ 70
- 0
file/assignment2/FoodTrucks/flask-app/static/src/components/Vendor.js Ver fichero

@ -0,0 +1,70 @@
import React from "react";
export default class Vendor extends React.Component {
constructor(props) {
super(props);
this.state = {
isExpanded: false,
};
this.toggleExpand = this.toggleExpand.bind(this);
}
formatFoodItems(items) {
if (this.state.isExpanded) {
return items.join(", ");
}
const summary = items.join(", ").substr(0, 80);
if (summary.length > 70) {
const indexOfLastSpace =
summary.split("").reverse().join("").indexOf(",") + 1;
return summary.substr(0, 80 - indexOfLastSpace) + " & more...";
}
return summary;
}
toggleExpand() {
this.setState({
isExpanded: !this.state.isExpanded,
});
}
render() {
const { name, branches, fooditems, drinks } = this.props.data;
const servesDrinks = (
<div className="row">
<div className="icons">
{" "}
<i className="ion-wineglass"></i>{" "}
</div>
<div className="content">Serves Cold Drinks</div>
</div>
);
return (
<li
onMouseEnter={this.props.handleHover.bind(null, name)}
onClick={this.toggleExpand}
>
<p className="truck-name">{name}</p>
<div className="row">
<div className="icons">
{" "}
<i className="ion-android-pin"></i>{" "}
</div>
<div className="content"> {branches.length} locations </div>
</div>
{drinks ? servesDrinks : null}
<div className="row">
<div className="icons">
{" "}
<i className="ion-fork"></i> <i className="ion-spoon"></i>
</div>
<div className="content">
Serves {this.formatFoodItems(fooditems)}
</div>
</div>
</li>
);
}
}

+ 202
- 0
file/assignment2/FoodTrucks/flask-app/static/styles/main.css Ver fichero

@ -0,0 +1,202 @@
html,
body {
padding: 0;
color: #aaa;
box-sizing: border-box;
font-family: "Titillium Web", sans-serif;
margin: 0;
}
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0);
}
20%,
60% {
transform: rotate(-25deg);
}
40%,
80% {
transform: rotate(10deg);
}
}
@media (max-width: 500px) {
.github-corner:hover .octo-arm {
animation: none;
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
}
i {
color: #3a3a3a;
}
div.intro a,
div.intro a:visited {
color: #fded39;
}
div.intro {
padding: 10px;
}
div.intro h3 {
color: #fded39;
text-transform: uppercase;
}
h1 i {
color: black;
text-shadow: none;
font-size: 21px;
}
textarea,
input,
button {
outline: none;
}
.container {
height: 90vh;
display: flex;
}
#map {
flex: 3;
}
#sidebar {
background: #1a1a1a;
border-left: 1px solid #444444;
width: 320px;
flex: 1;
overflow-y: scroll;
}
#sidebar div#results-area {
overflow-y: scroll;
}
div#heading {
background: #fded39;
margin: 0;
height: 10vh;
text-align: center;
}
div#heading h1 {
font-size: 25px;
text-transform: uppercase;
text-shadow: -2px 2px black;
margin: 0;
color: #fded39;
}
div#heading p {
color: black;
font-style: italic;
font-size: 12px;
margin: 0;
}
div#search-area {
padding: 10px;
border-bottom: 1px solid #444444;
}
div#search-area input,
div#search-area button {
padding: 10px 8px;
border: none;
}
div#search-area input {
width: 195px;
}
div#search-area button {
background: #fded39;
font-weight: 600;
text-transform: uppercase;
}
div#results-area {
padding: 10px;
}
div#results-area h5 {
font-weight: 200;
font-style: italic;
margin: 0;
color: #ddd;
}
div#results-area h5 span.highlight {
color: #fded39;
font-weight: 600;
}
div#results-area ul {
padding: 0;
list-style-type: none;
}
div#results-area ul li {
border: 1px solid #444;
padding: 5px 10px;
margin-bottom: 10px;
cursor: pointer;
}
div#results-area ul li:hover {
border: 1px solid #fded39;
}
div#results-area ul li p {
margin: 0;
font-size: 14px;
}
div#results-area ul li p.truck-name {
color: #fded39;
text-transform: uppercase;
margin: 0;
font-size: 16px;
margin-bottom: 5px;
}
div#results-area ul li div.row {
display: flex;
flex-direction: row;
}
div#results-area ul li div.row div.icons {
flex-shrink: 0;
width: 16px;
}
div#results-area ul li div.row div.content {
margin-left: 8px;
}
.mapboxgl-popup-content {
background: black;
font-family: "Titillium Web", sans-serif;
}
.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip {
border-top-color: black;
}
.mapboxgl-popup-anchor-top .mapboxgl-popup-tip {
border-bottom-color: black;
}
.mapboxgl-popup-close-button {
color: white;
}
.mapboxgl-popup-content .marker-title {
color: #fded39;
text-transform: uppercase;
font-size: 14px;
}
.mapboxgl-popup-content h4 {
margin: 0;
margin-top: 10px;
}

+ 50
- 0
file/assignment2/FoodTrucks/flask-app/templates/index.html Ver fichero

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>SF Food Trucks</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<link href='https://fonts.googleapis.com/css?family=Titillium+Web:400,700' rel='stylesheet' type='text/css'>
<script src='https://api.mapbox.com/mapbox-gl-js/v1.9.1/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v1.9.1/mapbox-gl.css' rel='stylesheet' />
<link href='/static/styles/main.css' rel='stylesheet' />
<link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css" />
<link rel="apple-touch-icon" sizes="57x57" href="/static/icons/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/static/icons/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/static/icons/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/static/icons/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/static/icons/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/static/icons/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/static/icons/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/static/icons/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/static/icons/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/static/icons//android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/static/icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/icons/favicon-16x16.png">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
<meta name="theme-color" content="#ffffff">
</head>
<body>
<!-- awesome svg octocat thanks to http://tholman.com/ -->
<a href="https://github.com/prakhar1989/FoodTrucks/" class="github-corner" title="Fork me on Github">
<svg width="72" height="72" viewBox="0 0 250 250" style="fill:#000; color:#FDED39; position: absolute; top: 0; border: 0; left: 0; transform: scale(-1, 1);">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
</svg>
</a>
<div id="heading">
<h1>SF F <i class="ion-pizza"></i> <i class="ion-icecream"></i> d Trucks</h1>
<p>San Francisco's finger-licking street food now at your fingertips.</p>
</div>
<div class="container">
<div id='map'></div>
<div id="sidebar"> </div>
</div>
<script src='/static/build/main.js'></script>
</body>
</html>

+ 19
- 0
file/assignment2/FoodTrucks/flask-app/webpack.config.js Ver fichero

@ -0,0 +1,19 @@
module.exports = {
cache: true,
entry: './static/src/app.js',
output: {
filename: './static/build/main.js'
},
devtool: 'source-map',
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
},
]
}
};

+ 13
- 0
file/assignment2/FoodTrucks/setup-aws-ecs.sh Ver fichero

@ -0,0 +1,13 @@
#!/bin/bash
# configure
ecs-cli configure --region us-east-1 --cluster foodtrucks
# setup cloud formation template
ecs-cli up --keypair ecs --capability-iam --size 1 --instance-type t2.medium
# deploy
cd aws-ecs && ecs-cli compose --file aws-compose.yml up
# check
ecs-cli ps

+ 13
- 0
file/assignment2/FoodTrucks/setup-docker.sh Ver fichero

@ -0,0 +1,13 @@
#!/bin/bash
# build the flask container
docker build -t prakhar1989/foodtrucks-web .
# create the network
docker network create foodtrucks-net
# start the ES container
docker run -d --name es --net foodtrucks-net -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2
# start the flask app container
docker run -d --net foodtrucks-net -p 5000:5000 --name foodtrucks-web prakhar1989/foodtrucks-web

+ 46
- 0
file/assignment2/FoodTrucks/utils/generate_geojson.py Ver fichero

@ -0,0 +1,46 @@
import json
import requests
def getData(url):
r = requests.get(url)
return r.json()
def convertData(data, msymbol="restaurant", msize="medium"):
data_dict = []
for d in data:
if d.get('longitude') and d.get("latitude"):
data_dict.append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [float(d["longitude"]),
float(d["latitude"])]
},
"properties": {
"name": d.get("applicant", ""),
"marker-symbol": msymbol,
"marker-size": msize,
"marker-color": "#CC0033",
"fooditems": d.get('fooditems', ""),
"address": d.get("address", "")
}
})
return data_dict
def writeToFile(data, filename="data.geojson"):
template = {
"type": "FeatureCollection",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
},
},
"features": data }
with open(filename, "w") as f:
json.dump(template, f, indent=2)
print "Geojson generated"
if __name__ == "__main__":
data = getData("http://data.sfgov.org/resource/rqzj-sfat.json")
writeToFile(convertData(data[:350]), filename="trucks.geojson")

+ 5771
- 0
file/assignment2/FoodTrucks/utils/trucks.geojson
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


Cargando…
Cancelar
Guardar