Author SHA1 Message Date
te5154c 14bef8d0ae conflicts from master resolved && map display finnaly fixed 2024-03-03 10:53:19 +03:00
uw935 44bb48e5ab change: prepare for map changing 2024-03-02 02:01:00 +03:00
uw935 0ea95c643c map style changed && viewer change 2024-02-28 00:47:39 +03:00
9 changed files with 180 additions and 356 deletions
+13 -33
View File
@@ -1,43 +1,23 @@
name: Create and publish a Docker image name: deployment on server
on: on:
push: push:
branches: branches: [ master ]
- master
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs: jobs:
build-and-push-image: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps: steps:
- name: Checkout repository - uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Log in to the Container registry - name: update bot
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 uses: appleboy/ssh-action@v1.0.3
with: with:
registry: ${{ env.REGISTRY }} host: ${{ secrets.SERVER_HOST }}
username: ${{ github.actor }} username: ${{ secrets.SERVER_USER }}
password: ${{ secrets.GITHUB_TOKEN }} key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
- name: Extract metadata (tags, labels) for Docker script: |
id: meta cd /home/deploy/vatsimfox && git pull origin master --force && cd /home/deploy/vatsimfox/src
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 docker compose up -d --build
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 -1
View File
@@ -9,4 +9,4 @@ RUN pip install -r requirements.txt
COPY . /build/ COPY . /build/
CMD ["python3", "app.py"] CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0"]
+25 -100
View File
@@ -1,116 +1,41 @@
import uvicorn
import requests import requests
from flask import Flask, render_template, abort, request, jsonify
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/" VATSIM_API_URL = "https://api.vatsim.net/v2/"
app = FastAPI(
title="VATSIM Fox", @app.route("/")
docs_url=None, def index_page():
redoc_url=None, return render_template("index.html")
openapi_url=None,
redirect_slashes=True
)
templates = Jinja2Templates(directory="templates")
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/") @app.errorhandler(404)
async def get_index_page(request: Request): def errorhandler_page(_):
""" return render_template("4O4.html"), 404
Index page
:param request: FastAPI request
:return: HTML template
"""
return templates.TemplateResponse(
name="index.html",
context={
"request": request,
}
)
@app.get("/favicon.ico", include_in_schema=False) @app.route("/api/request")
async def get_favicon(): def request_handler():
""" if request_string := request.args.get("request_string"):
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":
raise HTTPException(404)
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: try:
return requests.get(f"{VATSIM_API_URL}{request_string}").json() return requests.get(f"{VATSIM_API_URL}{request_string}").json()
except requests.exceptions.JSONDecodeError: except requests.exceptions.JSONDecodeError:
return {"result": "error", "message": "Something went wrong. Report that error please"} return jsonify({"result": "error", "message": "Something went wrong. Report that error please"})
return {"result": "not found", "message": "Not found"} return jsonify({"result": "not found", "message": "Not found"})
@app.route("/<int:cid>/")
def viewer_page(cid):
user = requests.get(f"{VATSIM_API_URL}members/{cid}").json()
if "detail" in user and user["detail"] == "Not Found":
return abort(404)
return render_template("viewer.html", user=user)
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run(app=app, host="0.0.0.0", port=80) app.run(host="0.0.0.0", port=80)
+1 -3
View File
@@ -1,5 +1,3 @@
fastapi==0.110.2 Flask==2.2.5
requests==2.31.0 requests==2.31.0
uvicorn==0.29.0
flake8==5.0.4 flake8==5.0.4
Jinja2==3.1.3
+5 -2
View File
@@ -7,6 +7,9 @@
background-color: #dee2e6 !important; background-color: #dee2e6 !important;
} }
#notification_wrapper { #fox_map {
display: none; box-sizing: border-box;
position: absolute;
width: 100%;
height: calc(100% - 5rem);
} }
+56 -59
View File
@@ -1,87 +1,84 @@
// Fetching user's ATC session informations from API // // Fetching user's ATC session informations from API
let userSession = {}; // let userSession = {};
fetch(ATC_API_URL, {}).then(response => response.json()).then(result => { // fetch(ATC_API_URL, {}).then(response => response.json()).then(result => {
if (result && (result["result"] == "not found" || result["result"] == "error")) { // if (result && result["result"] == "not found")
showAlert("Error while getting ATC information", result["message"]); // return
return;
}
for (let session of result["items"]) { // for (let session of result["items"]) {
// Separate each session by it's connection ID // // Separate each session by it's connection ID
let session_id = session["connection_id"]["id"]; // let session_id = session["connection_id"]["id"];
userSession[session_id] = session; // userSession[session_id] = session;
// Long line. Not in the whole file, but still bad // // Long line. Not in the whole file, but still bad
// Open to any suggestions about fix it // // Open to any suggestions about fix it
let sessionElement = "<div id='" + session_id + "' class='fox_button fox_atc mb-2 p-2 text-center text-bg-white border border-1 rounded-3'>" + session["connection_id"]["callsign"] + "</div>"; // let sessionElement = "<div id='" + session_id + "' class='fox_button fox_atc mb-2 p-2 text-center text-bg-white border border-1 rounded-3'>" + session["connection_id"]["callsign"] + "</div>";
$("#fox_atc").append(sessionElement); // $("#fox_atc").append(sessionElement);
} // }
// Click event handler to left menu button // // Click event handler to left menu button
$(".fox_atc").click(function() { // $(".fox_atc").click(function() {
if ($(this).hasClass("fox_atc_active")) return; // if ($(this).hasClass("fox_atc_active")) return;
$(".fox_atc_active").removeClass("fox_atc_active"); // $(".fox_atc_active").removeClass("fox_atc_active");
$(this).addClass("fox_atc_active"); // $(this).addClass("fox_atc_active");
showSession(userSession[$(this).attr("id")]); // showSession(userSession[$(this).attr("id")]);
}); // });
showSession(result["items"][0]); // showSession(result["items"][0]);
}); // });
// Fetching user's flightplans from VATSIM API // // Fetching user's flightplans from VATSIM API
let userFlightplans = {}; // let userFlightplans = {};
fetch(FLIGHTPLANS_API_URL, {}).then(response => response.json()).then(result => { // fetch(FLIGHTPLANS_API_URL, {}).then(response => response.json()).then(result => {
if (result && result["result"] == "not found") // if (result && result["result"] == "not found")
return // return
for (let flightplan of result) { // for (let flightplan of result) {
// Separate each flight by it's connection ID // // Separate each flight by it's connection ID
// Like it was with ATC session's // // Like it was with ATC session's
let flightplan_id = flightplan["id"] // let flightplan_id = flightplan["id"]
userFlightplans[flightplan_id] = flightplan; // userFlightplans[flightplan_id] = flightplan;
userFlightplans[flightplan_id]["not_filed"] = flightplan["connection_id"] === 0 // userFlightplans[flightplan_id]["not_filed"] = flightplan["connection_id"] === 0
// Long line again // // Long line again
let flightplanElement = "<div id="+ flightplan_id +" class='fox_button mb-2 p-2 text-center text-bg-white border border-1 rounded-3'>" + flightplan["callsign"] + "</div>"; // let flightplanElement = "<div id="+ flightplan_id +" class='fox_button mb-2 p-2 text-center text-bg-white border border-1 rounded-3'>" + flightplan["callsign"] + "</div>";
$("#fox_flightplans").append(flightplanElement); // $("#fox_flightplans").append(flightplanElement);
} // }
$(".fox_button").click(function() { // $(".fox_button").click(function() {
if ($(this).hasClass("fox_active")) return; // if ($(this).hasClass("fox_active")) return;
$(".fox_active").removeClass("fox_active"); // $(".fox_active").removeClass("fox_active");
$(this).addClass("fox_active"); // $(this).addClass("fox_active");
showFlight(userFlightplans[$(this).attr("id")]); // showFlight(userFlightplans[$(this).attr("id")]);
}); // });
showFlight(result[0]); // showFlight(result[0]);
}); // });
// Fetching airports information // // Fetching airports information
// Get it from localhost // // Get it from localhost
// Becase I think it would be much faster than getting it from VATSIM API // // Becase I think it would be much faster than getting it from VATSIM API
let airports = {}; // let airports = {};
$.getJSON("/static/data/airports.json", function(json) { // $.getJSON("/static/data/airports.json", function(json) {
airports = json; // airports = json;
} // }
); // );
// Map creation // Map creation
const MAP_ELEMENT = L.map("fox_map"); const MAP_ELEMENT = L.map("fox_map");
const MAP_DEFAULT_ZOOM_VIEW = 2;
const MAP_DEFAULT_TARGET = L.latLng("0", "0"); const MAP_DEFAULT_TARGET = L.latLng("0", "0");
const MAP_DEFAULT_ZOOM_VIEW = 2;
let line = null; let line = null;
MAP_ELEMENT.setView(MAP_DEFAULT_TARGET, MAP_DEFAULT_ZOOM_VIEW); MAP_ELEMENT.setView(MAP_DEFAULT_TARGET, MAP_DEFAULT_ZOOM_VIEW);
L.tileLayer("https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{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', attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors | VATSIM Fox by <a href="https://uw935.t.me/" style="text-decoration: underline; cursor: pointer;">uw935</a> (1606255)',
subdomains: "abcd", subdomains: "abcd",
maxZoom: 20, maxZoom: 20,
minZoom: 2 minZoom: 2
+18 -3
View File
@@ -10,9 +10,8 @@
<meta content="yes" name="apple-touch-fullscreen" /> <meta content="yes" name="apple-touch-fullscreen" />
<meta name="apple-mobile-web-app-status-bar-style" content="#FA8232"> <meta name="apple-mobile-web-app-status-bar-style" content="#FA8232">
<meta name="format-detection" content="telephone=no"> <meta name="format-detection" content="telephone=no">
<link rel="icon" href="{{ url_for('static', path='icon.ico') }}" type="image/x-icon"> <link rel="icon" href="{{ url_for('static', filename='icon.ico') }}" type="image/x-icon">
<title>{% block title %}{% endblock %}</title> <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"> <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 %} {% block header_css %}{% endblock %}
</head> </head>
@@ -28,12 +27,26 @@
color: var(--main-rgb); color: var(--main-rgb);
} }
.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 { :root {
--main-rgb: #FA8232; --main-rgb: #FA8232;
--bs-primary-rgb: 250, 130, 50 --bs-primary-rgb: 250, 130, 50
--main-rgb: #FA8232;
--bs-primary-rgb: 250, 130, 50;
} }
</style> </style>
<body> <body>
{% block above_content %}{% endblock %}
<main> <main>
<header class="border-bottom"> <header class="border-bottom">
<div class="container py-4"> <div class="container py-4">
@@ -50,7 +63,9 @@
<footer class="text-muted border-top"> <footer class="text-muted border-top">
<div class="container py-4"> <div class="container py-4">
created by created by
<a href="https://uw935.com/" style="text-decoration: underline; cursor: pointer;">uw935</a> (1606255) <a href="https://uw935.t.me/" style="text-decoration: underline; cursor: pointer;">
uw935
</a> (1606255)
</div> </div>
</footer> </footer>
</main> </main>
+9 -9
View File
@@ -2,15 +2,15 @@
{% block title %}VATSIM Fox — view detailed profile statistic{% endblock %} {% block title %}VATSIM Fox — view detailed profile statistic{% endblock %}
{% block content %} {% block content %}
<div class="p-5 mb-4 bg-light rounded-3"> <div class="p-5 mb-4 bg-light rounded-3">
<div class="container-fluid py-5"> <div class="container-fluid py-5">
<h1 class="display-5 fw-bold">Enter your VATSIM CID</h1> <h1 class="display-5 fw-bold">Enter your VATSIM CID</h1>
<p class="col-md-8 fs-4">See your detailed profile statistic</p> <p class="col-md-8 fs-4">See your detailed profile statistic</p>
<div class="d-flex" role="search"> <div class="d-flex" role="search">
<input class="fox_input form-control me-2" type="number" placeholder="100000" aria-label="CID"> <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> <button class="fox_submit btn btn-outline-primary" type="submit">Search</button>
</div> </div>
<p class="fox_message text-primary col-md-8 fs-4"></p> <p class="fox_message text-primary col-md-8 fs-4"></p>
</div> </div>
</div> </div>
<div class="mb-4 row align-items-md-stretch"> <div class="mb-4 row align-items-md-stretch">
+52 -146
View File
@@ -1,150 +1,56 @@
{% extends "/include/template.html" %} <!DOCTYPE html>
{% block title %}User {{user["id"]}} statistic — VATSIM Fox{% endblock %} <html lang="en">
{% block header_css %} <head>
<link rel="stylesheet" href="{{ url_for('static', path='viewer.css') }}"> <meta charset="UTF-8">
<link href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" rel="stylesheet"/> <meta name="author" content="uw935">
{% endblock %} <meta name="description" content="Check detailed stastic of VATSIM user {{user['id']}}">
{% block content %} <meta name="keywords" content="VATSIM FOX, vatsim id, {{user['id']}}, fox, vatsim, vatsim statistic, statistic, vatsim fox, flight simulator">
<div class="mb-5"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<div id="notification_wrapper"> <meta name="apple-mobile-web-app-capable" content="yes">
<div class="mb-4 alert alert-warning" role="alert"> <meta content="yes" name="apple-touch-fullscreen" />
<h4 id="notification_title"></h4> <meta name="apple-mobile-web-app-status-bar-style" content="#FA8232">
<span id="notification_message"></span> <meta name="format-detection" content="telephone=no">
<link rel="icon" href="{{ url_for('static', filename='icon.ico') }}" type="image/x-icon">
<title>User {{user["id"]}} statistic — VATSIM Fox</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<link rel="stylesheet" href="{{ url_for('static', filename='viewer.css') }}">
<link href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" rel="stylesheet"/>
</head>
<style>
.btn-outline-primary:hover {
color: white;
background-color: var(--main-rgb);
border-color: var(--main-rgb);
}
.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 class="border-bottom">
<div class="container py-4">
<header class="d-flex">
<a href="/" class="d-flex align-items-center text-dark text-decoration-none">
<span class="fs-4 me-4">🦊 VATSIM <span class="text-primary">Fox: </span></span>
<span class="fs-4">user {{user["id"]}}</span>
</a>
</header>
</div> </div>
</main>
<div id="fox_map">
</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>
<div class="mb-4 row align-items-md-stretch">
<h3 class="mb-3">Last 50 flightplans</h3>
<div id="fox_flightplans" class="mb-4 col-md-3"></div>
<div class="col-md" style="height: 500px; overflow-y: scroll;"> <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<div id="fox_map" class="mb-4 h-50 bg-dark rounded-3"></div> <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
<div class="h-auto p-5 bg-light border rounded-3"> <script type="text/javascript" src="{{ url_for('static', filename='viewer.js') }}"></script>
<h2><span class="fp_callsign">Flight</span> — from <span class="fp_depa_city"></span> to <span class="fp_arra_city"></span></h2> </body>
<span> </html>
<b class="fp_is_filed"></b>
</span>
<br>
<span>
<b>Aircraft type:</b>
<span class="fp_aicraft"></span>
</span>
<br>
<span>
<b>Enroute time:</b>
<span class="fp_entime"></span>
</span>
<br>
<span>
<b>Departure airport:</b>
<span class="fp_depa"></span>
</span>
<br>
<span>
<b>Arrival airport:</b>
<span class="fp_arra"></span>
</span>
<br>
<span>
<b>Alternative airport:</b>
<span class="fp_altna"></span>
</span>
<br>
<span>
<b>Flight type:</b>
<span class="fp_type"></span>
</span>
<br>
<span>
<b>Cruise speed:</b>
<span class="fp_speed"></span> knots
</span>
<br>
<span>
<b>Flight altitude:</b>
<span class="fp_alt"></span> feet
</span>
<br>
<span>
<b>Route:</b>
<span class="fp_route"></span>
</span>
<br>
<span>
<b>Remarks:</b>
<span class="fp_remarks"></span>
</span>
<br>
<span>
<b>Squawk:</b>
<span class="fp_squawk"></span>
</span>
<br>
<span>
<b>Fligthplan filed at:</b>
<span class="fp_filed"></span>
</span>
<br>
</div>
</div>
</div>
<div class="mb-4 row align-items-md-stretch">
<h3 class="mb-3">Last 100 ATC shifts</h3>
<div id="fox_atc" class="mb-4 col-md-3"></div>
<div class="col-md" style="height: 321px;">
<div class="h-auto p-5 bg-light border rounded-3">
<h2 class="atc_callsign">Callsign</h2>
<span>
<b>Start time:</b>
<span class="atc_start_time"></span>
</span>
<br>
<span>
<b>End time:</b>
<span class="atc_end_time"></span>
</span>
<br>
<span>
<b>Aircraft tracked:</b>
<span class="atc_trackedair"></span>
</span>
<br>
<span>
<b>Aircraft seen:</b>
<span class="atc_seenair"></span>
</span>
<br>
<span>
<b>Squawk assigned:</b>
<span class="atc_squawks"></span>
</span>
<br>
<span>
<b>Aircraft's transferred:</b>
<span class="atc_transfers"></span>
</span>
<br>
<span>
<b>Aircraft's received:</b>
<span class="atc_received"></span>
</span>
</div>
</div>
</div>
{% endblock %}
{% block javascript_code %}
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
<script type="text/javascript">
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("{{user['reg_date']}}".replace("T", " "));
</script>
<script type="text/javascript" src="{{ url_for('static', path='viewer.js') }}"></script>
{% endblock %}