20 Commits
Author SHA1 Message Date
Mark acf1691fd0 Merge pull request #14 from uw935/fix/reqs
v1.3-hotfix: Requirements
2024-04-22 00:15:49 +03:00
te5154c 7cf015be22 fix: requirements 2024-04-22 00:14:56 +03:00
Mark b22879474b Merge pull request #13 from uw935/feature/fastapi
v1.3: Backend rewrite to FastAPI
2024-04-22 00:06:18 +03:00
te5154c f7f76b1cfd add: new reqs && cmd docker 2024-04-22 00:04:58 +03:00
te5154c 1cc1772c4b fix: 404 error 2024-04-22 00:03:50 +03:00
te5154c b4d0c388ef add: fastapi 2024-04-21 23:57:42 +03:00
Mark f4230e1bf7 Merge pull request #12 from uw935/feature/deploy-by-package
added deploy using github packages
2024-04-14 20:22:55 +03:00
chydo 9ba02586ad fixed: repo name changed 2024-04-14 15:54:17 +07:00
chydo 4a627cc8b0 added: deploy using github packages 2024-04-14 15:53:03 +07:00
Mark ea704ca5af Merge pull request #11 from uw935/fix/iphone-display
Fixed first appearing on small screens
2024-03-02 16:04:43 +03:00
te5154c 4ef47c6ce5 fix first display on apple iphones 2024-03-02 16:02:36 +03:00
Mark f860de470f Merge pull request #10 from uw935/feature/borders
New borders
2024-03-02 02:08:09 +03:00
uw935 2d1e4e6f9f refactor: html style fixed && add: border footer & header changed 2024-03-02 02:06:30 +03:00
Mark 96175d2af5 Merge pull request #9 from uw935/hotfix/userid
v1.2.1 — hotfix #1
2024-02-28 00:57:33 +03:00
te5154c bc8ca01729 add: notification in viewer when error appears && api requests exception && new map-style (preparing for new update) && buttons color change 2024-02-28 00:51:08 +03:00
te5154c ce41b31331 hotfix: userid 2024-02-27 23:58:57 +03:00
Mark cdbe2820f3 Merge pull request #8 from uw935/feature/minorchanges
Migration to Python 3.8
2024-02-27 19:58:38 +03:00
te5154c 62c31d6f74 add: contributing information to README 2024-02-27 19:57:01 +03:00
te5154c 507f01630f migration to python38 2024-02-26 23:55:37 +03:00
te5154c a06827b1d2 switch to new python version && minor bugs fixed 2024-02-26 23:52:31 +03:00
13 changed files with 260 additions and 115 deletions
+34 -14
View File
@@ -1,23 +1,43 @@
name: deployment on server
name: Create and publish a Docker image
on:
push:
branches: [ master ]
branches:
- master
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
build-and-push-image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
permissions:
contents: read
packages: write
- name: update bot
uses: appleboy/ssh-action@v1.0.3
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script: |
cd /home/deploy/vatsimfox && git pull origin master --force && cd /home/deploy/vatsimfox/src
docker compose up -d --build
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: src/.
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+1
View File
@@ -1 +1,2 @@
__pycache__
venv/
+19 -3
View File
@@ -1,7 +1,7 @@
<br>
<p align="center">
<img align="center" src="media/cover.png">
<h3 align="center">VATSIM Fox</h3>
<h3 align="center">VATSIM Fox — v1.2</h3>
<p align="center">Website to find out your detailed VATSIM stastic</p>
</p>
<br>
@@ -11,10 +11,10 @@
Project based on [Virtual Air Traffic Simulation Network (VATSIM) API](https://vatsim.dev/).
Written in Python 3.7.10 + JS (JQuery framework) + Boostrap
Written in Python 3.8.18 + JS (JQuery framework) + Boostrap
## Startup
For run application on your own machine, you must install docker. Then just one command in the "src" folder with the project source code:
To run application on your own machine, you must install docker. Then just one command in the "src" folder with the project source code:
```bash
docker compose up
@@ -25,6 +25,22 @@ Any help with this project would be greatly appreciated. Fixes or some features
VATSIM Fox is free software, open to suggestions and contributions. All the files are under the MIT License.
### How to set virtual environment
```bash
# you must be in "src" folder before init
cd src
python -m venv venv
.\venv\Scripts\activate
# downloading requirements && upgrading pip
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
# starting application
python -m flask run --host 0.0.0.0 --debug
```
## Feature
There is small TODO list I would like to make in this project:
-1
View File
@@ -1,3 +1,2 @@
[flake8]
exclude = .git, pycache, .env, .venv
max-line-length = 120
+2 -2
View File
@@ -1,4 +1,4 @@
FROM python:3.7.10
FROM python:3.8.18
WORKDIR /build/
@@ -9,4 +9,4 @@ RUN pip install -r requirements.txt
COPY . /build/
CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0"]
CMD ["python3", "app.py"]
+99 -23
View File
@@ -1,40 +1,116 @@
from flask import Flask, render_template, abort, request, jsonify
import uvicorn
import requests
from fastapi import (
FastAPI,
Request,
HTTPException,
)
from fastapi.responses import FileResponse
from starlette.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = Flask(__name__)
VATSIM_API_URL = "https://api.vatsim.net/v2/"
@app.route("/")
def index_page():
return render_template("index.html")
app = FastAPI(
title="VATSIM Fox",
docs_url=None,
redoc_url=None,
openapi_url=None,
redirect_slashes=True
)
templates = Jinja2Templates(directory="templates")
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.errorhandler(404)
def errorhandler_page(_):
return render_template("4O4.html"), 404
@app.get("/")
async def get_index_page(request: Request):
"""
Index page
# Custom wrapper to fetch VATSIM data
@app.route("/api/request")
def request_handler():
request_string = request.args.get("request_string")
:param request: FastAPI request
:return: HTML template
"""
if request_string:
return requests.get(f"{VATSIM_API_URL}{request_string}").json()
return jsonify({"result": "not found"})
return templates.TemplateResponse(
name="index.html",
context={
"request": request,
}
)
@app.route("/<int:cid>/")
def viewer_page(cid):
user = requests.get(f"{VATSIM_API_URL}members/{cid}").json()
@app.get("/favicon.ico", include_in_schema=False)
async def get_favicon():
"""
Get favicon
:return: Icon file
"""
return FileResponse("./static/icon.ico")
@app.get("/{cid}/")
async def get_viewer_page(request: Request, cid: str = None):
"""
Viewer page
:param request: FastAPI request
:param cid: User CID
:return: HTML template
"""
try:
user = requests.get(f"{VATSIM_API_URL}members/{cid}").json()
except requests.exceptions.JSONDecodeError:
raise HTTPException(404)
if "detail" in user and user["detail"] == "Not Found":
return abort(404)
raise HTTPException(404)
return render_template("viewer.html", data=(user, ))
return templates.TemplateResponse(
name="viewer.html",
context={
"user": user,
"request": request,
}
)
@app.exception_handler(404)
async def error_handler(request: Request, _):
"""
Handler to the 404 HTTP error
:param request: FastAPI request
:return: HTML template
"""
return templates.TemplateResponse(
name="4O4.html",
context={
"request": request,
}
)
@app.get("/api/request")
def request_handler(request_string: str):
"""
Wrapper to the VATSIM API functions
:param request_string: String that will fetched from VATSIM API
"""
if request_string:
try:
return requests.get(f"{VATSIM_API_URL}{request_string}").json()
except requests.exceptions.JSONDecodeError:
return {"result": "error", "message": "Something went wrong. Report that error please"}
return {"result": "not found", "message": "Not found"}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=80)
uvicorn.run(app=app, host="0.0.0.0", port=80)
+3 -1
View File
@@ -1,3 +1,5 @@
Flask==2.2.5
fastapi==0.110.2
requests==2.31.0
uvicorn==0.29.0
flake8==5.0.4
Jinja2==3.1.3
+4
View File
@@ -6,3 +6,7 @@
.fox_active, .fox_atc_active {
background-color: #dee2e6 !important;
}
#notification_wrapper {
display: none;
}
+13 -5
View File
@@ -2,8 +2,10 @@
let userSession = {};
fetch(ATC_API_URL, {}).then(response => response.json()).then(result => {
if (result && result["result"] == "not found")
return
if (result && (result["result"] == "not found" || result["result"] == "error")) {
showAlert("Error while getting ATC information", result["message"]);
return;
}
for (let session of result["items"]) {
// Separate each session by it's connection ID
@@ -72,13 +74,13 @@ $.getJSON("/static/data/airports.json", function(json) {
// Map creation
const MAP_ELEMENT = L.map("fox_map");
const MAP_DEFAULT_TARGET = L.latLng("47.50737", "19.04611");
const MAP_DEFAULT_ZOOM_VIEW = 14;
const MAP_DEFAULT_ZOOM_VIEW = 2;
const MAP_DEFAULT_TARGET = L.latLng("0", "0");
let line = null;
MAP_ELEMENT.setView(MAP_DEFAULT_TARGET, MAP_DEFAULT_ZOOM_VIEW);
L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
L.tileLayer("https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png", {
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
subdomains: "abcd",
maxZoom: 20,
@@ -153,3 +155,9 @@ function showSession(session) {
$(".atc_transfers").text(session["handoffsinitiated"]);
$(".atc_received").text(session["handoffsreceived"]);
}
function showAlert(title, message) {
$("#notification_wrapper").css("display", "block");
$("#notification_title").text(title);
$("#notification_message").text(message);
}
+6 -6
View File
@@ -2,11 +2,11 @@
{% block title %}404 - Not Found / VATSIM Fox{% endblock %}
{% block content %}
<div class="p-5 mb-4 bg-light rounded-3">
<div class="container-fluid py-5">
<h1 class="mb-4 display-5 fw-bold">4O4 - Page Not Found</h1>
<a href="/">
<button class="btn btn-outline-primary" type="submit">Back to index page</button>
</a>
</div>
<div class="container-fluid py-5">
<h1 class="mb-4 display-5 fw-bold">4O4 - Page Not Found</h1>
<a href="/">
<button class="btn btn-outline-primary" type="submit">Back to index page</button>
</a>
</div>
</div>
{% endblock %}
+33 -23
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="VATSIM Fox — see your detailed profile statistic">
<meta name="description" content="VATSIM Fox — check your detailed profile statistic">
<meta name="keywords" content="VATSIM FOX, fox, vatsim, vatsim statistic, statistic, vatsim fox, flight simulator">
<meta name="author" content="uw935">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -10,42 +10,52 @@
<meta content="yes" name="apple-touch-fullscreen" />
<meta name="apple-mobile-web-app-status-bar-style" content="#FA8232">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width = 320, initial-scale = 2.3, user-scalable = no">
<link rel="icon" href="{{ url_for('static', filename='icon.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', path='icon.ico') }}" type="image/x-icon">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
{% block header_css %}{% endblock %}
</head>
<style>
:root {
--bs-primary-rgb: 250, 130, 50
}
.btn-outline-primary:hover, .btn-outline-primary:active {
color: white !important;
background-color: var(--main-rgb) !important;
border-color: var(--main-rgb) !important;
}
.btn-outline-primary {
border-color: var(--main-rgb);
color: var(--main-rgb);
}
:root {
--main-rgb: #FA8232;
--bs-primary-rgb: 250, 130, 50
}
</style>
<body>
<main>
<div class="container py-4">
<header class="pb-3 mb-4 border-bottom">
<header class="border-bottom">
<div class="container py-4">
<a href="/" class="d-flex align-items-center text-dark text-decoration-none">
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="40" height="32" class="me-2" viewBox="0 0 118 94" role="img"><title>Bootstrap</title><path fill-rule="evenodd" clip-rule="evenodd" d="M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z" fill="currentColor"></path></svg> -->
<span class="fs-4">🦊 VATSIM <span class="text-primary">Fox</span></span>
<span class="fs-4">🦊 VATSIM <span class="text-primary">Fox</span></span>
</a>
</header>
<!-- <div class="mb-4 alert alert-primary" role="alert">
Notification
</div>
-->
</div>
</header>
<div class="container py-4">
{% block content %}{% endblock %}
<footer class="pt-3 mt-4 text-muted border-top">
created by <a href="https://uw935.t.me/" style="text-decoration: underline; cursor: pointer;">uw935</a> (1606255)
</footer>
</div>
</main>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
{% block javascript_code %}{% endblock %}
<footer class="text-muted border-top">
<div class="container py-4">
created by
<a href="https://uw935.com/" style="text-decoration: underline; cursor: pointer;">uw935</a> (1606255)
</div>
</footer>
</main>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
{% block javascript_code %}{% endblock %}
</body>
</html>
+30 -28
View File
@@ -2,43 +2,45 @@
{% block title %}VATSIM Fox — view detailed profile statistic{% endblock %}
{% block content %}
<div class="p-5 mb-4 bg-light rounded-3">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">Enter your VATSIM CID</h1>
<p class="col-md-8 fs-4">See your detailed profile statistic</p>
<div class="d-flex" role="search">
<input class="fox_input form-control me-2" type="number" placeholder="100000" aria-label="CID">
<button class="fox_submit btn btn-outline-success" type="submit">Search</button>
</div>
<p class="fox_message text-primary col-md-8 fs-4"></p>
</div>
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">Enter your VATSIM CID</h1>
<p class="col-md-8 fs-4">See your detailed profile statistic</p>
<div class="d-flex" role="search">
<input class="fox_input form-control me-2" type="number" placeholder="100000" aria-label="CID">
<button class="fox_submit btn btn-outline-primary" type="submit">Search</button>
</div>
<p class="fox_message text-primary col-md-8 fs-4"></p>
</div>
</div>
<div class="mb-4 row align-items-md-stretch">
<div class="col-md-6">
<div class="h-100 p-5 text-bg-dark rounded-3">
<h2>About</h2>
<p>VATSIM Fox is an open-source project to make it easier for users to see their detailed profile statistic</p>
</div>
</div>
<div class="col-md-6">
<div class="h-100 p-5 bg-light border rounded-3">
<h2>Open source project</h2>
<p>This project is fully opened, so you can contribute in it if you want</p>
<a href="https://github.com/uw935/vatsimfox">
<button class="btn btn-outline-secondary" type="button">GitHub</button>
</a>
</div>
</div>
<div class="col-md-6">
<div class="h-100 p-5 text-bg-dark rounded-3">
<h2>About</h2>
<p>VATSIM Fox is an open-source project to make it easier for users to see their detailed profile statistic</p>
</div>
</div>
<div class="col-md-6">
<div class="h-100 p-5 bg-light border rounded-3">
<h2>Open source project</h2>
<p>This project is fully opened, so you can contribute in it if you want</p>
<a href="https://github.com/uw935/vatsimfox">
<button class="btn btn-outline-secondary" type="button">GitHub</button>
</a>
</div>
</div>
</div>
{% endblock %}
{% block javascript_code %}
<script type="text/javascript">
$(".fox_submit").click(function() {
const INPUT_CID = 0 + $(".fox_input").val();
const INPUT_CID = $(".fox_input").val();
if ((0 + INPUT_CID) < 100000) {
$(".fox_message").text("CID should be equal or more than 100000");
return;
if (INPUT_CID < 100000) {
$(".fox_message").text("CID should be equal or more than 100000");
return;
}
fetch("/api/request?request_string=members/" + INPUT_CID, {})
+14 -7
View File
@@ -1,12 +1,19 @@
{% extends "/include/template.html" %}
{% block title %}User {{user_cid}} statistic — VATSIM Fox{% endblock %}
{% block title %}User {{user["id"]}} statistic — VATSIM Fox{% endblock %}
{% block header_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='viewer.css') }}">
<link rel="stylesheet" href="{{ url_for('static', path='viewer.css') }}">
<link href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" rel="stylesheet"/>
{% endblock %}
{% block content %}
<div class="mb-5">
<h2 class="text-center text-primary mb-4">{{data[0]["id"]}}</h2>
<div id="notification_wrapper">
<div class="mb-4 alert alert-warning" role="alert">
<h4 id="notification_title"></h4>
<span id="notification_message"></span>
</div>
</div>
<h2 class="text-center text-primary mb-4">{{user["id"]}}</h2>
<p class="fs-4 text-secondary">Date of registration: <span class="date_registration"></span></p>
</div>
@@ -91,7 +98,7 @@
<div class="col-md" style="height: 321px;">
<div class="h-auto p-5 bg-light border rounded-3">
<h2 class="atc_callsign">UUEE_DEL</h2>
<h2 class="atc_callsign">Callsign</h2>
<span>
<b>Start time:</b>
<span class="atc_start_time"></span>
@@ -133,11 +140,11 @@
{% block javascript_code %}
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
<script type="text/javascript">
const USER_CID = '{{data[0]["id"]}}';
const USER_CID = '{{user["id"]}}';
const FLIGHTPLANS_API_URL = "/api/request?request_string=members/" + USER_CID + "/flightplans";
const ATC_API_URL = "/api/request?request_string=members/" + USER_CID + "/atc";
$(".date_registration").text("{{data[0]['reg_date']}}".replace("T", " "));
$(".date_registration").text("{{user['reg_date']}}".replace("T", " "));
</script>
<script type="text/javascript" src="{{ url_for('static', filename='viewer.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', path='viewer.js') }}"></script>
{% endblock %}