13 Commits
11 changed files with 229 additions and 260 deletions
+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
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.7.10
FROM python:3.8.18
WORKDIR /build/
+8 -7
View File
@@ -1,5 +1,5 @@
from flask import Flask, render_template, abort, request, jsonify
import requests
from flask import Flask, render_template, abort, request, jsonify
app = Flask(__name__)
@@ -15,15 +15,16 @@ def index_page():
def errorhandler_page(_):
return render_template("4O4.html"), 404
# Custom wrapper to fetch VATSIM data
@app.route("/api/request")
def request_handler():
request_string = request.args.get("request_string")
if request_string:
if request_string := request.args.get("request_string"):
try:
return requests.get(f"{VATSIM_API_URL}{request_string}").json()
except requests.exceptions.JSONDecodeError:
return jsonify({"result": "error", "message": "Something went wrong. Report that error please"})
return jsonify({"result": "not found"})
return jsonify({"result": "not found", "message": "Not found"})
@app.route("/<int:cid>/")
@@ -33,7 +34,7 @@ def viewer_page(cid):
if "detail" in user and user["detail"] == "Not Found":
return abort(404)
return render_template("viewer.html", data=(user, ))
return render_template("viewer.html", user=user)
if __name__ == "__main__":
+7
View File
@@ -6,3 +6,10 @@
.fox_active, .fox_atc_active {
background-color: #dee2e6 !important;
}
#fox_map {
box-sizing: border-box;
position: absolute;
width: 100%;
height: calc(100% - 5rem);
}
+64 -59
View File
@@ -1,85 +1,84 @@
// Fetching user's ATC session informations from API
let userSession = {};
// // Fetching user's ATC session informations from API
// let userSession = {};
fetch(ATC_API_URL, {}).then(response => response.json()).then(result => {
if (result && result["result"] == "not found")
return
// fetch(ATC_API_URL, {}).then(response => response.json()).then(result => {
// if (result && result["result"] == "not found")
// return
for (let session of result["items"]) {
// Separate each session by it's connection ID
let session_id = session["connection_id"]["id"];
userSession[session_id] = session;
// for (let session of result["items"]) {
// // Separate each session by it's connection ID
// let session_id = session["connection_id"]["id"];
// userSession[session_id] = session;
// Long line. Not in the whole file, but still bad
// 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>";
$("#fox_atc").append(sessionElement);
}
// // Long line. Not in the whole file, but still bad
// // 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>";
// $("#fox_atc").append(sessionElement);
// }
// Click event handler to left menu button
$(".fox_atc").click(function() {
if ($(this).hasClass("fox_atc_active")) return;
// // Click event handler to left menu button
// $(".fox_atc").click(function() {
// if ($(this).hasClass("fox_atc_active")) return;
$(".fox_atc_active").removeClass("fox_atc_active");
$(this).addClass("fox_atc_active");
// $(".fox_atc_active").removeClass("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
let userFlightplans = {};
// // Fetching user's flightplans from VATSIM API
// let userFlightplans = {};
fetch(FLIGHTPLANS_API_URL, {}).then(response => response.json()).then(result => {
if (result && result["result"] == "not found")
return
// fetch(FLIGHTPLANS_API_URL, {}).then(response => response.json()).then(result => {
// if (result && result["result"] == "not found")
// return
for (let flightplan of result) {
// Separate each flight by it's connection ID
// Like it was with ATC session's
let flightplan_id = flightplan["id"]
userFlightplans[flightplan_id] = flightplan;
userFlightplans[flightplan_id]["not_filed"] = flightplan["connection_id"] === 0
// for (let flightplan of result) {
// // Separate each flight by it's connection ID
// // Like it was with ATC session's
// let flightplan_id = flightplan["id"]
// userFlightplans[flightplan_id] = flightplan;
// userFlightplans[flightplan_id]["not_filed"] = flightplan["connection_id"] === 0
// 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>";
$("#fox_flightplans").append(flightplanElement);
}
// // 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>";
// $("#fox_flightplans").append(flightplanElement);
// }
$(".fox_button").click(function() {
if ($(this).hasClass("fox_active")) return;
// $(".fox_button").click(function() {
// if ($(this).hasClass("fox_active")) return;
$(".fox_active").removeClass("fox_active");
$(this).addClass("fox_active");
// $(".fox_active").removeClass("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
// Get it from localhost
// Becase I think it would be much faster than getting it from VATSIM API
let airports = {};
// // Fetching airports information
// // Get it from localhost
// // Becase I think it would be much faster than getting it from VATSIM API
// let airports = {};
$.getJSON("/static/data/airports.json", function(json) {
airports = json;
}
);
// $.getJSON("/static/data/airports.json", function(json) {
// airports = 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_TARGET = L.latLng("0", "0");
const MAP_DEFAULT_ZOOM_VIEW = 2;
let line = null;
MAP_ELEMENT.setView(MAP_DEFAULT_TARGET, MAP_DEFAULT_ZOOM_VIEW);
L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
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 | VATSIM Fox by <a href="https://uw935.t.me/" style="text-decoration: underline; cursor: pointer;">uw935</a> (1606255)',
subdomains: "abcd",
maxZoom: 20,
minZoom: 2
@@ -153,3 +152,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);
}
+39 -14
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,39 +10,64 @@
<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">
<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>
.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);
}
.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
--main-rgb: #FA8232;
--bs-primary-rgb: 250, 130, 50;
}
</style>
<body>
{% block above_content %}{% endblock %}
<main>
<header class="border-bottom">
<div class="container py-4">
<header class="pb-3 mb-4 border-bottom">
<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>
</a>
</div>
</header>
<!-- <div class="mb-4 alert alert-primary" role="alert">
Notification
</div>
-->
<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>
<footer class="text-muted border-top">
<div class="container py-4">
created by
<a href="https://uw935.t.me/" 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>
+5 -3
View File
@@ -7,7 +7,7 @@
<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>
<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>
@@ -20,10 +20,12 @@
<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>
@@ -34,9 +36,9 @@
{% 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) {
if (INPUT_CID < 100000) {
$(".fox_message").text("CID should be equal or more than 100000");
return;
}
+50 -137
View File
@@ -1,143 +1,56 @@
{% extends "/include/template.html" %}
{% block title %}User {{user_cid}} statistic — VATSIM Fox{% endblock %}
{% block header_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='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>
<p class="fs-4 text-secondary">Date of registration: <span class="date_registration"></span></p>
</div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="author" content="uw935">
<meta name="description" content="Check detailed stastic of VATSIM user {{user['id']}}">
<meta name="keywords" content="VATSIM FOX, vatsim id, {{user['id']}}, fox, vatsim, vatsim statistic, statistic, vatsim fox, flight simulator">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="apple-mobile-web-app-capable" content="yes">
<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">
<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);
}
<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>
.btn-outline-primary {
border-color: var(--main-rgb);
color: var(--main-rgb);
}
<div class="col-md" style="height: 500px; overflow-y: scroll;">
<div id="fox_map" class="mb-4 h-50 bg-dark rounded-3"></div>
<div class="h-auto p-5 bg-light border rounded-3">
<h2><span class="fp_callsign">Flight</span> — from <span class="fp_depa_city"></span> to <span class="fp_arra_city"></span></h2>
<span>
<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>
: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>
<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>
<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>
<span class="fs-4">user {{user["id"]}}</span>
</a>
</header>
</div>
</main>
<div id="fox_map">
</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 = '{{data[0]["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", " "));
</script>
<script type="text/javascript" src="{{ url_for('static', filename='viewer.js') }}"></script>
{% endblock %}
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
<script type="text/javascript" src="{{ url_for('static', filename='viewer.js') }}"></script>
</body>
</html>