今回も前回からの続きです。
今回は前回までで作成したAPIを使ったWebページを作ってみます。
あんまり凝ったのは作れないのでテーブルだけでシンプルに作ります。
静的ファイルのマウント
静的ファイル - FastAPI で静的ファイルのマウントができるそうなのでこ横着してそこにページを作ってしまいます。
チュートリアルによると aiofiles
が必要らしいのでインストールします。
pip3 install aiofiles
webapi
の下にsample
というディレクトリを作ってHello world
とだけ書いたindex.htmlを作成します。
そして、api.py を以下のように書き換えて実行してみます。
from fastapi import FastAPI from fastapi.staticfiles import StaticFiles app = FastAPI() app.mount( "/api/sample", StaticFiles(directory="sample", html=True), name="spa") if __name__ == "__main__": import uvicorn uvicorn.run("api:app", host="0.0.0.0", port=1234, reload=True)
http://*******:1234/api/sample をブラウザで開くと、めでたくHello worldされてますね。
ページの構築
では簡単に検索用ページの構築をしていきましょう。
今回はJavascriptフレームワークにVue.js
を、CSSフレームワークにBootStrapVue
を利用します。
非同期のHTTP通信を行いたいので、axios
も利用します。
index.html
<html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap@4.5.3/dist/css/bootstrap.min.css" > <link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue@2.21.2/dist/bootstrap-vue.min.css" > </head> <body> <div id="app"> <b-button @click="search">search</b-button> <div> <b-table :items="result" :fields="fields" striped hover small /> </div> </div> <script src="https://unpkg.com/vue@2.6.12/dist/vue.min.js"></script> <script src="https://unpkg.com/axios@0.21.1/dist/axios.min.js"></script> <script src="https://unpkg.com/bootstrap-vue@2.21.2/dist/bootstrap-vue.min.js"></script> <script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue-icons.min.js"></script> <script type="text/javascript" src="./main.js"></script> </body> </html>
main.js
function convert(d) { var formattedDate = function(date) { return date.getFullYear() + '/' + ('0' + (date.getMonth() + 1)).slice(-2) + '/' + ('0' + date.getDate()).slice(-2) + ' ' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2) }; d.time = formattedDate(new Date(d.time)) return d } function listed(data) { data.map(d => convert(d)) return data } var app = new Vue({ el: '#app', data: { result: [], fields: [ { key: 'name', sortable: true }, { key: 'location', sortable: true }, { key: 'time', sortable: true }, ] }, methods: { search: function() { axios .get(`http://********:1234/api/search/`) .then(response => { this.result = listed(response.data) }) .catch(error => { this.result = [] }) } } });
ボタンを押してsearch
メソッドがコールされると、http://********:1234/api/search/
を呼び出します。
その結果をListにして this.resultに代入します。
resultは b-table
の itemsにバインドされているので、いい感じで表示してくれます。
これらを前回作成した api/search
のAPIに合わせた入力を与えることで冒頭のイメージで示したページが出来上がります。
運用
Webページもできたのでひとまず完成ですが、いちいちコマンドラインで起動するんじゃなくてサービス化しましょう。
まずはWebサーバであるngnixをインストールします。
sudo apt install nginx
ブラウザからhttp://raspberrypi.local
を開いて
このページが表示されればセットアップできています。
次はASGIです。
すでにuvicornを利用してるのでそれでいいんですが、Uvicornのドキュメントで
Run gunicorn -k uvicorn.workers.UvicornWorker for production.
との記載があるので、それに従います。
インストールはpipで行います。
pip3 install gunicorn
設定はgunicorn.conf.py
に書くっぽいのでファイルを用意します。
from os import getenv from multiprocessing import cpu_count bind = '0.0.0.0:' + str(getenv('PORT', 8000)) worker_class = 'uvicorn.workers.UvicornWorker' workers = cpu_count() loglevel = "debug" accesslog = None
続いてWebAppをサービス化します。
/etc/systemd/system
に*.serviceファイルを作成します(シンボリックリンクの方がいいかもです)。
ファイル名はわかればなんでもいいです。私はtrack-location.service
としてみました。
track-location.service
[Unit] Description = Tracker API daemon [Service] User=pi WorkingDirectory=/home/pi/App/track-location/webapi ExecStart=/home/pi/.local/bin/gunicorn api:app [Install] WantedBy=multi-user.target
ユーザ名やパスは各自の環境に合わせて変更してください。
これで準備が整ったのでデーモンを登録します。
sudo systemctrl daemon-reload sudo systemctrl start track-location.service
問題なく起動できているかは systemctrl status track-location.service
で確認できます。
うまく起動できていれば、nginxに繋げてしまえば完了です。
nginxの設定を記述します。
/etc/nginx/conf.d/default.conf
server { listen 80 default; server_name 127.0.0.1 localhost; location /api { proxy_pass http://127.0.0.1:8000/api; } }
また最初からある /etc/nginx/sites-enabled/default
を削除します。
ここまでできたらnginxを再起動して http://*******/api/sample
をブラウザで開いてみましょう。
作成したアプリがひらけました。
期間を"Today"にしてみると、
あおいちゃんが学園寮に、いちごちゃんが学園長室にそれぞれ20:48頃までいたことが確認できます。
その後の位置は確認できてないので、きっと二人でどこかに出かけたんでしょうね。
と、動作確認で適当に登録したデータでのストーリー妄想は置いておいて、ゆるーくお手軽にプレゼンス管理するソリューションが出来上がりました。
思った以上に説明が長引いてしまったのが反省点ですが、なかなか楽しかったです。
追記
実験的に数人の有志に協力してもらって運用してみました。
意外なことにそこそこ使い物になったんですが、帰宅時にPCを閉じるだけでスリープもさせない運用の人が少なからずいて、24時間在室しっぱなしでよくわからんという状況が発生してました😅
また、実験などでしばらくPC触らない人も結構いて、同じ職場でもいろんな働き方の人がいるんだなって再確認させられました。
色々考えたけど、まだまだ工夫が必要だったな、、というオチでした。
頭の中だけで考えてると仕様の抜けが簡単に発生しますね(反省)