kopia lustrzana https://github.com/jprochazka/adsb-receiver
commit
d68c0844d2
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -2,10 +2,20 @@
|
|||
|
||||
The following is a history of the changes made to this project.
|
||||
|
||||
## v2.8.0 *(June 24th, 2024)*
|
||||
## v2.8.1 *(in development)* :hatched_chick:
|
||||
|
||||
### Installers
|
||||
|
||||
* Plane Finder Feeder now installs properly on aarch64 operating systems.
|
||||
|
||||
### Portal
|
||||
|
||||
* Fix problem where multiple instances of flights.py and maintenance.py were being launched.
|
||||
* Addressed portal map centering settings not being added to the database on fresh installs.
|
||||
* An issue was addressed allowing existing receivers to upgrade to phoenix properly.
|
||||
|
||||
## v2.8.0 *(June 24th, 2024)* :hatching_chick:
|
||||
|
||||
* The dump1090-fa and piaware_builder install process has been updated to support the latest versions.
|
||||
* The dump978 script has been rewritten in order to use FlightAwares version of dump978.
|
||||
* The Beast Splitter script has been updated to work with the current version of Beast Splitter.
|
||||
|
@ -22,9 +32,6 @@ The following is a history of the changes made to this project.
|
|||
* Removed the option to install AboveTustin.
|
||||
* Removed the option to install ADSBHub support due to the fact it was incomplete.
|
||||
* Removed incomplete RTL-SDR OGN setup scripting.
|
||||
|
||||
### Portal
|
||||
|
||||
* The portal install scripts have been updated to work on current operation systems.
|
||||
* All portal related Python scripts have been updated to work with Python 3.
|
||||
* TinyMCE has been replace by CKeditor in the portal.
|
||||
|
|
37
CREDITS.md
37
CREDITS.md
|
@ -7,26 +7,34 @@ directly. Their time and effort is greatly appreciated by myself as well as many
|
|||
|
||||
**Direct Contributors**
|
||||
|
||||
* @adamkaplan
|
||||
* @atbPy
|
||||
* @charlie-foxtrot
|
||||
* @dschaper
|
||||
* @g0wfv
|
||||
* @mgunther68
|
||||
* @jdwall
|
||||
* @leearmstrong
|
||||
* @mgunther68
|
||||
* @Romeo-Golf
|
||||
* @target-drone
|
||||
* @vitaliy-sk
|
||||
* @wiedehopf
|
||||
* @vitaliy-sk
|
||||
* @target-drone
|
||||
* @dschaper
|
||||
* @adamkaplan
|
||||
* @charlie-foxtrot
|
||||
* @jsloyer
|
||||
* @kylegordon
|
||||
* @BudBundi
|
||||
* @kuchel77
|
||||
* @atbPy
|
||||
* @g0wfv
|
||||
* @ttsak
|
||||
* @mkrzysztofowicz
|
||||
* @espinielli
|
||||
* @bitpixdigital
|
||||
* @Romeo-Golf
|
||||
* @g0wfv
|
||||
|
||||
I would like to thank the members and contributors to the following forums for all the time and effort
|
||||
they have put in in order to create a useful and informative place to discuss aircraft tracking.
|
||||
|
||||
* The ADS-B Receiver Project Forums: https://www.adsbreceiver.net
|
||||
* The FlightAware Forums: http://discussions.flightaware.com
|
||||
* The PlaneFinder Forums: http://forum.planefinder.net
|
||||
* The ADS-B Exchange Forums: http://www.adsbexchange.com/forums/
|
||||
* The ADS-B Exchange Forums: https://adsbx.discourse.group
|
||||
|
||||
The performance graphs were created thanks in part to the great contributions made by members of both
|
||||
the FlightAware and PlaneFinder communities. Through their examples and help posted to the following
|
||||
|
@ -37,15 +45,12 @@ threads myself and a couple contributors were able to add these very informative
|
|||
|
||||
## Third Party Software
|
||||
|
||||
Thanks to the people over at JetBrains for the complementary licenses for PhpStorm and PyCharms which
|
||||
is being used to develop the portal portion of this project. https://www.jetbrains.com
|
||||
|
||||
I would also like to thank the developers and contributors to the following projects. Without their
|
||||
I would like to thank the developers and contributors to the following projects. Without their
|
||||
hard work and dedication this project would not have been possible.
|
||||
|
||||
* Dump1090 (fa): https://github.com/flightaware/dump1090
|
||||
* Dump978 (fa): https://github.com/flightaware/dump978
|
||||
* FlightAware's PiAware: http://flightaware.com
|
||||
* FlightAware's PiAware: https://github.com/flightaware/piaware
|
||||
* Plane Finder ADS-B Client: https://planefinder.net
|
||||
* Flightradar24 Client: https://www.flightradar24.com
|
||||
* Dump1090-Tools: https://github.com/mutability/dump1090-tools
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2017 Joseph A. Prochazka
|
||||
Copyright (c) 2015-2024 Joseph A. Prochazka
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -65,6 +65,15 @@ fi
|
|||
echo -e "\e[95m Installing packages needed to fulfill dependencies for PlaneFinder ADS-B Client...\e[97m"
|
||||
echo -e ""
|
||||
CheckPackage wget
|
||||
|
||||
# Some architectures require additional packages.
|
||||
case "${CPU_ARCHITECTURE}" in
|
||||
"aarch64")
|
||||
echo -e "\e[94m Adding support for the armhf architecture...\e[97m"
|
||||
sudo dpkg --add-architecture armhf
|
||||
CheckPackage libc6:armhf
|
||||
;;
|
||||
esac
|
||||
echo ""
|
||||
|
||||
### DETERMINE WHICH PACACKAGE TO INSTALL
|
||||
|
|
|
@ -382,20 +382,11 @@ url.redirect += (
|
|||
"^/dump1090$" => "/dump1090/"
|
||||
)
|
||||
# Add CORS header
|
||||
server.modules += ( "mod_setenv" )
|
||||
\$HTTP["url"] =~ "^/dump1090/data/.*\.json$" {
|
||||
setenv.add-response-header = ( "Access-Control-Allow-Origin" => "*" )
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
sudo tee -a /etc/lighttpd/conf-available/87-adsb-portal.conf > /dev/null <<EOF
|
||||
# Block all access to the data directory accept for local requests.
|
||||
\$HTTP["remoteip"] !~ "127.0.0.1" {
|
||||
\$HTTP["url"] =~ "^/data/" {
|
||||
url.access-deny = ( "" )
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
if [[ ! -L "/etc/lighttpd/conf-enabled/87-adsb-portal.conf" ]] ; then
|
||||
echo -e "\e[94m Enabling the Lighttpd portal configuration file...\e[97m"
|
||||
|
|
|
@ -33,8 +33,8 @@
|
|||
|
||||
## VARIABLES
|
||||
|
||||
PORTAL_PYTHON_DIRECTORY="${RECEIVER_BUILD_DIRECTORY}/python"
|
||||
PYTHON_PATH=`which python`
|
||||
PORTAL_PYTHON_DIRECTORY="${RECEIVER_BUILD_DIRECTORY}/portal/python"
|
||||
PYTHON_PATH=`which python3`
|
||||
|
||||
## SETUP FLIGHT LOGGING
|
||||
|
||||
|
@ -45,6 +45,6 @@ echo -e ""
|
|||
# Create the cron jobs responsible for logging and maintenance.
|
||||
echo -e "\e[94m Creating the maintenance maintenance script...\e[97m"
|
||||
sudo tee /etc/cron.d/adsb-receiver-flight-logging > /dev/null <<EOF
|
||||
* * * * * root ${PYTHON_PATH} ${PORTAL_PYTHON_DIRECTORY}/flights.py; sleep 30; root ${PYTHON_PATH} ${PORTAL_PYTHON_DIRECTORY}/flights.py
|
||||
* * * * * root ${PYTHON_PATH} ${PORTAL_PYTHON_DIRECTORY}/flights.py
|
||||
30 * * * * root ${PYTHON_PATH} ${PORTAL_PYTHON_DIRECTORY}/maintenance.py
|
||||
EOF
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
## SOFTWARE VERSIONS
|
||||
|
||||
# The ADS-B Receiver Project
|
||||
PROJECT_VERSION="2.8.0"
|
||||
PROJECT_VERSION="2.8.1"
|
||||
|
||||
# RTL-SDR OGN
|
||||
RTLSDROGN_VERSION="0.2.5"
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
$dbh = NULL;
|
||||
foreach ($savedFlights as $flight) {
|
||||
// Remove flight if not in list.
|
||||
if (!in_array($flight, $notificationArray)) {
|
||||
if (!in_array($flight, $notificationArray ?? [])) {
|
||||
$dbh = $common->pdoOpen();
|
||||
$sql = "DELETE FROM ".$settings::db_prefix."flightNotifications WHERE flight = :flight";
|
||||
$sth = $dbh->prepare($sql);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// //
|
||||
// The MIT License (MIT) //
|
||||
// //
|
||||
// Copyright (c) 2015-2019 Joseph A. Prochazka //
|
||||
// Copyright (c) 2015-2024 Joseph A. Prochazka //
|
||||
// //
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy //
|
||||
// of this software and associated documentation files (the "Software"), to deal //
|
||||
|
@ -37,7 +37,7 @@
|
|||
|
||||
// Check if the portal is installed or needs upgraded.
|
||||
|
||||
$thisVersion = "2.8.0";
|
||||
$thisVersion = "2.8.1";
|
||||
|
||||
if (!file_exists($_SERVER['DOCUMENT_ROOT']."/classes/settings.class.php")) {
|
||||
header ("Location: /install/install.php");
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// //
|
||||
// The MIT License (MIT) //
|
||||
// //
|
||||
// Copyright (c) 2015-2019 Joseph A. Prochazka //
|
||||
// Copyright (c) 2015-2024 Joseph A. Prochazka //
|
||||
// //
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy //
|
||||
// of this software and associated documentation files (the "Software"), to deal //
|
||||
|
@ -29,7 +29,7 @@
|
|||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The most current stable release.
|
||||
$thisVersion = "2.8.0";
|
||||
$thisVersion = "2.8.1";
|
||||
|
||||
// Begin the upgrade process if this release is newer than what is installed.
|
||||
if (file_exists($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR."classes".DIRECTORY_SEPARATOR."settings.class.php")) {
|
||||
|
@ -398,11 +398,11 @@ EOF;
|
|||
$sth = $dbh->prepare($settingsSql);
|
||||
$sth->execute();
|
||||
$sth = NULL;
|
||||
|
||||
|
||||
$sth = $dbh->prepare($aircraftIndexSql);
|
||||
$sth->execute();
|
||||
$sth = NULL;
|
||||
|
||||
|
||||
$sth = $dbh->prepare($positionsIndexSql);
|
||||
$sth->execute();
|
||||
$sth = NULL;
|
||||
|
@ -444,6 +444,8 @@ EOF;
|
|||
$common->addSetting('googleMapsApiKey', '');
|
||||
$common->addSetting("hideNavbarAndFooter", FALSE);
|
||||
$common->addSetting("purgeAircraft", FALSE);
|
||||
$common->addSetting("advancedMapCenterLatitude", "41.3683798");
|
||||
$common->addSetting("advancedMapCenterLongitude", "-82.1076486");
|
||||
|
||||
if ($_POST['driver'] == "xml")
|
||||
$common->addSetting('enableFlights', FALSE);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// //
|
||||
// The MIT License (MIT) //
|
||||
// //
|
||||
// Copyright (c) 2015-2017 Joseph A. Prochazka //
|
||||
// Copyright (c) 2015-2024 Joseph A. Prochazka //
|
||||
// //
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy //
|
||||
// of this software and associated documentation files (the "Software"), to deal //
|
||||
|
@ -48,7 +48,7 @@
|
|||
$settings = new settings();
|
||||
|
||||
try {
|
||||
// Add portal navigation and footer autohide option.
|
||||
// Add map centering longitude and latitude.
|
||||
$common->addSetting("advancedMapCenterLatitude", "41.3683798");
|
||||
$common->addSetting("advancedMapCenterLongitude", "-82.1076486");
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// ADS-B RECEIVER PORTAL //
|
||||
// =============================================================================== //
|
||||
// Copyright and Licensing Information: //
|
||||
// //
|
||||
// The MIT License (MIT) //
|
||||
// //
|
||||
// Copyright (c) 2015-2024 Joseph A. Prochazka //
|
||||
// //
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy //
|
||||
// of this software and associated documentation files (the "Software"), to deal //
|
||||
// in the Software without restriction, including without limitation the rights //
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //
|
||||
// copies of the Software, and to permit persons to whom the Software is //
|
||||
// furnished to do so, subject to the following conditions: //
|
||||
// //
|
||||
// The above copyright notice and this permission notice shall be included in all //
|
||||
// copies or substantial portions of the Software. //
|
||||
// //
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //
|
||||
// SOFTWARE. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////
|
||||
// UPGRADE TO V2.8.0
|
||||
///////////////////////
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Updates the version setting to 2.8.0.
|
||||
// --------------------------------------------------------
|
||||
|
||||
$results = upgrade();
|
||||
exit(json_encode($results));
|
||||
|
||||
function upgrade() {
|
||||
require_once($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR."classes".DIRECTORY_SEPARATOR."common.class.php");
|
||||
require_once($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR."classes".DIRECTORY_SEPARATOR."settings.class.php");
|
||||
|
||||
$common = new common();
|
||||
$settings = new settings();
|
||||
|
||||
try {
|
||||
|
||||
// Update the version and patch settings..
|
||||
$common->updateSetting("version", "2.8.0");
|
||||
$common->updateSetting("patch", "");
|
||||
|
||||
// The upgrade process completed successfully.
|
||||
$results['success'] = TRUE;
|
||||
$results['message'] = "Upgrade to v2.8.0 successful.";
|
||||
return $results;
|
||||
|
||||
} catch(Exception $e) {
|
||||
// Something went wrong during this upgrade process.
|
||||
$results['success'] = FALSE;
|
||||
$results['message'] = $e->getMessage();
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// ADS-B RECEIVER PORTAL //
|
||||
// =============================================================================== //
|
||||
// Copyright and Licensing Information: //
|
||||
// //
|
||||
// The MIT License (MIT) //
|
||||
// //
|
||||
// Copyright (c) 2015-2024 Joseph A. Prochazka //
|
||||
// //
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy //
|
||||
// of this software and associated documentation files (the "Software"), to deal //
|
||||
// in the Software without restriction, including without limitation the rights //
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //
|
||||
// copies of the Software, and to permit persons to whom the Software is //
|
||||
// furnished to do so, subject to the following conditions: //
|
||||
// //
|
||||
// The above copyright notice and this permission notice shall be included in all //
|
||||
// copies or substantial portions of the Software. //
|
||||
// //
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //
|
||||
// SOFTWARE. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////
|
||||
// UPGRADE TO V2.8.1
|
||||
///////////////////////
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Updates the version setting to 2.8.1.
|
||||
// --------------------------------------------------------
|
||||
|
||||
$results = upgrade();
|
||||
exit(json_encode($results));
|
||||
|
||||
function upgrade() {
|
||||
require_once($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR."classes".DIRECTORY_SEPARATOR."common.class.php");
|
||||
require_once($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR."classes".DIRECTORY_SEPARATOR."settings.class.php");
|
||||
|
||||
$common = new common();
|
||||
$settings = new settings();
|
||||
|
||||
try {
|
||||
|
||||
// Update the version and patch settings..
|
||||
$common->updateSetting("version", "2.8.1");
|
||||
$common->updateSetting("patch", "");
|
||||
|
||||
// The upgrade process completed successfully.
|
||||
$results['success'] = TRUE;
|
||||
$results['message'] = "Upgrade to v2.8.1 successful.";
|
||||
return $results;
|
||||
|
||||
} catch(Exception $e) {
|
||||
// Something went wrong during this upgrade process.
|
||||
$results['success'] = FALSE;
|
||||
$results['message'] = $e->getMessage();
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
$common = new common();
|
||||
|
||||
// The most current stable release.
|
||||
$thisVersion = "2.8.0";
|
||||
$thisVersion = "2.8.1";
|
||||
|
||||
// Begin the upgrade process if this release is newer than what is installed.
|
||||
if ($common->getSetting("version") == $thisVersion) {
|
||||
|
@ -178,7 +178,7 @@
|
|||
}
|
||||
|
||||
// UPGRADE TO V2.8.0
|
||||
if ($common->getSetting("version") == "2.8.0" && $success) {
|
||||
if ($common->getSetting("version") == "2.7.2" && $success) {
|
||||
$json = file_get_contents("http://localhost/install/upgrade-v2.8.0.php");
|
||||
$results = json_decode($json, TRUE);
|
||||
$success = $results['success'];
|
||||
|
@ -186,6 +186,15 @@
|
|||
$version = "2.8.0";
|
||||
}
|
||||
|
||||
// UPGRADE TO V2.8.1
|
||||
if ($common->getSetting("version") == "2.8.0" && $success) {
|
||||
$json = file_get_contents("http://localhost/install/upgrade-v2.8.1.php");
|
||||
$results = json_decode($json, TRUE);
|
||||
$success = $results['success'];
|
||||
$message = $results['message'];
|
||||
$version = "2.8.1";
|
||||
}
|
||||
|
||||
require_once($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR."admin".DIRECTORY_SEPARATOR."includes".DIRECTORY_SEPARATOR."header.inc.php");
|
||||
|
||||
// Display the instalation wizard.
|
||||
|
|
|
@ -36,15 +36,29 @@
|
|||
# 3) Update the last time the flight was seen.
|
||||
|
||||
import datetime
|
||||
import fcntl
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
import time
|
||||
|
||||
from urllib.request import urlopen
|
||||
|
||||
def log(string):
|
||||
#print(string) # uncomment to enable debug logging
|
||||
return
|
||||
|
||||
# Do not allow another instance of the script to run.
|
||||
lock_file = open('/tmp/flights.py.lock','w')
|
||||
|
||||
try:
|
||||
fcntl.flock(lock_file, fcntl.LOCK_EX|fcntl.LOCK_NB)
|
||||
except (IOError, OSError):
|
||||
log('another instance already running')
|
||||
quit()
|
||||
|
||||
lock_file.write('%d\n'%os.getpid())
|
||||
lock_file.flush()
|
||||
|
||||
# Read the configuration file.
|
||||
with open(os.path.dirname(os.path.realpath(__file__)) + '/config.json') as config_file:
|
||||
config = json.load(config_file)
|
||||
|
@ -125,7 +139,7 @@ class FlightsProcessor(object):
|
|||
log("\tFound Aircraft ID: " + str(aircraft_id))
|
||||
|
||||
# Check that a flight is tied to this track.
|
||||
if aircraft.has_key('flight'):
|
||||
if 'flight' in aircraft:
|
||||
self.processFlight(aircraft_id, aircraft)
|
||||
|
||||
def processFlight(self, aircraft_id, aircraft):
|
||||
|
@ -166,7 +180,7 @@ class FlightsProcessor(object):
|
|||
|
||||
if row == None or row[0] != aircraft["messages"]:
|
||||
# Add this position to the database.
|
||||
if aircraft.has_key('squawk'):
|
||||
if 'squawk' in aircraft:
|
||||
params = (flight_id, self.time_now, aircraft["messages"], aircraft["squawk"],
|
||||
aircraft["lat"], aircraft["lon"], aircraft["track"],
|
||||
aircraft["nav_altitude"], aircraft["geom_rate"], aircraft["gs"], aircraft_id,)
|
||||
|
|
|
@ -28,11 +28,23 @@
|
|||
# SOFTWARE. #
|
||||
#================================================================================#
|
||||
|
||||
import datetime
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
import datetime
|
||||
import time
|
||||
|
||||
# Do not allow another instance of the script to run.
|
||||
lock_file = open('/tmp/flights.py.lock','w')
|
||||
|
||||
try:
|
||||
fcntl.flock(lock_file, fcntl.LOCK_EX|fcntl.LOCK_NB)
|
||||
except (IOError, OSError):
|
||||
quit()
|
||||
|
||||
lock_file.write('%d\n'%os.getpid())
|
||||
lock_file.flush()
|
||||
|
||||
while True:
|
||||
|
||||
## Read the configuration file.
|
||||
|
|
Ładowanie…
Reference in New Issue