build script

This commit is contained in:
coulisse 2023-01-07 23:15:43 +01:00
parent 0a85e94787
commit c780bc4650
95 changed files with 4089 additions and 3617 deletions

2
.gitignore vendored
View File

@ -3,4 +3,4 @@
config.json
__init__
__pycache__
.vscode/

View File

@ -13,7 +13,7 @@
- **Release:** v2.4.1
- **Author:** Corrado Gerbaldo - [IU1BOW](https://www.qrz.com/db/IU1BOW)
- **Mail:** <corrado.gerbaldo@gmail.com>
- **Licensing:** Gpl V3.0 see ["LICENSE"](LICENSE) file.
- **Licensing:** Gpl V3.0 see [LICENSE](LICENSE) file.
- **Languages:** This application is written in Python 3.11/flask,Javascript and HTML
___
@ -233,12 +233,69 @@ This application is designed for desktop and mobile phone. It is a [PWA](https:/
### API
**Spot list**
You can retrive last spots calling "**/spotlist**"; For example [www.iu1bow.com/spotlist](https://www.iu1bow.com/spotlist)
**country of a callsign**
**Country of a callsign**
You cam retrive some informations about a callsign with **callsign**; For example [www.iu1bow.com/callsign?c=IU1BOW](https://www.iu1bow.com/callsign?c=IU1BOW)
### Development
**Directory structure**
```
/ . main application files
├── cfg . configuration files (put here your config.json with your setting)
├── docs . documentation
├── lib . python libs used for the application
├── log . application log
├── scripts . utility scripts for testing, build etc.
├── static . static files css, js, data, html, images etc.
│ ├── css .
│ │ ├── dev . development css not minifyed/uglifyed
│ │ └── rel . release css minifyed/uglifyed (do not change these files)
│ ├── data . application data (world.json)
│ ├── html .
│ │ └── dev . html templates used for build release static html (for offline)
│ │ └── rel . release static html (for offline)
│ ├── images . static images
│ │ └── icons . static icons
│ └── js .
│ ├── dev . development js not minifyed/uglifyed
│ └── rel . release js minifyed/uglifyed (do not change these files)
└── templates . html templates used by python flask for serve the web pages
```
**Application description**
The main **server** application ```webapp.py``` is in the root folder. In this application there are routing to html dynamic templates and serves also back-end API. This is whapped by ```wsgi.py``` for using with **bjoern** server.
Static files (css, js...) are in ```static``` directory: here there are subdirectories:
- ```dev``` where you can edit and modify sources
- ```rel``` here there are release files created with the building process and used in producion
**Lint**
For lint javascript I use **ESLint**. You can install with ```npm init @eslint/config```
pylint ```pip install pylint```
**Building process**
Prerequisites:
| **Component** | **Description** | **Install command** |
|---------------|----------------------------------------------------|---------------------------------|
| npm | a packet manager for javascript | depend on your os. See [official page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) |
| uglify-js | npm component used to minify and uglify javascript | ```npm install uglify-js -g``` |
| css-minify | npm component used to minify css | ```npm install css-minify -g``` |
| staticjinja | python module used to create static page starting from a html template | ```pip install staticjinja``` |
You can build the software for test (dev), or for production (release) environements.
In ```scripts``` directory launch:
- ```./build.sh -d``` for dev environment
- ```./build.sh -r``` for release environment
### Screenshots
----------

View File

@ -12,6 +12,7 @@ level=INFO
handlers=stream_handler,file_handler
[handler_stream_handler]
level=INFO
class=StreamHandler
formatter=formatter
args=(sys.stderr,)

View File

@ -1,13 +1,15 @@
### Change log
Date: 02/01/2023
Release v.2.4
Date: 07/01/2023
Release: v2.4.1
- changed dimensions of spots in world dx spost charts
- managed empty data in data providers for charts
- removed jQuery: migrated to vanilla javascript
- for spot refresh get only new spots starting from last rowid
- modified building script
- moved cty file in data directory
___
Date: 01/01/2023
Release v.2.4
Release: v2.4
- migration to python 3.11
- added descriptions to continents
- fixed issue #23: Wrong flag for Cocos (Keeling) Islands

View File

@ -1,77 +1,85 @@
#***********************************************************************************
# ***********************************************************************************
# Module used to get Announced DX Operation from NG3K website via .ICS (Calendar)
# file, parse it and return a dictionary with these events
#***********************************************************************************
__author__ = 'IU1BOW - Corrado'
# ***********************************************************************************
__author__ = "IU1BOW - Corrado"
import requests
import logging
from datetime import datetime
import tempfile
logging.basicConfig(level=logging.INFO,format='%(asctime)s [%(levelname)s]: %(message)s',datefmt='%m/%d/%Y %I:%M:%S')
#format single line
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s]: %(message)s",
datefmt="%m/%d/%Y %I:%M:%S",
)
# format single line
def format_line(prop):
prop_out=dict()
prop_out = dict()
try:
dtstart=datetime.strptime(prop['DTSTART;VALUE=DATE'], '%Y%m%d')
dtend=datetime.strptime(prop['DTEND;VALUE=DATE'], '%Y%m%d')
now=datetime.now()
if dtstart <=now and dtend>=now:
prop_out['start']=dtstart.strftime('%Y-%m-%dT%H:%M:%S%z')
prop_out['end']=dtend.strftime('%Y-%m-%dT%H:%M:%S%z')
prop_out['summary']=prop['SUMMARY'].split('(')[0].strip()
prop_out['callsign']=prop['SUMMARY'].split('(',1)[1].split(')',1)[0]
prop_out['description']=prop['DESCRIPTION'].replace('\\', '')
dtstart = datetime.strptime(prop["DTSTART;VALUE=DATE"], "%Y%m%d")
dtend = datetime.strptime(prop["DTEND;VALUE=DATE"], "%Y%m%d")
now = datetime.now()
if dtstart <= now and dtend >= now:
prop_out["start"] = dtstart.strftime("%Y-%m-%dT%H:%M:%S%z")
prop_out["end"] = dtend.strftime("%Y-%m-%dT%H:%M:%S%z")
prop_out["summary"] = prop["SUMMARY"].split("(")[0].strip()
prop_out["callsign"] = prop["SUMMARY"].split("(", 1)[1].split(")", 1)[0]
prop_out["description"] = prop["DESCRIPTION"].replace("\\", "")
except KeyError:
pass
return prop_out
#TODO: url from conf parameter
# TODO: url from conf parameter
def get_adxo_events():
url = 'http://dxcal.kj4z.com/dxcal'
line_num=0
event_num=0
url = "http://dxcal.kj4z.com/dxcal"
line_num = 0
event_num = 0
try:
logging.info('connection to: '+url)
req=requests.get(url)
events=[]
prop=dict()
prop_name=''
logging.info("connection to: " + url)
req = requests.get(url)
events = []
prop = dict()
prop_name = ""
with tempfile.TemporaryFile() as temp:
temp.write(req.content)
temp.seek(0)
lines=temp.readlines()
lines = temp.readlines()
for line_bytes in lines:
line=line_bytes.decode()
line_num+=1
current_line_array=line.strip().split(':', 1)
if current_line_array[0]=='BEGIN':
if current_line_array[1]=='VCALENDAR':
prop={}
if current_line_array[1]=='VEVENT':
event_num+=1
prop={}
line = line_bytes.decode()
line_num += 1
current_line_array = line.strip().split(":", 1)
if current_line_array[0] == "BEGIN":
if current_line_array[1] == "VCALENDAR":
prop = {}
if current_line_array[1] == "VEVENT":
event_num += 1
prop = {}
else:
if current_line_array[0]=='END':
if current_line_array[1]=='VCALENDAR':
if current_line_array[0] == "END":
if current_line_array[1] == "VCALENDAR":
pass
if current_line_array[1]=='VEVENT':
prop=format_line(prop)
if current_line_array[1] == "VEVENT":
prop = format_line(prop)
if prop:
events.append(prop)
else:
if len(current_line_array)>1:
prop_name=current_line_array[0]
prop[prop_name]=current_line_array[1]
if len(current_line_array) > 1:
prop_name = current_line_array[0]
prop[prop_name] = current_line_array[1]
else:
if len(prop_name)>0:
prop[prop_name]=prop[prop_name]+current_line_array[0]
if len(prop_name) > 0:
prop[prop_name] = (
prop[prop_name] + current_line_array[0]
)
logging.debug('number of line reads: '+str(line_num))
logging.info('number ADXO events: '+str(event_num))
logging.debug("number of line reads: " + str(line_num))
logging.info("number ADXO events: " + str(event_num))
return events
except Exception as e1:
logging.error(e1)

View File

@ -1,55 +1,59 @@
#*************************************************************************************
# *************************************************************************************
# CLI Utility used for manage configuration file
#*************************************************************************************
__author__ = 'IU1BOW - Corrado'
# *************************************************************************************
__author__ = "IU1BOW - Corrado"
import os
import os.path
from os import path
import json
configs = [('mycallsign','Callsign_______________: '),
('mysql/host','MySql host_____________: '),
('mysql/db','MySql database_________: '),
('mysql/user','MySql user_____________: '),
('mysql/passwd','MySql password_________: '),
('timer/interval','Spot page refresh(ms)__: ' ),
('plot_refresh_timer/interval','Plot page refresh(ms)__: '),
('mail','Mail address___________: '),
('mail_token','token google 2FA auth__: '),
('telnet','Telnet address_________: '),
('enable_cq_filter','Enable cq filter______: ')
]
configs = [
("mycallsign", "Callsign_______________: "),
("mysql/host", "MySql host_____________: "),
("mysql/db", "MySql database_________: "),
("mysql/user", "MySql user_____________: "),
("mysql/passwd", "MySql password_________: "),
("timer/interval", "Spot page refresh(ms)__: "),
("plot_refresh_timer/interval", "Plot page refresh(ms)__: "),
("mail", "Mail address___________: "),
("mail_token", "token google 2FA auth__: "),
("telnet", "Telnet address_________: "),
("enable_cq_filter", "Enable cq filter______: "),
]
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
HEADER = "\033[95m"
OKBLUE = "\033[94m"
OKGREEN = "\033[92m"
WARNING = "\033[93m"
FAIL = "\033[91m"
ENDC = "\033[0m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"
TEMPLATE_FILE = '../cfg/config.json.template'
USER_FILE = '../cfg/config.json'
#search and open the configuration file
TEMPLATE_FILE = "../cfg/config.json.template"
USER_FILE = "../cfg/config.json"
# search and open the configuration file
def get_cfg_file(template):
if template:
cfg_file = TEMPLATE_FILE
if not path.exists(cfg_file):
print ('file not found')
cfg_file = ''
print("file not found")
cfg_file = ""
else:
cfg_file = USER_FILE
if not path.exists(cfg_file):
cfg_file = TEMPLATE_FILE
if not path.exists(cfg_file):
cfg_file = ''
cfg_file = ""
print('Configuration file loaded from: '+cfg_file)
print("Configuration file loaded from: " + cfg_file)
return cfg_file
#covert file in json
# covert file in json
def get_cfg_json(f):
if f:
with open(f) as json_data_file:
@ -59,10 +63,11 @@ def get_cfg_json(f):
return cfg
#read a single value from json
def get_cfg_value(cfg,key):
k_arr = key.split('/')
# read a single value from json
def get_cfg_value(cfg, key):
k_arr = key.split("/")
l_arr = len(k_arr)
try:
@ -71,228 +76,254 @@ def get_cfg_value(cfg,key):
elif l_arr == 2:
val = cfg[k_arr[0]][k_arr[1]]
except KeyError:
val=''
val = ""
return val
def set_cfg_value(cfg,key,val):
k_arr = key.split('/')
def set_cfg_value(cfg, key, val):
k_arr = key.split("/")
l_arr = len(k_arr)
try:
if l_arr == 1:
cfg[k_arr[0]]=val
cfg[k_arr[0]] = val
elif l_arr == 2:
cfg[k_arr[0]][k_arr[1]]=val
cfg[k_arr[0]][k_arr[1]] = val
except KeyError:
pass
return cfg
def style_field(lbl,val):
return lbl+bcolors.BOLD + str(val) + bcolors.ENDC
def show_menu(cfg,key):
menu=get_cfg_value(cfg,'menu/menu_list')
i=-1
def style_field(lbl, val):
return lbl + bcolors.BOLD + str(val) + bcolors.ENDC
def show_menu(cfg, key):
menu = get_cfg_value(cfg, "menu/menu_list")
i = -1
for element in menu:
i+=1
print(style_field(str(i)+'. external: ',str(element['external']))+style_field(', label: ',element['label']))
print(style_field(' link____: ',element['link']))
i += 1
print(
style_field(str(i) + ". external: ", str(element["external"]))
+ style_field(", label: ", element["label"])
)
print(style_field(" link____: ", element["link"]))
print()
return
def help_list():
print ()
print (' h: help')
print (' vc: view config.')
print (' ec: edit config.')
print (' vm: view menu')
print (' em: edit menu')
print (' s: save')
print (' t: load config. from template')
print ()
print (' x: exit')
print ()
print()
print(" h: help")
print(" vc: view config.")
print(" ec: edit config.")
print(" vm: view menu")
print(" em: edit menu")
print(" s: save")
print(" t: load config. from template")
print()
print(" x: exit")
print()
return
def help_menu_edit():
print ()
print (' n: new menu entry')
print (' d: delete menu entry')
print (' e: edit menu entry')
print ()
print (' x: exit')
print ()
print()
print(" n: new menu entry")
print(" d: delete menu entry")
print(" e: edit menu entry")
print()
print(" x: exit")
print()
return
def view(cfg,t):
if t == 'c':
i=0
def view(cfg, t):
if t == "c":
i = 0
for element in configs:
(key, lbl)=element
print(style_field(str(i)+'. '+lbl,str(get_cfg_value(cfg,key))))
i+=1
elif t == 'm':
print ('Menu:')
show_menu(cfg,'menu/menu_list')
(key, lbl) = element
print(style_field(str(i) + ". " + lbl, str(get_cfg_value(cfg, key))))
i += 1
elif t == "m":
print("Menu:")
show_menu(cfg, "menu/menu_list")
print()
return
def user_input(caption):
return input(caption)
def edit_config(cfg):
view(cfg,'c')
inp=''
while inp!='x':
inp=str(user_input ('Type the number of config. you would to edit, x for end: ')).lower()
view(cfg, "c")
inp = ""
while inp != "x":
inp = str(
user_input("Type the number of config. you would to edit, x for end: ")
).lower()
if inp.isdigit():
inp=int(inp)
inp = int(inp)
try:
(key, lbl)=configs[inp]
print(style_field(lbl,get_cfg_value(cfg,key)))
val=str(user_input ('Enter new value, [ENTER] for nothing: '))
(key, lbl) = configs[inp]
print(style_field(lbl, get_cfg_value(cfg, key)))
val = str(user_input("Enter new value, [ENTER] for nothing: "))
except IndexError:
print ('configuration not found!')
print("configuration not found!")
finally:
if val!='x' and val !='':
cfg=set_cfg_value(cfg,key,val)
if val != "x" and val != "":
cfg = set_cfg_value(cfg, key, val)
return cfg
def menu_delete_entry(cfg):
view(cfg,'m')
inp=''
while inp!='x':
inp=str(user_input ('Choose the menu you would to delete, x for end: ')).lower()
view(cfg, "m")
inp = ""
while inp != "x":
inp = str(
user_input("Choose the menu you would to delete, x for end: ")
).lower()
if inp.isdigit():
inp=int(inp)
element = cfg['menu']['menu_list']
inp = int(inp)
element = cfg["menu"]["menu_list"]
try:
del element[inp]
cfg['menu']['menu_list']=element
cfg["menu"]["menu_list"] = element
except IndexError:
print ('menu entry not found!')
print("menu entry not found!")
return cfg
def is_external(val):
return val == 'y'
return val == "y"
def menu_input_entry(entry,new_entry):
def menu_input_entry(entry, new_entry):
if not new_entry:
print('label old value: '+entry['label'])
print("label old value: " + entry["label"])
label=str(user_input('label new value: '))
label = str(user_input("label new value: "))
if not new_entry:
print('link old value: '+entry['link'])
print("link old value: " + entry["link"])
link=str(user_input('link value: '))
link = str(user_input("link value: "))
if not new_entry:
print('external old value: '+str(entry['external']))
print("external old value: " + str(entry["external"]))
external = ''
while external != 'y' and external != 'n':
external=str(user_input('open link external [y/n]: ')).lower()
external = ""
while external != "y" and external != "n":
external = str(user_input("open link external [y/n]: ")).lower()
# if external == 'y':
# external = True
# else:
# external = False
external=is_external(external)
# if external == 'y':
# external = True
# else:
# external = False
external = is_external(external)
entry['label']=label
entry['link']=link
entry['external']=external
entry["label"] = label
entry["link"] = link
entry["external"] = external
return entry
def menu_edit_entry(cfg):
view(cfg,'m')
inp=''
while inp!='x':
inp=str(user_input ('Choose the menu you would to edit, X for end: ')).lower()
view(cfg, "m")
inp = ""
while inp != "x":
inp = str(user_input("Choose the menu you would to edit, X for end: ")).lower()
if inp.isdigit():
inp=int(inp)
element = cfg['menu']['menu_list']
inp = int(inp)
element = cfg["menu"]["menu_list"]
try:
element[inp]=menu_input_entry(element[inp],False)
cfg['menu']['menu_list']=element
element[inp] = menu_input_entry(element[inp], False)
cfg["menu"]["menu_list"] = element
except IndexError:
print ('menu entry not found!')
print("menu entry not found!")
return cfg
def menu_new_entry(cfg):
view(cfg,'m')
inp=''
view(cfg, "m")
inp = ""
valid = False
entry=menu_input_entry({"label":"", "link": "", "external": False},True)
entry = menu_input_entry({"label": "", "link": "", "external": False}, True)
while not valid:
inp=str(user_input ('Enter the position number of your menu entry, X for end: '))
inp = str(
user_input("Enter the position number of your menu entry, X for end: ")
)
if inp.isdigit():
inp=int(inp)
if inp > len(cfg['menu']['menu_list']):
print('position not valid!')
inp = int(inp)
if inp > len(cfg["menu"]["menu_list"]):
print("position not valid!")
valid = False
elif inp == len(cfg['menu']['menu_list']):
cfg['menu']['menu_list'].append(entry)
elif inp == len(cfg["menu"]["menu_list"]):
cfg["menu"]["menu_list"].append(entry)
valid = True
else:
cfg['menu']['menu_list'].insert(inp,entry)
cfg["menu"]["menu_list"].insert(inp, entry)
valid = True
else:
valid = False
return cfg
def edit_menu(cfg):
view(cfg,'m')
inp=''
while inp!='x':
view(cfg, "m")
inp = ""
while inp != "x":
help_menu_edit()
inp=str(user_input ('Edit menu> make your choiche: ')).lower()
if (inp == 'n'):
cfg=menu_new_entry(cfg)
elif inp == 'd':
cfg=menu_delete_entry(cfg)
elif inp == 'e':
cfg=menu_edit_entry(cfg)
inp = str(user_input("Edit menu> make your choiche: ")).lower()
if inp == "n":
cfg = menu_new_entry(cfg)
elif inp == "d":
cfg = menu_delete_entry(cfg)
elif inp == "e":
cfg = menu_edit_entry(cfg)
return cfg
def save_cfg(cfg):
with open(USER_FILE, 'w') as outfile:
json.dump(cfg, outfile,indent=4)
print ('configuration saved to: '+USER_FILE)
with open(USER_FILE, "w") as outfile:
json.dump(cfg, outfile, indent=4)
print("configuration saved to: " + USER_FILE)
return
def main():
print()
print('*** DxSpider configuration ***')
finput=get_cfg_file(False)
print("*** DxSpider configuration ***")
finput = get_cfg_file(False)
help_list()
cfg=get_cfg_json(finput)
inp = ''
while inp != 'x' and inp != 'exit':
inp = str(user_input('Main> make your choiche: ')).lower()
if (inp == 'h' or inp =='?' or inp =='help'):
cfg = get_cfg_json(finput)
inp = ""
while inp != "x" and inp != "exit":
inp = str(user_input("Main> make your choiche: ")).lower()
if inp == "h" or inp == "?" or inp == "help":
help_list()
elif inp == 'vc':
view(cfg,'c')
elif inp == 'vm':
view(cfg,'m')
elif inp == 'ec':
cfg=edit_config(cfg)
elif inp == 'em':
cfg=edit_menu(cfg)
elif inp == 's' or inp == 'save':
elif inp == "vc":
view(cfg, "c")
elif inp == "vm":
view(cfg, "m")
elif inp == "ec":
cfg = edit_config(cfg)
elif inp == "em":
cfg = edit_menu(cfg)
elif inp == "s" or inp == "save":
save_cfg(cfg)
elif inp == 't':
finput=get_cfg_file(True)
cfg=get_cfg_json(finput)
elif inp == "t":
finput = get_cfg_file(True)
cfg = get_cfg_json(finput)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@ -1,7 +1,7 @@
#*************************************************************************************
# *************************************************************************************
# Module used to download cty.dat country file and search callsign in it
#*************************************************************************************
__author__ = 'IU1BOW - Corrado'
# *************************************************************************************
__author__ = "IU1BOW - Corrado"
import requests
import logging
import os
@ -10,124 +10,147 @@ from threading import Timer
from datetime import datetime
import json
logging.basicConfig(level=logging.INFO,format='%(asctime)s [%(levelname)s]: %(message)s',datefmt='%m/%d/%Y %I:%M:%S')
#TODO: url from conf parameter
url = 'https://www.country-files.com/cty/cty_wt_mod.dat'
cty_local=os.path.dirname(__file__)+'/../cfg/cty_wt_mod.dat'
country_file=os.path.dirname(__file__)+'/../cfg/country.json'
#-------------------------------------------------------------------------------------
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s]: %(message)s",
datefmt="%m/%d/%Y %I:%M:%S",
)
# TODO: url from conf parameter
url = "https://www.country-files.com/cty/cty_wt_mod.dat"
cty_local = os.path.dirname(__file__) + "/../static/data/cty_wt_mod.dat"
country_file = os.path.dirname(__file__) + "/../cfg/country.json"
# -------------------------------------------------------------------------------------
# download country files cty.dat
#-------------------------------------------------------------------------------------
def download_cty(url,cty_local):
# -------------------------------------------------------------------------------------
def download_cty(url, cty_local):
try:
logging.info('connection to: '+url)
req=requests.get(url)
f=open(cty_local,'wb')
logging.info("connection to: " + url)
req = requests.get(url)
f = open(cty_local, "wb")
f.write(req.content)
f.close()
logging.info('cty file saved in: '+cty_local)
logging.info("cty file saved in: " + cty_local)
return 0
except Exception as e1:
logging.error(e1)
return 1
#-------------------------------------------------------------------------------------
# get age of a file in days
#-------------------------------------------------------------------------------------
def file_age_in_days(pathname):
return (time.time() - os.stat(pathname).st_ctime)/(24*3600)
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# get age of a file in days
# -------------------------------------------------------------------------------------
def file_age_in_days(pathname):
return (time.time() - os.stat(pathname).st_ctime) / (24 * 3600)
# -------------------------------------------------------------------------------------
# manage file cty.dat
#-------------------------------------------------------------------------------------
def get_cty(url,local):
# -------------------------------------------------------------------------------------
def get_cty(url, local):
if os.path.isfile(local):
age=file_age_in_days(local)
if age>7:
logging.info(cty_local+' too old ('+str(round(age,0))+' days): proceding to download it')
return download_cty(url,local)
# else:
# logging.info(cty_local+' updated ('+str(round(age,0))+' days), is not necessary to download it')
# return 0
logging.info(cty_local+' updated ('+str(round(age,0))+' days), is not necessary to download it')
age = file_age_in_days(local)
if age > 7:
logging.info(
cty_local
+ " too old ("
+ str(round(age, 0))
+ " days): proceding to download it"
)
return download_cty(url, local)
# else:
# logging.info(cty_local+' updated ('+str(round(age,0))+' days), is not necessary to download it')
# return 0
logging.info(
cty_local
+ " updated ("
+ str(round(age, 0))
+ " days), is not necessary to download it"
)
return 0
# else:
# logging.info(cty_local+' not present: proceding to download it')
# return download_cty(url,local)
logging.info(cty_local+' not present: proceding to download it')
return download_cty(url,local)
logging.info(cty_local + " not present: proceding to download it")
return download_cty(url, local)
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# parsing alias and get exceptions
#-------------------------------------------------------------------------------------
def parse_alias(alias,master):
# -------------------------------------------------------------------------------------
def parse_alias(alias, master):
try:
#create a dictionary of array, with start and end position of each exception
find_dict={}
find_dict['pos_cq']=[alias.find('('),alias.find(')')]
find_dict['pos_itu']=[alias.find('['),alias.find(']')]
find_dict['pos_lat_lon']=[alias.find('<'),alias.find('>')]
find_dict['pos_continent']=[alias.find('{'),alias.find('}')]
find_dict['pos_time']=[alias.find('~'),alias[:alias.find('~')].find('~')]
# create a dictionary of array, with start and end position of each exception
find_dict = {}
find_dict["pos_cq"] = [alias.find("("), alias.find(")")]
find_dict["pos_itu"] = [alias.find("["), alias.find("]")]
find_dict["pos_lat_lon"] = [alias.find("<"), alias.find(">")]
find_dict["pos_continent"] = [alias.find("{"), alias.find("}")]
find_dict["pos_time"] = [alias.find("~"), alias[: alias.find("~")].find("~")]
first=9999
parsed={}
first = 9999
parsed = {}
#assign default values from master callsing
parsed["country"]=master["country"]
parsed["cq"]=master["cq"]
parsed["itu"]=master["itu"]
parsed["continent"]=master["continent"]
parsed["lat"]=master["lat"]
parsed["lon"]=master["lon"]
parsed["time_loc"]=master["time_loc"]
parsed["full"]=master["full"]
parsed["darc_waedc"]=master["darc_waedc"]
# assign default values from master callsing
parsed["country"] = master["country"]
parsed["cq"] = master["cq"]
parsed["itu"] = master["itu"]
parsed["continent"] = master["continent"]
parsed["lat"] = master["lat"]
parsed["lon"] = master["lon"]
parsed["time_loc"] = master["time_loc"]
parsed["full"] = master["full"]
parsed["darc_waedc"] = master["darc_waedc"]
#extract override cq
if find_dict['pos_cq'][0]>=0:
parsed["cq"]=alias[find_dict['pos_cq'][0]+1:find_dict['pos_cq'][1]]
if find_dict['pos_cq'][0] < first:
first=find_dict['pos_cq'][0]
# extract override cq
if find_dict["pos_cq"][0] >= 0:
parsed["cq"] = alias[find_dict["pos_cq"][0] + 1 : find_dict["pos_cq"][1]]
if find_dict["pos_cq"][0] < first:
first = find_dict["pos_cq"][0]
#extract override itu
if find_dict['pos_itu'][0]>=0:
parsed["itu"]=alias[find_dict['pos_itu'][0]+1:find_dict['pos_itu'][1]]
if find_dict['pos_itu'][0] < first:
first=find_dict['pos_itu'][0]
# extract override itu
if find_dict["pos_itu"][0] >= 0:
parsed["itu"] = alias[find_dict["pos_itu"][0] + 1 : find_dict["pos_itu"][1]]
if find_dict["pos_itu"][0] < first:
first = find_dict["pos_itu"][0]
#extract override lat_lon
if find_dict['pos_lat_lon'][0]>=0:
lat_lon=alias[find_dict['pos_lat_lon'][0]+1:find_dict['pos_lat_lon'][1]]
parsed["lat"]=lat_lon[0:].split('/')[0]
parsed["lon"]=lat_lon[:len(lat_lon)].split('/')[1]
if find_dict['pos_lat_lon'][0] < first:
first=find_dict['pos_lat_lon'][0]
# extract override lat_lon
if find_dict["pos_lat_lon"][0] >= 0:
lat_lon = alias[
find_dict["pos_lat_lon"][0] + 1 : find_dict["pos_lat_lon"][1]
]
parsed["lat"] = lat_lon[0:].split("/")[0]
parsed["lon"] = lat_lon[: len(lat_lon)].split("/")[1]
if find_dict["pos_lat_lon"][0] < first:
first = find_dict["pos_lat_lon"][0]
#extract override continent
if find_dict['pos_continent'][0]>=0:
parsed["continent"]=alias[find_dict['pos_continent'][0]+1:find_dict['pos_continent'][1]]
if find_dict['pos_continent'][0] < first:
first=find_dict['pos_continent'][0]
# extract override continent
if find_dict["pos_continent"][0] >= 0:
parsed["continent"] = alias[
find_dict["pos_continent"][0] + 1 : find_dict["pos_continent"][1]
]
if find_dict["pos_continent"][0] < first:
first = find_dict["pos_continent"][0]
#extract override time
if find_dict['pos_time'][0]>=0:
parsed["time_loc"]=alias[find_dict['pos_time'][0]+1:find_dict['pos_time'][1]]
if find_dict['pos_time'][0] < first:
first=find_dict['pos_time'][0]
# extract override time
if find_dict["pos_time"][0] >= 0:
parsed["time_loc"] = alias[
find_dict["pos_time"][0] + 1 : find_dict["pos_time"][1]
]
if find_dict["pos_time"][0] < first:
first = find_dict["pos_time"][0]
#extract callsign
callsing=alias[:first].upper()
if callsing.startswith('='):
parsed["full"]='y'
callsing=callsing[1:]
# extract callsign
callsing = alias[:first].upper()
if callsing.startswith("="):
parsed["full"] = "y"
callsing = callsing[1:]
if callsing.startswith('*'):
parsed["darc_waedc"]='y'
callsing=callsing[1:]
if callsing.startswith("*"):
parsed["darc_waedc"] = "y"
callsing = callsing[1:]
return callsing, parsed
@ -138,38 +161,42 @@ def parse_alias(alias,master):
logging.error(alias)
return -1
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# load file from configuration, containing all world country, with related ISO codes
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
def load_country():
with open(country_file) as json_country:
return json.load(json_country)
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# search for ISO code, transcoding the country description
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
def add_country(table):
country_data=load_country()
country_data = load_country()
for key, value in table.items():
found=0
for i in country_data['country_codes']:
if i['desc'].upper()==value['country'].upper():
value["iso"]=i["ISO"]
value["wpx"]=i["WPX"]
found=1
found = 0
for i in country_data["country_codes"]:
if i["desc"].upper() == value["country"].upper():
value["iso"] = i["ISO"]
value["wpx"] = i["WPX"]
found = 1
break
if found==0:
logging.warning('country "'+value['country']+'" not found in cfg/country.json')
if found == 0:
logging.warning(
'country "' + value["country"] + '" not found in cfg/country.json'
)
return
class prefix_table:
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# init of the class
# - download file cty.dat
# - parse file and create prefix_master with all prefixies and attributes
#.....................................................................................
# .....................................................................................
# CTY.DAT Format
#
# reference: https://www.country-files.com/cty-dat-format/
@ -203,84 +230,89 @@ class prefix_table:
# {aa} Override Continent
# ~#~ Override local time offset from GMT
#
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
def __init__(self):
global prefix_master
prefix_master=dict()
prefix_master = dict()
initialization()
return
global initialization
def initialization():
refresh()
global timer
timer = Timer(3600*24,initialization) #try to refresh once a day
timer = Timer(3600 * 24, initialization) # try to refresh once a day
timer.start()
return
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# refresh data
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
global refresh
def refresh():
logging.info('CTY: start initialization')
if get_cty(url,cty_local)>0:
logging.error('there is a problem during downloading country files!')
logging.info('continue with previous file')
logging.info('check the connectivity, or put manually the file '+cty_local)
line_num=0
line_num_valid=0
entities_number=0
data=''
table=[]
logging.info("CTY: start initialization")
if get_cty(url, cty_local) > 0:
logging.error("there is a problem during downloading country files!")
logging.info("continue with previous file")
logging.info(
"check the connectivity, or put manually the file " + cty_local
)
line_num = 0
line_num_valid = 0
entities_number = 0
data = ""
table = []
prefix_master.clear()
try:
with open(cty_local, 'r') as f:
with open(cty_local, "r") as f:
for line in f:
line_num+=1
li=line.strip()
#remove comments
line_num += 1
li = line.strip()
# remove comments
if not li.startswith("#"):
line_num_valid+=1
data+=li
logging.info('number of lines reads: '+str(line_num))
logging.info('number of valid lines: '+str(line_num_valid))
line_num_valid += 1
data += li
logging.info("number of lines reads: " + str(line_num))
logging.info("number of valid lines: " + str(line_num_valid))
#split in array of lines terminated with semicolon
table=data.split(';')
for i,item_table in enumerate(table):
row=item_table.split(':')
#remove trailing spaces and uppercasing
row = [x.strip(' ') for x in row]
if len(row)==9:
#if the row is corret put the row in a master prefix dictionary
entities_number+=1
single_prefix={}
single_prefix["country"]=row[0]
single_prefix["cq"]=row[1]
single_prefix["itu"]=row[2]
single_prefix["continent"]=row[3]
single_prefix["lat"]=row[4]
single_prefix["lon"]=row[5]
single_prefix["time_loc"]=row[6]
single_prefix["full"]='n'
single_prefix["darc_waedc"]='n'
prefix_master[row[7].upper()]=single_prefix
#managing sub-prefixies
sub_prefixies=row[8].split(',')
# split in array of lines terminated with semicolon
table = data.split(";")
for i, item_table in enumerate(table):
row = item_table.split(":")
# remove trailing spaces and uppercasing
row = [x.strip(" ") for x in row]
if len(row) == 9:
# if the row is corret put the row in a master prefix dictionary
entities_number += 1
single_prefix = {}
single_prefix["country"] = row[0]
single_prefix["cq"] = row[1]
single_prefix["itu"] = row[2]
single_prefix["continent"] = row[3]
single_prefix["lat"] = row[4]
single_prefix["lon"] = row[5]
single_prefix["time_loc"] = row[6]
single_prefix["full"] = "n"
single_prefix["darc_waedc"] = "n"
prefix_master[row[7].upper()] = single_prefix
# managing sub-prefixies
sub_prefixies = row[8].split(",")
for sb in sub_prefixies:
values={}
callsign, values=parse_alias(sb,single_prefix)
prefix_master[callsign]=values
values = {}
callsign, values = parse_alias(sb, single_prefix)
prefix_master[callsign] = values
logging.info('number of entities: '+str(entities_number))
logging.info('number of single alias: '+str(len(prefix_master)))
logging.info("number of entities: " + str(entities_number))
logging.info("number of single alias: " + str(len(prefix_master)))
add_country(prefix_master)
logging.info('memory used for prefix: '+str(prefix_master.__sizeof__())+' bytes')
logging.info('CTY: initialization complete')
logging.info(
"memory used for prefix: " + str(prefix_master.__sizeof__()) + " bytes"
)
logging.info("CTY: initialization complete")
return
except Exception as e1:
@ -289,24 +321,23 @@ class prefix_table:
logging.error(message)
return
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# find a callsign
#-------------------------------------------------------------------------------------
def find(self,callsign):
# -------------------------------------------------------------------------------------
def find(self, callsign):
try:
data=dict()
i=len(callsign)
callsign=callsign.strip().upper()
while i>0:
data = dict()
i = len(callsign)
callsign = callsign.strip().upper()
while i > 0:
try:
data=prefix_master[callsign[:i]]
data['match']=callsign[:i]
data = prefix_master[callsign[:i]]
data["match"] = callsign[:i]
return data
except KeyError:
pass
i-=1
i -= 1
except Exception as e1:
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
@ -314,12 +345,12 @@ class prefix_table:
logging.error(message)
return data
#not found
data["country"]="unknown country"
data["iso"]="xx"
# not found
data["country"] = "unknown country"
data["iso"] = "xx"
return data
def __del__(self):
timer.cancel()
logging.info('prefix_table destroyed')
logging.info("prefix_table destroyed")
return

View File

@ -1,74 +1,78 @@
#*************************************************************************************
# *************************************************************************************
# Module used to interface with telnet cluster and get connected nodes
#*************************************************************************************
__author__ = 'IU1BOW - Corrado'
# *************************************************************************************
__author__ = "IU1BOW - Corrado"
import telnetlib
import struct
import json
import logging
def parse_who(lines):
#print(lines.decode('ascii'))
#create a list o lines and define the structure
def parse_who(lines):
# print(lines.decode('ascii'))
# create a list o lines and define the structure
lines = lines.splitlines()
fmtstring='2x 9s 10s 18s 9s 8s 15s'
fmtstring = "2x 9s 10s 18s 9s 8s 15s"
fieldstruct = struct.Struct(fmtstring)
row_headers=('callsign','type','started','name','average_rtt','link')
row_headers = ("callsign", "type", "started", "name", "average_rtt", "link")
#skip first lines and last line
payload=[]
for i in range(3,len(lines)-1):
line=lines[i]
ln=len(line)
# skip first lines and last line
payload = []
for i in range(3, len(lines) - 1):
line = lines[i]
ln = len(line)
padding=bytes(' ' * (struct.calcsize(fmtstring)-ln),'utf-8')
line=(line+padding)
padding = bytes(" " * (struct.calcsize(fmtstring) - ln), "utf-8")
line = line + padding
if ln > 10:
parse = fieldstruct.unpack_from
fields =list(parse(line))
fields = list(parse(line))
for j,item_field in enumerate(fields):
for j, item_field in enumerate(fields):
try:
fields[j]=item_field.decode('utf-8').strip()
fields[j] = item_field.decode("utf-8").strip()
except AttributeError:
print(item_field)
payload.append(dict(zip(row_headers,fields)))
payload.append(dict(zip(row_headers, fields)))
# payload = json.dumps(payload)
return payload
def who(host,port,user):
WAIT_FOR = b'dxspider >'
TIMEOUT=1
res=0
def who(host, port, user):
WAIT_FOR = b"dxspider >"
TIMEOUT = 1
res = 0
try:
tn = telnetlib.Telnet(host,port,TIMEOUT)
tn = telnetlib.Telnet(host, port, TIMEOUT)
try:
tn.read_until(b"login: ",TIMEOUT)
tn.write(user.encode('ascii') + b"\n")
res=tn.read_until(WAIT_FOR,TIMEOUT)
tn.read_until(b"login: ", TIMEOUT)
tn.write(user.encode("ascii") + b"\n")
res = tn.read_until(WAIT_FOR, TIMEOUT)
tn.write(b"who\n")
res=tn.read_until(WAIT_FOR,TIMEOUT)
res = tn.read_until(WAIT_FOR, TIMEOUT)
tn.write(b"exit\n")
except EOFError:
logging.error ('could not autenticate to telnet dxspider host: check user callsign ')
logging.error (res)
res=0
logging.error(
"could not autenticate to telnet dxspider host: check user callsign "
)
logging.error(res)
res = 0
except:
logging.error ('could not connect to telnet dxspider host: check host/port')
ret = ''
logging.error("could not connect to telnet dxspider host: check host/port")
ret = ""
if res!=0:
ret=parse_who(res)
if res != 0:
ret = parse_who(res)
else:
ret=''
ret = ""
return ret

View File

@ -1,9 +1,9 @@
#*************************************************************************************
# *************************************************************************************
# Module used to convert dxcluster band file to band configuration file modes for
# spiderweb
# you can use it in build step
#*************************************************************************************
__author__ = 'IU1BOW - Corrado'
# *************************************************************************************
__author__ = "IU1BOW - Corrado"
import logging
import os
import time
@ -12,47 +12,53 @@ import json
import re
import sys
logging.basicConfig(level=logging.INFO,format='%(asctime)s [%(levelname)s]: %(message)s',datefmt='%m/%d/%Y %I:%M:%S')
#dxspider_band='/home/sysop/spider/data/bands.pl'
output_modes='../cfg/modes.json'
#-------------------------------------------------------------------------------------
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s]: %(message)s",
datefmt="%m/%d/%Y %I:%M:%S",
)
# dxspider_band='/home/sysop/spider/data/bands.pl'
output_modes = "../cfg/modes.json"
# -------------------------------------------------------------------------------------
# reads bands file and convert to json
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
def parse(input_file):
line_num=0
line_num_valid=0
data=''
line_num = 0
line_num_valid = 0
data = ""
band_trigger = False
try:
with open(input_file, 'r') as f:
with open(input_file, "r") as f:
for line in f:
line_num+=1
li=line.strip()
#remove comments
line_num += 1
li = line.strip()
# remove comments
if li.startswith("%bands = "):
band_trigger = True
if li.endswith(");"):
band_trigger = False
if not li.startswith("#") and band_trigger == True:
line_num_valid+=1
data+=li
line_num_valid += 1
data += li
logging.debug("first step parsing output: ")
logging.debug(data)
#replacing strings in order to obtain a json
data=data.lower()
data=data.replace(" ","")
data=data.replace("bless","")
data=data.replace("%bands=","")
data=data.replace(",'bands'","")
data=re.sub(r"([\"'])(?:(?=(\\?))\2.)*?\1=>", "", data) #remove token like '136khz' =>
data=data.replace("=>",":")
data=data.replace("(","")
data=data.replace(")","")
data=re.sub(r'([a-zA-Z]+):', r'"\1":', data) #add quotation around words
data=data.replace(",}","}")
data="["+data+"]"
data=data.replace(",]","]")
# replacing strings in order to obtain a json
data = data.lower()
data = data.replace(" ", "")
data = data.replace("bless", "")
data = data.replace("%bands=", "")
data = data.replace(",'bands'", "")
data = re.sub(
r"([\"'])(?:(?=(\\?))\2.)*?\1=>", "", data
) # remove token like '136khz' =>
data = data.replace("=>", ":")
data = data.replace("(", "")
data = data.replace(")", "")
data = re.sub(r"([a-zA-Z]+):", r'"\1":', data) # add quotation around words
data = data.replace(",}", "}")
data = "[" + data + "]"
data = data.replace(",]", "]")
logging.debug("second step parsing output: ")
logging.debug(data)
@ -65,17 +71,20 @@ def parse(input_file):
logging.error(input_file)
return ""
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# add min max freq element to the related mode in final json
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
def add_freq(mode, freq, json_modes):
try:
for modes in json_modes['modes']:
if modes["id"]==mode:
for modes in json_modes["modes"]:
if modes["id"] == mode:
ind_freq = 0
while ind_freq < len(freq):
modes["freq"].append({"min":freq[ind_freq],"max":freq[ind_freq+1]})
ind_freq +=2
modes["freq"].append(
{"min": freq[ind_freq], "max": freq[ind_freq + 1]}
)
ind_freq += 2
return json_modes
@ -85,12 +94,15 @@ def add_freq(mode, freq, json_modes):
logging.error(message)
return {}
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# reads bands file and convert to json
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
def create_output(data):
json_modes=json.loads('{"modes":[{"id":"cw","freq":[]},{"id":"phone","freq":[]},{"id":"digi","freq":[]}]}')
json_modes = json.loads(
'{"modes":[{"id":"cw","freq":[]},{"id":"phone","freq":[]},{"id":"digi","freq":[]}]}'
)
try:
for element in data:
@ -98,11 +110,11 @@ def create_output(data):
if mode == "band":
pass
elif mode == "cw":
json_modes=add_freq("cw",element[mode],json_modes)
json_modes = add_freq("cw", element[mode], json_modes)
elif mode == "ssb":
json_modes=add_freq("phone",element[mode],json_modes)
json_modes = add_freq("phone", element[mode], json_modes)
else:
json_modes=add_freq("digi",element[mode],json_modes)
json_modes = add_freq("digi", element[mode], json_modes)
logging.debug("final step output: ")
logging.debug(json.dumps(json_modes))
@ -115,23 +127,24 @@ def create_output(data):
logging.error(input_file)
return {}
#*************************************************************************************
# *************************************************************************************
# Main
#*************************************************************************************
logging.info('RDxSpider band file conversion starting...')
if len(sys.argv)!=2:
# *************************************************************************************
logging.info("RDxSpider band file conversion starting...")
if len(sys.argv) != 2:
logging.error("argument invalid. Specify dxcluster band file")
logging.error("use: python get_dxcluster_modes.py input_file")
raise Exception()
dxspider_band=sys.argv[1]
dxspider_band = sys.argv[1]
parsed = parse(dxspider_band)
if parsed != "" :
json_output=create_output(parsed)
with open(output_modes,'w') as outfile:
json.dump(json_output, outfile,indent=4)
logging.info('modes saved to: '+output_modes)
logging.info('DxSpider band file conversion completed')
if parsed != "":
json_output = create_output(parsed)
with open(output_modes, "w") as outfile:
json.dump(json_output, outfile, indent=4)
logging.info("modes saved to: " + output_modes)
logging.info("DxSpider band file conversion completed")
else:
logging.error("error on parsing input file")

View File

@ -1,83 +1,101 @@
#***********************************************************************************
# ***********************************************************************************
# Module that contain classess for providing data to plotting front-end page
#***********************************************************************************
__author__ = 'IU1BOW - Corrado'
# ***********************************************************************************
__author__ = "IU1BOW - Corrado"
import threading
import time
from lib.qry import query_manager
import pandas as pd
import json
#-----------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------
# Base class (as template for other classes)
#-----------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------
class BaseDataProvider:
#glb_data is used for store and return informations to within get_data method
# glb_data is used for store and return informations to within get_data method
global glb_data
global glb_last_refresh
global glb_response
#refresh is used to refetch data from db and store in memory. you can define a
# refresh is used to refetch data from db and store in memory. you can define a
# time for refresh
def refresh(self):
self.logger.info("Class: %s refresh data",self.__class__.__name__)
self.logger.info("Class: %s refresh data", self.__class__.__name__)
return {}
#return data to the caller
# return data to the caller
def get_data(self):
self.glb_response = {}
self.glb_response.update({"last_refresh":self.glb_last_refresh})
self.glb_response.update({"last_refresh": self.glb_last_refresh})
return self.glb_response
#constructor: you have to pass logger, continent list and bands (for frequencies)
# constructor: you have to pass logger, continent list and bands (for frequencies)
# this method call the first refresh
def __init__(self, logger, qm, continents, bands):
self.logger = logger
self.logger.info("Class: %s init start",self.__class__.__name__)
self.logger.info("Class: %s init start", self.__class__.__name__)
self.qm = qm
self.continents=continents
self.bands=bands
self.continents = continents
self.bands = bands
self.refresh()
self.logger.info("Class: %s init end",self.__class__.__name__)
self.logger.info("Class: %s init end", self.__class__.__name__)
return
#-----------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------
# Class for managing data for Continent/Band chart
#-----------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------
class ContinentsBandsProvider(BaseDataProvider):
def __init__(self,logger, qm, continents, bands):
def __init__(self, logger, qm, continents, bands):
# Calling constructor of base class
super().__init__(logger,qm,continents,bands)
super().__init__(logger, qm, continents, bands)
def __load_data(self,band_frequencies, continents_cq):
def __load_data(self, band_frequencies, continents_cq):
self.logger.info("Start")
self.logger.info("doing query...")
#construct bands query
bands_qry_string = 'CASE '
# construct bands query
bands_qry_string = "CASE "
self.logger.debug(band_frequencies)
for i in range(len(band_frequencies["bands"])):
bands_qry_string+=' WHEN freq between '+str(band_frequencies["bands"][i]["min"])+' AND '+ str(band_frequencies["bands"][i]["max"])
bands_qry_string+=' THEN "'+band_frequencies["bands"][i]["id"]+'"'
bands_qry_string += (
" WHEN freq between "
+ str(band_frequencies["bands"][i]["min"])
+ " AND "
+ str(band_frequencies["bands"][i]["max"])
)
bands_qry_string += ' THEN "' + band_frequencies["bands"][i]["id"] + '"'
#construct continent region query
spottercq_qry_string = 'CASE '
spotcq_qry_string = 'CASE '
# construct continent region query
spottercq_qry_string = "CASE "
spotcq_qry_string = "CASE "
for i in range(len(continents_cq["continents"])):
spottercq_qry_string+=' WHEN spottercq in('+continents_cq["continents"][i]["cq"]+')'
spottercq_qry_string+=' THEN "' +continents_cq["continents"][i]["id"]+'"'
spotcq_qry_string+=' WHEN spotcq in('+continents_cq["continents"][i]["cq"]+')'
spotcq_qry_string+=' THEN "' +continents_cq["continents"][i]["id"]+'"'
spottercq_qry_string += (
" WHEN spottercq in(" + continents_cq["continents"][i]["cq"] + ")"
)
spottercq_qry_string += (
' THEN "' + continents_cq["continents"][i]["id"] + '"'
)
spotcq_qry_string += (
" WHEN spotcq in(" + continents_cq["continents"][i]["cq"] + ")"
)
spotcq_qry_string += ' THEN "' + continents_cq["continents"][i]["id"] + '"'
#construct final query string
qry_string ="""
# construct final query string
qry_string = (
"""
SELECT
"""+spottercq_qry_string+""" ELSE spottercq END,
"""+spotcq_qry_string+""" ELSE spotcq END,
"""+bands_qry_string+""" END,
"""
+ spottercq_qry_string
+ """ ELSE spottercq END,
"""
+ spotcq_qry_string
+ """ ELSE spotcq END,
"""
+ bands_qry_string
+ """ END,
count(0) number
from spot
where
@ -86,25 +104,26 @@ class ContinentsBandsProvider(BaseDataProvider):
group by 1, 2, 3
;
"""
)
self.logger.debug(qry_string)
self.qm.qry(qry_string)
data=self.qm.get_data()
if len(data)==0:
data = self.qm.get_data()
if len(data) == 0:
self.logger.warning("no data found")
self.logger.info("query done")
self.logger.debug (data)
self.logger.debug(data)
return data
# function for search continent in the global data returned by query and making a cartesian product
# in order to prepare data for heatmap
def __normalize_continent(self,data_list,continent,continents_list, band_list):
data_filtered=[]
def __normalize_continent(self, data_list, continent, continents_list, band_list):
data_filtered = []
for i, item_data in enumerate(data_list):
if item_data[0]==continent and not (item_data[3] is None):
element=[]
if item_data[0] == continent and not (item_data[3] is None):
element = []
element.append(item_data[1])
element.append(item_data[2])
element.append(item_data[3])
@ -114,63 +133,68 @@ class ContinentsBandsProvider(BaseDataProvider):
for j, item_continent in enumerate(continents_list):
for k, item_band in enumerate(band_list):
found=0
found = 0
for lis, item_filtered in enumerate(data_filtered):
if item_filtered[0]==item_continent["id"] and item_filtered[1]==item_band["id"]:
#cartesian_product.append(item_filtered)
element=[]
if (
item_filtered[0] == item_continent["id"]
and item_filtered[1] == item_band["id"]
):
# cartesian_product.append(item_filtered)
element = []
element.append(j)
element.append(k)
element.append(item_filtered[2])
cartesian_product.append(element)
found=1
if found==0:
element=[]
found = 1
if found == 0:
element = []
element.append(j)
element.append(k)
element.append(0)
cartesian_product.append(element)
self.logger.debug("cartesian product for continent: "+continent)
self.logger.debug("cartesian product for continent: " + continent)
self.logger.debug(cartesian_product)
return cartesian_product
def refresh(self):
super().refresh()
lcl_data={}
qry_data=self.__load_data(self.bands, self.continents)
lcl_data = {}
qry_data = self.__load_data(self.bands, self.continents)
for i, item in enumerate(self.continents["continents"]):
continent=item["id"]
data_de=self.__normalize_continent(qry_data,continent,self.continents["continents"],self.bands["bands"])
lcl_data.update({continent:data_de})
continent = item["id"]
data_de = self.__normalize_continent(
qry_data, continent, self.continents["continents"], self.bands["bands"]
)
lcl_data.update({continent: data_de})
self.glb_data=lcl_data
self.glb_last_refresh=time.time()
self.glb_data = lcl_data
self.glb_last_refresh = time.time()
threading.Timer(15*60,self.refresh).start() #periodic refresh: set time
threading.Timer(15 * 60, self.refresh).start() # periodic refresh: set time
return
def get_data(self,continent_filter):
def get_data(self, continent_filter):
super().get_data()
self.glb_response.update({"band activity": self.glb_data[continent_filter]})
return self.glb_response
#-----------------------------------------------------------------------------------
# Class for managing data for Spots per months chart
#-----------------------------------------------------------------------------------
class SpotsPerMounthProvider(BaseDataProvider):
def __init__(self,logger,qm):
# -----------------------------------------------------------------------------------
# Class for managing data for Spots per months chart
# -----------------------------------------------------------------------------------
class SpotsPerMounthProvider(BaseDataProvider):
def __init__(self, logger, qm):
# Calling constructor of base class
super().__init__(logger,qm,[],[])
super().__init__(logger, qm, [], [])
def __load_data(self):
self.logger.info("Start")
self.logger.info("doing query...")
#construct final query string
qry_string="""
# construct final query string
qry_string = """
select month(s1.ym) as referring_month,
cast(sum(
case
@ -228,53 +252,57 @@ class SpotsPerMounthProvider(BaseDataProvider):
"""
self.logger.debug(qry_string)
self.qm.qry(qry_string)
data=self.qm.get_data()
if len(data)==0:
data = self.qm.get_data()
if len(data) == 0:
self.logger.warning("no data found")
self.logger.info("query done")
self.logger.debug (data)
self.logger.debug(data)
return data
def refresh(self):
super().refresh()
lcl_data={}
qry_data=self.__load_data()
lcl_data = {}
qry_data = self.__load_data()
for i, item in enumerate(qry_data):
year_data={'year_0':item[1],'year_1':item[2],'year_2':item[3]}
lcl_data.update({item[0]:year_data})
year_data = {"year_0": item[1], "year_1": item[2], "year_2": item[3]}
lcl_data.update({item[0]: year_data})
self.logger.debug(lcl_data)
self.glb_data=lcl_data
self.glb_last_refresh=time.time()
self.glb_data = lcl_data
self.glb_last_refresh = time.time()
threading.Timer(60*60*24,self.refresh).start() #periodic refresh: set time
threading.Timer(
60 * 60 * 24, self.refresh
).start() # periodic refresh: set time
return
def get_data(self,):
def get_data(
self,
):
super().get_data()
self.glb_response.update({"spots_per_month": self.glb_data})
return self.glb_response
#-----------------------------------------------------------------------------------
# Class for managing data for Spots trend chart
#-----------------------------------------------------------------------------------
class SpotsTrend(BaseDataProvider):
def __init__(self,logger,qm):
# -----------------------------------------------------------------------------------
# Class for managing data for Spots trend chart
# -----------------------------------------------------------------------------------
class SpotsTrend(BaseDataProvider):
def __init__(self, logger, qm):
# Calling constructor of base class
super().__init__(logger,qm,[],[])
super().__init__(logger, qm, [], [])
def __load_data(self):
self.logger.info("Start")
self.logger.info("doing query...")
#construct final query string
qry_string="""
# construct final query string
qry_string = """
select
FROM_UNIXTIME(time,'%Y-%m-%d') as day,
count(0) as total
@ -285,39 +313,43 @@ class SpotsTrend(BaseDataProvider):
"""
self.logger.debug(qry_string)
self.qm.qry_pd(qry_string)
df=self.qm.get_data()
df = self.qm.get_data()
self.logger.info("query done")
self.logger.debug (df)
self.logger.debug(df)
if len(df)==0:
if len(df) == 0:
self.logger.warning("no data found")
#normalize data eliminating peaks
df['day']=pd.to_datetime(df['day'])
df=df.set_index('day')
df=df.resample('D').interpolate(method='pad', limit_direction='forward', axis=0)
df=df.rolling('30D').mean()
df['total']=df['total'].round(0)
# normalize data eliminating peaks
df["day"] = pd.to_datetime(df["day"])
df = df.set_index("day")
df = df.resample("D").interpolate(
method="pad", limit_direction="forward", axis=0
)
df = df.rolling("30D").mean()
df["total"] = df["total"].round(0)
return df
def refresh(self):
super().refresh()
qry_data=self.__load_data()
qry_data = self.__load_data()
lcl_data={}
lcl_data = {}
#iterate panda dataframe
# iterate panda dataframe
for index, row in qry_data.iterrows():
lcl_data.update({str(index.date()):row['total']})
lcl_data.update({str(index.date()): row["total"]})
self.logger.debug(lcl_data)
self.glb_data=lcl_data
self.glb_last_refresh=time.time()
self.glb_data = lcl_data
self.glb_last_refresh = time.time()
threading.Timer(60*60*24,self.refresh).start() #periodic refresh: set time
threading.Timer(
60 * 60 * 24, self.refresh
).start() # periodic refresh: set time
return
def get_data(self):
@ -325,14 +357,14 @@ class SpotsTrend(BaseDataProvider):
self.glb_response.update({"spots_trend": self.glb_data})
return self.glb_response
#-----------------------------------------------------------------------------------
# Class for managing data for Hour/Band chart
#-----------------------------------------------------------------------------------
class HourBand(BaseDataProvider):
def __init__(self,logger,qm,bands):
# -----------------------------------------------------------------------------------
# Class for managing data for Hour/Band chart
# -----------------------------------------------------------------------------------
class HourBand(BaseDataProvider):
def __init__(self, logger, qm, bands):
# Calling constructor of base class
super().__init__(logger,qm,[],bands)
super().__init__(logger, qm, [], bands)
def __load_data(self):
@ -340,25 +372,42 @@ class HourBand(BaseDataProvider):
self.logger.info("doing query...")
self.logger.debug(self.bands)
#construct bands query
bands_qry_string = 'CASE '
# construct bands query
bands_qry_string = "CASE "
for i in range(len(self.bands["bands"])):
bands_qry_string+=' WHEN freq between '+str(self.bands["bands"][i]["min"])+' AND '+ str(self.bands["bands"][i]["max"])
bands_qry_string+=' THEN "'+self.bands["bands"][i]["id"]+'"'
bands_qry_string += (
" WHEN freq between "
+ str(self.bands["bands"][i]["min"])
+ " AND "
+ str(self.bands["bands"][i]["max"])
)
bands_qry_string += ' THEN "' + self.bands["bands"][i]["id"] + '"'
#construct bands query weight
bands_weight_qry_string = 'CASE '
# construct bands query weight
bands_weight_qry_string = "CASE "
for i in range(len(self.bands["bands"])):
bands_weight_qry_string+=' WHEN freq between '+str(self.bands["bands"][i]["min"])+' AND '+ str(self.bands["bands"][i]["max"])
bands_weight_qry_string+=' THEN "'+str(self.bands["bands"][i]["min"])+'"'
bands_weight_qry_string += (
" WHEN freq between "
+ str(self.bands["bands"][i]["min"])
+ " AND "
+ str(self.bands["bands"][i]["max"])
)
bands_weight_qry_string += (
' THEN "' + str(self.bands["bands"][i]["min"]) + '"'
)
#construct final query string
qry_string ="""
# construct final query string
qry_string = (
"""
select s1.band, s1.hour, s1.total from (
SELECT
cast(concat(HOUR (FROM_UNIXTIME(time))) as unsigned) as hour,
"""+bands_qry_string+""" ELSE "other" END as band,
cast("""+bands_weight_qry_string+""" ELSE 0 END as unsigned) as band_weight,
"""
+ bands_qry_string
+ """ ELSE "other" END as band,
cast("""
+ bands_weight_qry_string
+ """ ELSE 0 END as unsigned) as band_weight,
count(0) AS total
from spot
WHERE FROM_UNIXTIME(time) > DATE_SUB(now(), INTERVAL 1 MONTH)
@ -368,34 +417,37 @@ class HourBand(BaseDataProvider):
order by s1.band, s1.hour
;
"""
)
self.logger.debug(qry_string)
self.qm.qry(qry_string)
data=self.qm.get_data()
if len(data)==0:
data = self.qm.get_data()
if len(data) == 0:
self.logger.warning("no data found")
self.logger.info("query done")
self.logger.debug (data)
self.logger.debug(data)
return data
def refresh(self):
super().refresh()
lcl_data={}
qry_data=self.__load_data()
lcl_data = {}
qry_data = self.__load_data()
for i,j,k in qry_data:
for i, j, k in qry_data:
if i not in lcl_data:
lcl_data[i]={}
lcl_data[i].update({j:k})
lcl_data[i] = {}
lcl_data[i].update({j: k})
self.logger.debug(lcl_data)
self.glb_data=lcl_data
self.glb_last_refresh=time.time()
self.glb_data = lcl_data
self.glb_last_refresh = time.time()
threading.Timer(60*60*24,self.refresh).start() #periodic refresh: set time
threading.Timer(
60 * 60 * 24, self.refresh
).start() # periodic refresh: set time
return
def get_data(self):
@ -404,24 +456,24 @@ class HourBand(BaseDataProvider):
return self.glb_response
#-----------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------
# Class for managing data for World DX SPOTS current activity
#-----------------------------------------------------------------------------------
# -----------------------------------------------------------------------------------
class WorldDxSpotsLive(BaseDataProvider):
global glb_pfxt
def __init__(self,logger,qm,pfxt):
def __init__(self, logger, qm, pfxt):
# Calling constructor of base class
self.glb_pfxt=pfxt
super().__init__(logger,qm,[],[])
self.glb_pfxt = pfxt
super().__init__(logger, qm, [], [])
def __load_data(self):
self.logger.info("Start")
self.logger.info("doing query...")
#construct final query string
qry_string ="""
# construct final query string
qry_string = """
select spotcall as dx
from spot
WHERE FROM_UNIXTIME(time) > DATE_SUB(now(), INTERVAL 1 HOUR)
@ -431,63 +483,63 @@ class WorldDxSpotsLive(BaseDataProvider):
self.logger.debug(qry_string)
self.qm.qry(qry_string)
data=self.qm.get_data()
row_headers=self.qm.get_headers()
if len(data)==0:
data = self.qm.get_data()
row_headers = self.qm.get_headers()
if len(data) == 0:
self.logger.warning("no data found")
self.logger.info("query done")
self.logger.debug (data)
self.logger.debug(data)
#define country table for search info on callsigns
df = pd.DataFrame(columns=['row_id','dx','lat','lon'])
dx=[]
lat=[]
lon=[]
row_id=[]
idx=0
# define country table for search info on callsigns
df = pd.DataFrame(columns=["row_id", "dx", "lat", "lon"])
dx = []
lat = []
lon = []
row_id = []
idx = 0
for result in data:
main_result=dict(zip(row_headers,result))
main_result = dict(zip(row_headers, result))
# find the country in prefix table
search_prefix=self.glb_pfxt.find(main_result["dx"])
if search_prefix["country"] != "unknown country" :
search_prefix = self.glb_pfxt.find(main_result["dx"])
if search_prefix["country"] != "unknown country":
# merge recordset and contry prefix
dx.append(main_result["dx"])
lon.append(float(search_prefix["lat"]))
lat.append(-float(search_prefix["lon"]))
idx+=1
idx += 1
row_id.append(idx)
df['dx']=dx
df['lat']=lat
df['lon']=lon
df['row_id']=row_id
df_grp=df.groupby(["lat", "lon"])["row_id"].count().reset_index(name="count")
df["dx"] = dx
df["lat"] = lat
df["lon"] = lon
df["row_id"] = row_id
df_grp = df.groupby(["lat", "lon"])["row_id"].count().reset_index(name="count")
if df is None ==0:
if df is None == 0:
logger.warning("no data found")
return df_grp
def refresh(self):
super().refresh()
lcl_data={}
qry_data=self.__load_data()
lcl_data = {}
qry_data = self.__load_data()
self.logger.debug(qry_data)
lcl_data=[]
lcl_data = []
for index, row in qry_data.iterrows():
record=dict(lat=row['lat'], lon=row['lon'], count=row['count'])
record = dict(lat=row["lat"], lon=row["lon"], count=row["count"])
lcl_data.append(record)
self.logger.debug(lcl_data)
self.glb_data=lcl_data
self.glb_last_refresh=time.time()
self.glb_data = lcl_data
self.glb_last_refresh = time.time()
threading.Timer(5*60,self.refresh).start() #periodic refresh: set time
threading.Timer(5 * 60, self.refresh).start() # periodic refresh: set time
return
def get_data(self):

View File

@ -1,55 +1,63 @@
#*****************************************************************************************
# *****************************************************************************************
# module used to make query to mysql
# TODO: manage polymorfism and use only one qry sign
#*****************************************************************************************
#import MySQLdb as my
# *****************************************************************************************
# import MySQLdb as my
import mysql.connector as my
from mysql.connector import pooling
import logging
import json
import pandas as pd
logging.basicConfig(level=logging.INFO,format='%(asctime)s [%(levelname)s]: %(message)s',datefmt='%m/%d/%Y %I:%M:%S')
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s]: %(message)s",
datefmt="%m/%d/%Y %I:%M:%S",
)
class query_manager:
#connection definition
# connection definition
def __init__(self):
try:
with open('../cfg/config.json') as json_data_file:
with open("../cfg/config.json") as json_data_file:
cfg = json.load(json_data_file)
except Exception as e1:
logging.info(e1)
logging.info('trying with other path...')
logging.info("trying with other path...")
try:
with open ('cfg/config.json') as json_data_file:
with open("cfg/config.json") as json_data_file:
cfg = json.load(json_data_file)
except Exception as e2:
logging.error(e2)
return
logging.info('config file loaded')
self.__cnxpool = pooling.MySQLConnectionPool(host=cfg['mysql']['host'],
user=cfg['mysql']['user'],
passwd=cfg['mysql']['passwd'],
db=cfg['mysql']['db'],
charset='latin1',
# charset='utf8mb4',
# collation = 'utf8mb4_general_ci',
pool_name = "spider_pool",
use_pure = True,
pool_size = 3
logging.info("config file loaded")
self.__cnxpool = pooling.MySQLConnectionPool(
host=cfg["mysql"]["host"],
user=cfg["mysql"]["user"],
passwd=cfg["mysql"]["passwd"],
db=cfg["mysql"]["db"],
charset="latin1",
# charset='utf8mb4',
# collation = 'utf8mb4_general_ci',
pool_name="spider_pool",
use_pure=True,
pool_size=3,
)
logging.info('db connection pool created')
logging.info("db connection pool created")
#normal query
def qry(self,qs,prepared_statement=False):
# normal query
def qry(self, qs, prepared_statement=False):
try:
cnx=self.__cnxpool.get_connection()
cnx = self.__cnxpool.get_connection()
cursor = cnx.cursor(prepared=prepared_statement)
cursor.execute(qs)
self.__data=cursor.fetchall()
self.__row_headers=[x[0] for x in cursor.description] #this will extract row headers
self.__data = cursor.fetchall()
self.__row_headers = [
x[0] for x in cursor.description
] # this will extract row headers
cursor.close()
except Exception as e2:
logging.error(e2)
@ -62,11 +70,11 @@ class query_manager:
def get_headers(self):
return self.__row_headers
#query with pandas
def qry_pd(self,qs):
# query with pandas
def qry_pd(self, qs):
try:
cnx=self.__cnxpool.get_connection()
self.__data = pd.read_sql(qs,con=cnx)
cnx = self.__cnxpool.get_connection()
self.__data = pd.read_sql(qs, con=cnx)
except Exception as e2:
logging.error(e2)
finally:

View File

@ -1,15 +1,20 @@
#
# little script used to build static pages
#
__author__ = 'IU1BOW - Corrado'
__author__ = "IU1BOW - Corrado"
from staticjinja import Site
def cookies_check():
return False
if __name__ == "__main__":
site = Site.make_site(searchpath="../static/html/templates/",outpath="../static/html/",env_globals={
'cookies_check':cookies_check,
})
site = Site.make_site(
searchpath="../static/html/dev/",
outpath="../static/html/rel/",
env_globals={
"cookies_check": cookies_check,
},
)
site.render(use_reloader=False)

View File

@ -1 +1 @@
nohup ./test.sh >/dev/null 2>&1 &
nohup ./test.sh -r >/dev/null 2>&1 &

View File

@ -1,5 +1,7 @@
astroid==2.12.14
charset-normalizer==2.1.1
click==8.1.3
dill==0.3.6
docopt-ng==0.8.1
easywatch==0.0.5
Flask==2.2.2
@ -7,14 +9,18 @@ Flask-Minify==0.41
Flask-WTF==1.0.1
htmlmin==0.1.12
idna==3.4
isort==5.11.4
itsdangerous==2.1.2
Jinja2==3.1.2
jsmin==3.0.1
lazy-object-proxy==1.9.0
lesscpy==0.15.1
MarkupSafe==2.1.1
mccabe==0.7.0
mysql-connector-python==8.0.31
numpy==1.24.1
pandas==1.5.2
platformdirs==2.6.2
ply==3.11
protobuf==4.21.12
python-dateutil==2.8.2
@ -22,8 +28,10 @@ pytz==2022.7
rcssmin==1.1.1
requests==2.28.1
six==1.16.0
tomlkit==0.11.6
urllib3==1.26.13
watchdog==2.2.0
Werkzeug==2.2.2
wrapt==1.14.1
WTForms==3.0.1
xxhash==3.1.0

View File

@ -8,13 +8,141 @@ path_static='../static'
path_static_html=${path_static}'/html'
path_static_js=${path_static}'/js'
path_static_css=${path_static}'/css'
path_cfg='../cfg'
app_ini=${path_cfg}'/webapp_log_config.ini'
path_docs='../docs'
readme='../README.md'
manifest=${path_static}'/manifest.webmanifest'
changelog=${path_docs}'/'CHANGELOG.md
#echo '*** SPIDERWEB building process ***'
html_change_references(){
for i in ${path_templates}/*.html
do
echo "changing references to scripts in ${i}"
if [ "${1}" == "-r" ]; then
# concatenating 2 sed command with ";"
# 1) if found static/js/dev/ replace .js with .min.js
# 2) replace static/js/dev/ with static/js/rel/
if ! sed -i '/static\/js\/dev/s/\.js/\.min\.js/;s/static\/js\/dev/static\/js\/rel/g' ${i}; then
echo 'ERROR replacing .js to .min.js '
exit 6
fi
# the same but for css
if ! sed -i '/static\/css\/dev/s/\.css/\.min\.css/;s/static\/css\/dev/static\/css\/rel/g' ${i}; then
echo 'ERROR replacing .css to .min.css '
exit 6
fi
elif [ "${1}" == "-d" ]; then
# concatenating 2 sed command with ";"
# 1) if found static/js/rel/ replace .min.js with .js
# 2) replace static/js/rel/ with static/js/dev/
if ! sed -i '/static\/js\/rel/s/\.min\.js/\.js/;s/static\/js\/rel/static\/js\/dev/g' ${i}; then
echo 'ERROR replacing .min.js to .js'
exit 6
fi
# the same but for css
if ! sed -i '/static\/css\/rel/s/\.min\.css/\.css/;s/static\/css\/rel/static\/css\/dev/g' ${i}; then
echo 'ERROR replacing .min.css to .css'
exit 6
fi
fi
done
}
if [ "$1" != "-r" ] && [ "$1" != "-d" ]; then
echo 'wrong options:'
echo ' -d: debug'
echo ' -r: release'
exit 5
fi
echo '*** SPIDERWEB building process ***'
if [ "$1" == "-r" ]; then
echo 'creating RELEASE application'
#used to minify the application javascript
echo 'minify javascripts...'
shopt -s extglob
rm ${path_static_js}/rel/*.js
for i in ${path_static_js}/dev/*.js
do
[[ -e ${i} ]] || break # handle the case of no files found
file_no_ext=$(basename "${i%.js}")
out=${path_static_js}/rel/${file_no_ext}.min.js
echo "${i} --> ${out}"
if ! uglifyjs --compress --mangle -- ${i} > ${out}
then
echo 'ERROR minifying javascript: '${i}
shopt -u extglob
exit 80
fi
if [ ! -s "${out}" ]; then
echo "File is empty"
shopt -u extglob
exit 81
fi
done
#used to minify css
echo 'minify css...'
rm ${path_static_css}/rel/*.css
for i in ${path_static_css}/dev/*.css
do
[[ -e ${i} ]] || break # handle the case of no files found
echo ${i}
file_no_ext=$(basename "${i%.css}")
out=${path_static_css}/rel/${file_no_ext}.min.css
echo "${i} --> ${out}"
#if ! curl -X POST -s --fail --compressed --data "code_type=css" --data-urlencode 'code@'${i} https://htmlcompressor.com/compress > ${path_static_css}/rel/${file_no_ext}.min.css
if ! css-minify -f ${i} -o ${path_static_css}/rel/
then
echo 'ERROR minifying css: ' ${out}
shopt -u extglob
exit 90
fi
if [ ! -s "${out}" ]; then
echo "File is empty"
shopt -u extglob
exit 91
fi
#sleep 5
done
shopt -u extglob
html_change_references -r
echo 'writing requirements...'
if ! pip freeze|tee ../requirements.txt
then
echo 'ERROR wrinting requirements'
exit 60
fi
echo 'remove some packages from requirements...'
sed -i '/certifi==/d' ../requirements.txt
sed -i '/staticjinja==/d' ../requirements.txt
if ! sed -i 's/level=DEBUG/level=INFO/g' ${app_ini}; then
echo 'ERROR settimg loglevel=INFO '
exit 12
fi
fi
if [ "$1" == "-d" ]; then
echo 'creating DEBUG application'
html_change_references -d
if ! sed -i 's/level=INFO/level=DEBUG/g' ${app_ini}; then
echo 'ERROR settimg loglevel=DEBUG '
exit 12
fi
fi
#echo 'create modes.json from dxspider bands.pl'
#python ../lib/get_dxcluster_modes.py /home/sysop/spider/data/bands.pl
#if [ "$?" != "0" ]; then
# echo 'ERROR on creating modes.json from dxspider bands.pl'
@ -55,7 +183,7 @@ then
fi
echo 'writing date in '${changelog} '...'
#sed -i '1,4s/Date: [0-9][0-9]\/[0-9][0-9]\/[0-9][0-9][0-9][0-9]/Date: '`date '+%d\/%m\/%Y'`'/g' ${changelog}
if ! sed -i '1,4s/Date: [0-9][0-9]\/[0-9][0-9]\/[0-9][0-9][0-9][0-9]/Date: '$(date '+%d\/%m\/%Y')'/g' ${changelog}
then
echo 'ERROR writing date in '${changelog}
@ -76,58 +204,5 @@ then
exit 50
fi
echo 'writing requirements...'
if ! pip freeze|tee ../requirements.txt
then
echo 'ERROR wrinting requirements'
exit 60
fi
echo 'remove some packages...'
sed -i '/certifi==/d' ../requirements.txt
sed -i '/staticjinja==/d' ../requirements.txt
#used to minify the application javascript
echo 'minify javascripts...'
shopt -s extglob
for i in ${path_static_js}/!(*[m][i][n].js|*.md)
do
[[ -e ${i} ]] || break # handle the case of no files found
echo ${i}
file_no_ext=$(basename "${i%.js}")
if ! curl -X POST -s --fail --compressed --data "code_type=js" --data "js_engine=closure" --data-urlencode 'code@'${i} https://htmlcompressor.com/compress > ${path_static_js}/${file_no_ext}.min.js
then
echo 'ERROR minifying javascript: '${i}
shopt -u extglob
exit 80
fi
if [ ! -s "${path_static_js}/${file_no_ext}.min.js" ]; then
echo "File is empty"
shopt -u extglob
exit 81
fi
sleep 3
done
#used to minify css
echo 'minify css...'
for i in ${path_static_css}/!(*[m][i][n].css|*.md)
do
[[ -e ${i} ]] || break # handle the case of no files found
echo ${i}
file_no_ext=$(basename "${i%.css}")
if ! curl -X POST -s --fail --compressed --data "code_type=css" --data-urlencode 'code@'${i} https://htmlcompressor.com/compress > ${path_static_css}/${file_no_ext}.min.css
then
echo 'ERROR minifying css: '${i}
shopt -u extglob
exit 90
fi
if [ ! -s "${path_static_css}/${file_no_ext}.min.css" ]; then
echo "File is empty"
shopt -u extglob
exit 91
fi
sleep 3
done
shopt -u extglob
echo
echo Build ok

2
static/css/dev/README.md Normal file
View File

@ -0,0 +1,2 @@
This folder `static/css/dev/` contains the style scripts for **development** environment.
You can change these scripts as you want and rebuild the application

138
static/css/dev/style.css Normal file
View File

@ -0,0 +1,138 @@
@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.1/font/bootstrap-icons.css");
@font-face {
font-display: swap;
font-family: 'bootstrap-icons';
}
.badge-responsive {
width: 70px;
}
@media screen and (max-width: 768px) {
.text-responsive {
font-size: 11px;
}
.badge-responsive {
width: 40px;
}
}
@media screen and (max-width: 768px) {
#collapseFilters.collapsing {
position: absolute !important;
z-index: 20;
}
#collapseFilters.collapse.show {
display: block;
position: absolute;
z-index: 20;
}
.navbar-collapse {
max-height: none !important;
}
}
.img-flag {
background-color: white;
border: 1px solid #ddd;
border-radius: 2px;
padding: 3px;
background-size: cover !important;
max-width: auto;
max-height: auto;
height: 19px !important;
width: 32px !important;
-webkit-box-shadow: 0 2px 10px rgba(0, 0, 0, .5), 0 2px 3px rgba(0, 0, 0, .5);
-moz-box-shadow: 0 2px 10px rgba(0, 0, 0, .5), 0 2px 3px rgba(0, 0, 0, .5);
-o-box-shadow: 0 2px 10px rgba(0, 0, 0, .5), 0 2px 3px rgba(0, 0, 0, .5);
box-shadow: 0 2px 10px rgba(0, 0, 0, .5), 0 2px 3px rgba(0, 0, 0, .5);
}
.ipcs {
background-image: url("/static/images/background.webp");
background-size: cover;
background-repeat: no-repeat;
}
.copyleft {
display: inline-block;
transform: rotate(180deg);
}
span.search-callsign {
background: url(/static/images/search-callsign.svg) no-repeat top left;
background-size: contain;
cursor: pointer;
display: inline-block;
height: 16px;
width: 20px;
}
#input-group-callsign {
margin-right: 1rem;
margin-bottom: 0.5rem;
}
#collapseFilters {
margin-top: 10px;
background-color: #DDE2E6;
}
#spotsTable {
margin-top: 10px;
}
#band {
margin-top: 5px;
}
#dashboard {
padding: 10px;
gap: 10px;
}
#telnet-thead {
position: sticky;
top: 0
}
#form-band_activity {
width: 600px;
height: 500px;
}
#chart-band_activity {
width: 100%;
height: 400px
}
#chart-world_dx_spots_live {
width: 600px;
height: 500px;
gap: 0px;
}
#chart-hour_band {
width: 600px;
height: 500px;
}
#silo-propagation-img {
width: 480px;
height: 400px;
}
#chart-dx_spots_x_month {
width: 600px;
height: 400px;
}
#chart-dx_spots_trend {
width: 700px;
height: 400px;
}

2
static/css/rel/README.md Normal file
View File

@ -0,0 +1,2 @@
This folder `static/css/rel/` contains the style scripts for **release** environment.
You **CANNOT change** these scripts. Make changes only in `static/css/dev/` folder

1
static/css/rel/style.min.css vendored Normal file
View File

@ -0,0 +1 @@
@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.1/font/bootstrap-icons.css");@font-face{font-display:swap;font-family:bootstrap-icons}.badge-responsive{width:70px}@media screen and (max-width:768px){.text-responsive{font-size:11px}.badge-responsive{width:40px}#collapseFilters.collapsing{position:absolute!important;z-index:20}#collapseFilters.collapse.show{display:block;position:absolute;z-index:20}.navbar-collapse{max-height:none!important}}.img-flag{background-color:#fff;background-size:cover!important;border:1px solid #ddd;border-radius:2px;-webkit-box-shadow:0 2px 10px rgba(0,0,0,.5),0 2px 3px rgba(0,0,0,.5);-moz-box-shadow:0 2px 10px rgba(0,0,0,.5),0 2px 3px rgba(0,0,0,.5);-o-box-shadow:0 2px 10px rgba(0,0,0,.5),0 2px 3px rgba(0,0,0,.5);box-shadow:0 2px 10px rgba(0,0,0,.5),0 2px 3px rgba(0,0,0,.5);height:19px!important;max-height:auto;max-width:auto;padding:3px;width:32px!important}.ipcs{background-image:url(/static/images/background.webp);background-repeat:no-repeat;background-size:cover}.copyleft{display:inline-block;transform:rotate(180deg)}span.search-callsign{background:url(/static/images/search-callsign.svg) no-repeat 0 0;background-size:contain;cursor:pointer;display:inline-block;height:16px;width:20px}#input-group-callsign{margin-bottom:.5rem;margin-right:1rem}#collapseFilters{background-color:#dde2e6;margin-top:10px}#spotsTable{margin-top:10px}#band{margin-top:5px}#dashboard{gap:10px;padding:10px}#telnet-thead{position:sticky;top:0}#form-band_activity{height:500px;width:600px}#chart-band_activity{height:400px;width:100%}#chart-world_dx_spots_live{gap:0;height:500px;width:600px}#chart-hour_band{height:500px;width:600px}#silo-propagation-img{height:400px;width:480px}#chart-dx_spots_x_month{height:400px;width:600px}#chart-dx_spots_trend{height:400px;width:700px}

View File

@ -1,128 +0,0 @@
@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.1/font/bootstrap-icons.css");
@font-face {
font-display: swap;
font-family: 'bootstrap-icons';
}
.badge-responsive {
width: 70px;
}
@media screen and (max-width: 768px) {
.text-responsive {
font-size: 11px;
}
.badge-responsive {
width: 40px;
}
}
@media screen and (max-width: 768px) {
#collapseFilters.collapsing {
position: absolute !important;
z-index: 20;
}
#collapseFilters.collapse.show {
display: block;
position: absolute;
z-index: 20;
}
.navbar-collapse {
max-height: none !important;
}
}
.img-flag {
background-color: white;
border: 1px solid #ddd;
border-radius: 2px;
padding: 3px;
background-size:cover !important;
max-width: auto;
max-height: auto;
height: 19px !important;
width: 32px !important;
-webkit-box-shadow: 0 2px 10px rgba(0, 0, 0, .5), 0 2px 3px rgba(0, 0, 0, .5);
-moz-box-shadow: 0 2px 10px rgba(0, 0, 0, .5), 0 2px 3px rgba(0, 0, 0, .5);
-o-box-shadow: 0 2px 10px rgba(0, 0, 0, .5), 0 2px 3px rgba(0, 0, 0, .5);
box-shadow: 0 2px 10px rgba(0, 0, 0, .5), 0 2px 3px rgba(0, 0, 0, .5);
}
.ipcs {
background-image: url("/static/images/background.webp");
background-size: cover;
background-repeat: no-repeat;
}
.copyleft {
display: inline-block;
transform: rotate(180deg);
}
span.search-callsign {
background: url(/static/images/search-callsign.svg) no-repeat top left;
background-size: contain;
cursor: pointer;
display: inline-block;
height: 16px;
width: 20px;
}
#input-group-callsign {
margin-right: 1rem;
margin-bottom: 0.5rem;
}
#collapseFilters {
margin-top: 10px;
background-color: #DDE2E6;
}
#spotsTable {
margin-top: 10px;
}
#band {
margin-top: 5px;
}
#dashboard {
padding:10px;
gap:10px;
}
#telnet-thead {
position: sticky;top: 0
}
#form-band_activity {
width: 600px; height: 500px;
}
#chart-band_activity {
width:100%; height:400px
}
#chart-world_dx_spots_live {
width: 600px; height: 500px; gap: 0px;
}
#chart-hour_band {
width: 600px; height: 500px;
}
#silo-propagation-img {
width: 480px; height: 400px;
}
#chart-dx_spots_x_month {
width: 600px; height: 400px;
}
#chart-dx_spots_trend {
width: 700px; height: 400px;
}

View File

@ -1 +0,0 @@
@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.1/font/bootstrap-icons.css");@font-face{font-display:swap;font-family:'bootstrap-icons'}.badge-responsive{width:70px}@media screen and (max-width:768px){.text-responsive{font-size:11px}.badge-responsive{width:40px}}@media screen and (max-width:768px){#collapseFilters.collapsing{position:absolute!important;z-index:20}#collapseFilters.collapse.show{display:block;position:absolute;z-index:20}.navbar-collapse{max-height:none!important}}.img-flag{background-color:white;border:1px solid #ddd;border-radius:2px;padding:3px;background-size:cover!important;max-width:auto;max-height:auto;height:19px!important;width:32px!important;-webkit-box-shadow:0 2px 10px rgba(0,0,0,.5),0 2px 3px rgba(0,0,0,.5);-moz-box-shadow:0 2px 10px rgba(0,0,0,.5),0 2px 3px rgba(0,0,0,.5);-o-box-shadow:0 2px 10px rgba(0,0,0,.5),0 2px 3px rgba(0,0,0,.5);box-shadow:0 2px 10px rgba(0,0,0,.5),0 2px 3px rgba(0,0,0,.5)}.ipcs{background-image:url("/static/images/background.webp");background-size:cover;background-repeat:no-repeat}.copyleft{display:inline-block;transform:rotate(180deg)}span.search-callsign{background:url(/static/images/search-callsign.svg) no-repeat top left;background-size:contain;cursor:pointer;display:inline-block;height:16px;width:20px}#input-group-callsign{margin-right:1rem;margin-bottom:.5rem}#collapseFilters{margin-top:10px;background-color:#dde2e6}#spotsTable{margin-top:10px}#band{margin-top:5px}#dashboard{padding:10px;gap:10px}#telnet-thead{position:sticky;top:0}#form-band_activity{width:600px;height:500px}#chart-band_activity{width:100%;height:400px}#chart-world_dx_spots_live{width:600px;height:500px;gap:0}#chart-hour_band{width:600px;height:500px}#silo-propagation-img{width:480px;height:400px}#chart-dx_spots_x_month{width:600px;height:400px}#chart-dx_spots_trend{width:700px;height:400px}

File diff suppressed because it is too large Load Diff

1
static/html/dev/_base.html Symbolic link
View File

@ -0,0 +1 @@
../../../templates/_base.html

View File

@ -0,0 +1 @@
../../../templates/offline.html

View File

@ -16,7 +16,7 @@
<link rel="icon" href="/static/images/icons/spider_ico_master.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/images/icons/icon-apple.png">
<link rel="manifest" href="/static/manifest.webmanifest">
<link rel="stylesheet" href="/static/css/style.min.css">
<link rel="stylesheet" href="/static/css/dev/style.css">
<link rel="preload" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" as="style" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous" onload="this.rel='stylesheet' ">
<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css"></noscript>
@ -24,7 +24,7 @@
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/6.6.6/css/flag-icons.min.css" as="style" integrity="sha512-uvXdJud8WaOlQFjlz9B15Yy2Au/bMAvz79F7Xa6OakCl2jvQPdHD0hb3dEqZRdSwG4/sknePXlE7GiarwA/9Wg==" crossorigin="anonymous" onload="this.rel='stylesheet'" >
<noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/6.6.6/css/flag-icons.min.css"></noscript>
<script src="static/js/load_css.min.js"></script>
<script src="static/js/dev/load_css.js"></script>
</head>
@ -47,7 +47,7 @@
</ul>
<div id="MyClockDisplay" onload="showTime()" class="text-white-50 d-none d-lg-block"></div>
<div class="text-white-50 d-none d-lg-block" >&nbsp (UTC) &nbsp &nbsp</div>
<div class="text-white-50 d-none d-lg-block" >&nbsp;(UTC)&nbsp;&nbsp;</div>
</div>
@ -61,8 +61,8 @@
<div class="container-fluid mt-1 ml-0 mr-0 px-0">
<div class="ipcs bg-light p-4 rounded-lg m-2">
<h1 class="display-4 text-white">WEB DX Cluster</h1>
<p class="lead text-light">Spots list</p>
<h1 class="display-4 text-white">WEB DX Cluster</h1>
<p class="lead text-light">Spots list</p>
<p class="text-light">Telnet access: <a href="telnet://" class="text-white"></a></p>
<p class="text-light">For connect your cluster, write to <a href="mailto:?Subject=Connect%20my%20DxCluster%20node" target="_top" class="text-white"></a></p>
@ -73,13 +73,14 @@
<!-- <div class="container">
<div class="row">
<div class="col align-self-center"> -->
<div class="jumbotron alert alert-warning" role="alert">
<div class="jumbotron alert alert-warning" role="alert">
<h2 class="display-4">No internet connection</h2>
<p class="lead">The features in this area require Internet connectivity. Please connect your computer to the Internet</p>
<p class="lead">The features in this area require Internet connectivity. Please connect your computer to the
Internet</p>
<p class="lead">
<a class="btn btn-primary btn-lg" href="/" role="button">Try again</a>
</p>
<!-- </div>
<!-- </div>
</div>
</div> -->
</div>
@ -93,40 +94,43 @@
<span id="version">v2.4.1</span>
</div>
</footer>
<script async src="static/js/clock.min.js"></script>
<script async src="static/js/copy_date.min.js"></script>
<script async src="static/js/load-sw.min.js"></script>
<script async src="static/js/dev/clock.js"></script>
<script async src="static/js/dev/copy_date.js"></script>
<script async src="static/js/dev/load-sw.js"></script>
<script nonce="sedfGFG32xs">
</script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script defer src="static/js/dev/common.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
<script async src="static/js/callsign_search.min.js"></script>
<script async src="static/js/dev/callsign_search.js"></script>
<!-- cookie consent management -->
<div class="modal" tabindex="-1" id="cookie-consent-container">
<!-- Modal for cookie consent-->
<div class="modal fade" id="cookie_consent_modal" tabindex="-1" aria-labelledby="cookie-consent-container" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title">We use cookies</h1>
<h5 class="modal-title" id="exampleModalLabel">We use cookies</h5>
</div>
<div class="modal-body">
<p>We use only technical cookies.</p>
<p>Clicking &quot;I agree&quot;, you agree to the storing of cookies on your device. To learn more about how we use cookies, please see our cookies policy.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="cookie-consent">I agree</button>
<button type="button" class="btn btn-primary" id="cookie_consent_btn">I agree</button>
</div>
</div>
</div>
</div>
<script defer src="static/js/cookie_consent.min.js"></script>
<script defer src="static/js/dev/cookie_consent.js"></script>
</body>
</html>

View File

@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>DX Cluster from IU1BOW: OFFLINE</title>
<!-- page generated by staticjinja -->
<meta charset="utf-8">
<meta name="description" content="Web Ham Radio DX Cluster and spot search">
<meta name="keywords" content="ham radio, dx cluster, dx spots, cluster sposts,web dx cluster,dx cluster search, DX spots">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#2196f3">
<link rel="icon" href="/static/images/icons/favicon.ico" size="any">
<link rel="icon" href="/static/images/icons/spider_ico_master.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/images/icons/icon-apple.png">
<link rel="manifest" href="/static/manifest.webmanifest">
<link rel="stylesheet" href="/static/css/rel/style.min.css">
<link rel="preload" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" as="style" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous" onload="this.rel='stylesheet' ">
<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css"></noscript>
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/6.6.6/css/flag-icons.min.css" as="style" integrity="sha512-uvXdJud8WaOlQFjlz9B15Yy2Au/bMAvz79F7Xa6OakCl2jvQPdHD0hb3dEqZRdSwG4/sknePXlE7GiarwA/9Wg==" crossorigin="anonymous" onload="this.rel='stylesheet'" >
<noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/6.6.6/css/flag-icons.min.css"></noscript>
<script src="static/js/rel/load_css.min.js"></script>
</head>
<body>
<header>
<!-- nav bar -->
<nav class="navbar px-2 navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<img src="/static/images/icons/icon-72x72.png" width="30" height="30" class="d-inline-block align-top" alt="">
</a>
<button class="navbar-toggler" type="button" aria-controls="navbarToggler01" aria-expanded="false" aria-label="Toggle navigation" data-bs-toggle="collapse" data-bs-target="#navbarToggler01" >
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarToggler01">
<ul class="navbar-nav me-auto mb-2 mb-lg-0" >
</ul>
<div id="MyClockDisplay" onload="showTime()" class="text-white-50 d-none d-lg-block"></div>
<div class="text-white-50 d-none d-lg-block" >&nbsp;(UTC)&nbsp;&nbsp;</div>
</div>
</div>
</nav>
</header>
<div class="container-fluid mt-1 ml-0 mr-0 px-0">
<div class="ipcs bg-light p-4 rounded-lg m-2">
<h1 class="display-4 text-white">WEB DX Cluster</h1>
<p class="lead text-light">Spots list</p>
<p class="text-light">Telnet access: <a href="telnet://" class="text-white"></a></p>
<p class="text-light">For connect your cluster, write to <a href="mailto:?Subject=Connect%20my%20DxCluster%20node" target="_top" class="text-white"></a></p>
</div>
<!-- <div class="container">
<div class="row">
<div class="col align-self-center"> -->
<div class="jumbotron alert alert-warning" role="alert">
<h2 class="display-4">No internet connection</h2>
<p class="lead">The features in this area require Internet connectivity. Please connect your computer to the
Internet</p>
<p class="lead">
<a class="btn btn-primary btn-lg" href="/" role="button">Try again</a>
</p>
<!-- </div>
</div>
</div> -->
</div>
</div>
<footer class="page-footer font-small blue">
<div class="footer-copyright text-center py-3">
<span class="copyleft">&copy;</span> Copyleft:
<span id="copyDate"></span>
<a href="https://github.com/coulisse/spiderweb/" target="blank" rel="noopener">IU1BOW Spiderweb</a>
<span id="version">v2.4.1</span>
</div>
</footer>
<script async src="static/js/rel/clock.min.js"></script>
<script async src="static/js/rel/copy_date.min.js"></script>
<script async src="static/js/rel/load-sw.min.js"></script>
<script nonce="sedfGFG32xs">
</script>
<script defer src="static/js/rel/common.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
<script async src="static/js/rel/callsign_search.min.js"></script>
<!-- cookie consent management -->
<!-- Modal for cookie consent-->
<div class="modal fade" id="cookie_consent_modal" tabindex="-1" aria-labelledby="cookie-consent-container" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">We use cookies</h5>
</div>
<div class="modal-body">
<p>We use only technical cookies.</p>
<p>Clicking &quot;I agree&quot;, you agree to the storing of cookies on your device. To learn more about how we use cookies, please see our cookies policy.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="cookie_consent_btn">I agree</button>
</div>
</div>
</div>
</div>
<script defer src="static/js/rel/cookie_consent.min.js"></script>
</body>
</html>

View File

@ -1 +0,0 @@
Make changes only on file without '.min.' in their name, then minify using build.sh in 'script' folder

View File

@ -1,24 +0,0 @@
/*
* script loaded inline page in order to prepare data
* for next operations
*/
//var my_adxo_events=jQuery.parseJSON(my_adxo_events_json.replaceAll("\t",""));
var my_adxo_events=JSON.parse(my_adxo_events_json.replaceAll("\t",""));
var rows_list = new Array();
var qryString = 'spotlist?c='+my_callsign;
fetch(qryString)
.then((response) => response.json())
.then((data) => {
try {
//rows_list = buildHtmlTable('bodyspot',data, rows_list, my_callsign);
tb.build(data,my_callsign);
} catch (err) {
console.log(err);
console.log(err.stack);
console.log(data);
}
})

View File

@ -1 +0,0 @@
var my_adxo_events=jQuery.parseJSON(my_adxo_events_json.replaceAll("\t","")),rows_list=[];buildHtmlTable("#bodyspot",payload_json,rows_list,my_callsign);

View File

@ -1 +0,0 @@
function myCallsignSearch(){callsign=document.getElementById("callsignInput").value;0<callsign.replace(/\s/g,"").length&&(location.href="/callsign.html?c=".concat(callsign.trim().toUpperCase()))};

View File

@ -1,14 +0,0 @@
function showTime(){
var date=new Date()
var utc = new Date(date.getTime() + date.getTimezoneOffset() * 60000);
var time = utc.toTimeString().split(' ')[0];
time = time.split(':')[0]+':'+time.split(':')[1];
document.getElementById("MyClockDisplay").innerText = time;
document.getElementById("MyClockDisplay").textContent = time;
setTimeout(showTime, 1000);
}
showTime();

View File

@ -1 +0,0 @@
function showTime(){var a=new Date;a=(new Date(a.getTime()+6E4*a.getTimezoneOffset())).toTimeString().split(" ")[0];a=a.split(":")[0]+":"+a.split(":")[1];document.getElementById("MyClockDisplay").innerText=a;document.getElementById("MyClockDisplay").textContent=a;setTimeout(showTime,1E3)}showTime();

View File

@ -1,167 +0,0 @@
/**
* Create a cookie
*
* @param cname {String} cookie name
* @param cvalue {string} cookie value
* @param exdays {integer} the number of the days for cookie expiration
*/
function setCookie(cname, cvalue, exdays) {
const d = new Date();
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
let expires = "expires=" + d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/" + ";Samesite=Strict;Secure=True";
};
/**
* get a cookie
*
* @param cname {String} cookie name
* @returns cookie value
*/
function getCookie(cname) {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
/**
* format the data refresh string for exhibit on charts
*
* @param date {String} date of the last refresh
* @returns string formatted
*/
function get_last_refresh(data) {
var dt_refresh = new Date(0); // The 0 there is the key, which sets the date to the epoch
var dt_refresh;
dt_refresh.setUTCSeconds(data["last_refresh"]);
const pad = (n, s = 2) => (`${new Array(s).fill(0)}${n}`).slice(-s);
var hours = pad(dt_refresh.getHours());
var minutes = pad(dt_refresh.getMinutes());
var months_names = get_months_names();
var month = months_names[dt_refresh.getMonth()];
var day = dt_refresh.getDate();
var year = dt_refresh.getFullYear();
var last_refresh = "Data refresh: " + day + " of " + month + " " + year + " at " + hours + ":" + minutes;
return last_refresh;
}
/**
* format the data refresh string for exhibit on charts
*
* @returns {Array} list of months as short names
*/
function get_months_names() {
var months_name = [];
for (let monthNumber = 1; monthNumber < 13; monthNumber++) {
const date = new Date();
date.setMonth(monthNumber - 1);
months_name.push(date.toLocaleString('en-US', {
month: 'short'
}));
};
return months_name;
}
/**
* format the data refresh string for exhibit on charts
*
* @param {Integer} num Number to be formatted
* @returns {string} Number formatted with K for thousands and M for millions
*/
function format_u_k_m(num) {
let label;
let sign = 1;
if (num < 0) {
sign = -1;
}
let abs_num = Math.abs(num);
if (abs_num == 0) {
label = abs_num
} else {
if (abs_num > 0 && abs_num < 1000) {
label = abs_num * sign;
} else {
if (abs_num >= 1000 && abs_num < 1000000) {
label = abs_num / 1000 * sign + "K";
} else {
if (abs_num >= 1000000) {
label = abs_num / 1000000 * sign + "M";
}
}
}
}
return label;
}
/**
* Set a selected element of a combo box with a value
*
* @param {string} id of the selected
* @param {string} valueToSelect value to assign
*/
function selectElement(id, valueToSelect) {
let element = document.getElementById(id);
element.value = valueToSelect;
}
/**
* Add event Handler to element (on)
*
* @param {string} id of the element
* @param {string} eventType i.e. "change"
* @param {function} handler the function to execute whene the evenet is raised
*/
function addEventHandler(id, eventType, handler) {
if (id.addEventListener)
id.addEventListener(eventType, handler, false);
else if (elem.attachEvent)
id.attachEvent('on' + eventType, handler);
}
/**
* Set a text to a specific element
*
* @param {string} id of the element
* @param {string} newvalue the value to assign to element
*/
function setText(id, newvalue) {
var s = document.getElementById(id);
s.innerHTML = newvalue;
}
function openModal() {
document.getElementById("backdrop").style.display = "block"
document.getElementById("exampleModal").style.display = "block"
document.getElementById("exampleModal").classList.add("show")
}
function closeModal() {
document.getElementById("backdrop").style.display = "none"
document.getElementById("exampleModal").style.display = "none"
document.getElementById("exampleModal").classList.remove("show")
}
/*
function doRefresh(){
var chartDom = document.getElementById('chart-dx_spots_trend');
var myChart = echarts.init(chartDom);
plot_dst.refresh(myChart,'/plot_get_dx_spots_trend');
};
setInterval(function(){doRefresh()}, 5000);
*/

View File

@ -1 +0,0 @@
var fn=function(){document.cookie="cookie_consent=true;SameSite=Strict; Secure;max-age=2592000";$("#cookie-consent-container").modal("hide")};document.getElementById("cookie-consent").onclick=fn;$("#cookie-consent-container").modal({backdrop:"static",keyboard:!1});$("#cookie-consent-container").modal("show");

View File

@ -17,4 +17,4 @@ rules:
- single
semi:
- error
- never
- always

2
static/js/dev/README.md Normal file
View File

@ -0,0 +1,2 @@
This folder `static/js/dev/` contains the javascripts module for **development** environment.
You can change these scripts as you want and rebuild the application

View File

@ -0,0 +1,22 @@
/*
* script loaded inline page in order to prepare data
* for next operations
*/
//var my_adxo_events=jQuery.parseJSON(my_adxo_events_json.replaceAll("\t",""));
var my_adxo_events = JSON.parse(my_adxo_events_json.replaceAll('\t', ''));
var qryString = 'spotlist?c=' + my_callsign;
fetch(qryString)
.then((response) => response.json())
.then((data) => {
try {
tb.build(data, my_callsign);
} catch (err) {
console.log(err);
console.log(err.stack);
console.log(data);
}
});

12
static/js/dev/clock.js Normal file
View File

@ -0,0 +1,12 @@
function showTime(){
let date=new Date();
let utc = new Date(date.getTime() + date.getTimezoneOffset() * 60000);
let time = utc.toTimeString().split(' ')[0];
time = time.split(':')[0]+':'+time.split(':')[1];
document.getElementById("MyClockDisplay").innerText = time;
document.getElementById("MyClockDisplay").textContent = time;
setTimeout(showTime, 1000);
}
showTime();

155
static/js/dev/common.js Normal file
View File

@ -0,0 +1,155 @@
/**
* Create a cookie
*
* @param cname {String} cookie name
* @param cvalue {string} cookie value
* @param exdays {integer} the number of the days for cookie expiration
*/
function setCookie(cname, cvalue, exdays) {
const d = new Date();
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
let expires = 'expires=' + d.toUTCString();
document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/' + ';Samesite=Strict;Secure=True';
};
/**
* get a cookie
*
* @param cname {String} cookie name
* @returns cookie value
*/
function getCookie(cname) {
let name = cname + '=';
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return '';
}
/**
* format the data refresh string for exhibit on charts
*
* @param date {String} date of the last refresh
* @returns string formatted
*/
function get_last_refresh(data) {
let dt_refresh = new Date(0); // The 0 there is the key, which sets the date to the epoch
//var dt_refresh;
dt_refresh.setUTCSeconds(data["last_refresh"]);
const pad = (n, s = 2) => (`${new Array(s).fill(0)}${n}`).slice(-s);
const hours = pad(dt_refresh.getHours());
const minutes = pad(dt_refresh.getMinutes());
const months_names = get_months_names();
const month = months_names[dt_refresh.getMonth()];
const day = dt_refresh.getDate();
const year = dt_refresh.getFullYear();
const last_refresh = "Data refresh: " + day + " of " + month + " " + year + " at " + hours + ":" + minutes;
return last_refresh;
}
/**
* format the data refresh string for exhibit on charts
*
* @returns {Array} list of months as short names
*/
function get_months_names() {
var months_name = [];
for (let monthNumber = 1; monthNumber < 13; monthNumber++) {
const date = new Date();
date.setMonth(monthNumber - 1);
months_name.push(date.toLocaleString('en-US', {
month: 'short'
}));
}
return months_name;
}
/**
* format the data refresh string for exhibit on charts
*
* @param {Integer} num Number to be formatted
* @returns {string} Number formatted with K for thousands and M for millions
*/
function format_u_k_m(num) {
let label;
let sign = 1;
if (num < 0) {
sign = -1;
}
let abs_num = Math.abs(num);
if (abs_num == 0) {
label = abs_num;
} else {
if (abs_num > 0 && abs_num < 1000) {
label = abs_num * sign;
} else {
if (abs_num >= 1000 && abs_num < 1000000) {
label = abs_num / 1000 * sign + 'K';
} else {
if (abs_num >= 1000000) {
label = abs_num / 1000000 * sign + 'M';
}
}
}
}
return label;
}
/**
* Set a selected element of a combo box with a value
*
* @param {string} id of the selected
* @param {string} valueToSelect value to assign
*/
function selectElement(id, valueToSelect) {
let element = document.getElementById(id);
element.value = valueToSelect;
}
/**
* Add event Handler to element (on)
*
* @param {string} id of the element
* @param {string} eventType i.e. "change"
* @param {function} handler the function to execute whene the evenet is raised
*/
function addEventHandler(id, eventType, handler) {
if (id.addEventListener)
id.addEventListener(eventType, handler, false);
else if (id.attachEvent)
id.attachEvent('on' + eventType, handler);
}
/**
* Set a text to a specific element
*
* @param {string} id of the element
* @param {string} newvalue the value to assign to element
*/
function setText(id, newvalue) {
var s = document.getElementById(id);
s.innerHTML = newvalue;
}
/*
function doRefresh(){
var chartDom = document.getElementById('chart-dx_spots_trend');
var myChart = echarts.init(chartDom);
plot_dst.refresh(myChart,'/plot_get_dx_spots_trend');
};
setInterval(function(){doRefresh()}, 5000);
*/

View File

@ -1,9 +1,9 @@
/*
script used to acquire user conset to cookie banner (and set the cookie consent)
script used to acquire user consent to cookie banner (and set the cookie consent)
*/
let cookie_modal = new bootstrap.Modal(document.getElementById('cookie_consent_modal'), {
keyboard: false
})
});
cookie_modal.show();
//if button is pressed, setting cookie

View File

@ -1 +1 @@
document.getElementById('copyDate').innerHTML='2020-'.concat(new Date().getFullYear())
document.getElementById('copyDate').innerHTML='2020-'.concat(new Date().getFullYear());

View File

@ -5,8 +5,7 @@
* Moreover, add an event to the button of the filter form
*/
//var my_adxo_events=jQuery.parseJSON(my_adxo_events_json.replaceAll("\t",""));
var my_adxo_events=JSON.parse(my_adxo_events_json.replaceAll("\t",""));
var rows_list = new Array();
var my_adxo_events=JSON.parse(my_adxo_events_json.replaceAll('\t',''));
refresh_timer(); //run first data fetch
var myRefresh = setInterval(refresh_timer, timer_interval_json);

17
static/js/dev/load-sw.js Normal file
View File

@ -0,0 +1,17 @@
/* this is used to instance service worker for PWA
* Check compatibility for the browser we're running this in
*/
if ('serviceWorker' in navigator) {
if (navigator.serviceWorker.controller) {
console.log('[PWA Builder] active service worker found, no need to register');
} else {
// Register the service worker
navigator.serviceWorker
.register('service-worker.js', {
scope: './'
})
.then(function (reg) {
console.log('[PWA Builder] Service worker has been registered for scope: ' + reg.scope);
});
}
}

View File

@ -1,6 +1,6 @@
/*! loadCSS. [c]2020 Filament Group, Inc. MIT License */
(function(w){
"use strict";
'use strict';
/* exported loadCSS */
var loadCSS = function( href, before, media, attributes ){
// Arguments explained:
@ -10,13 +10,13 @@
// `media` [OPTIONAL] is the media type or query of the stylesheet. By default it will be 'all'
// `attributes` [OPTIONAL] is the Object of attribute name/attribute value pairs to set on the stylesheet's DOM Element.
var doc = w.document;
var ss = doc.createElement( "link" );
var ss = doc.createElement( 'link' );
var ref;
if( before ){
ref = before;
}
else {
var refs = ( doc.body || doc.getElementsByTagName( "head" )[ 0 ] ).childNodes;
var refs = ( doc.body || doc.getElementsByTagName( 'head' )[ 0 ] ).childNodes;
ref = refs[ refs.length - 1];
}
@ -29,10 +29,10 @@
}
}
}
ss.rel = "stylesheet";
ss.rel = 'stylesheet';
ss.href = href;
// temporarily set media to something inapplicable to ensure it'll fetch without blocking render
ss.media = "only x";
ss.media = 'only x';
// wait until body is defined before injecting link. This ensures a non-blocking load in IE11.
function ready( cb ){
@ -65,24 +65,24 @@
function loadCB(){
if( ss.addEventListener ){
ss.removeEventListener( "load", loadCB );
ss.removeEventListener( 'load', loadCB );
}
ss.media = media || "all";
ss.media = media || 'all';
}
// once loaded, set link's media back to `all` so that the stylesheet applies once it loads
if( ss.addEventListener ){
ss.addEventListener( "load", loadCB);
ss.addEventListener( 'load', loadCB);
}
ss.onloadcssdefined = onloadcssdefined;
onloadcssdefined( loadCB );
return ss;
};
// commonjs
if( typeof exports !== "undefined" ){
if( typeof exports !== 'undefined' ){
exports.loadCSS = loadCSS;
}
else {
w.loadCSS = loadCSS;
}
}( typeof global !== "undefined" ? global : this ));
}( typeof global !== 'undefined' ? global : this ));

View File

@ -0,0 +1,191 @@
/********************************************************************************
* javascript used to build band_activity chart
* ******************************************************************************/
class band_activity {
/**
* refresh and populate chart
*
* @param {String} my_cart The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
* @param {string} region This is the continent name (EU,AF,NA...) of the selected
* @param {Json} bands The frequency band list (160, 80, 60... UHF, SHF)
* @param {Json} continents The continent list( EU, SA, ...)
*/
refresh(my_chart, end_point, region, bands, continents){
// Asynchronous Data Loading
fetch(end_point+'?continent='+region)
.then((response) => response.json())
.then((data) => {
// Fill in the data
var last_refresh=get_last_refresh(data);
var dataMap=Array.from(data['band activity']).map(function (item) {
return [item[1], item[0], item[2] || '-'];
});
//options
my_chart.setOption({
tooltip: {
position: 'top',
formatter: function (p) {
var format = p.seriesName +' on ' + p.name +' band: '+'<strong>'+p.data[2]+'</strong>';
return format;
}
},
title: {
text:'Band activity',
subtext: last_refresh,
top: 'top',
left:'left'
},
toolbox: {
show: true,
showTitle: false,
orient: 'vertical',
right: 'right',
top : 'bottom',
feature: {
mark: { show: true },
dataView: { show: true, readOnly: true },
restore: { show: true },
saveAsImage: { show: true }
}
},
grid: {
height: '80%',
left: 25,
top: 50,
right: 60,
bottom: 0,
show: true,
backgroundColor: 'rgb(255, 255, 255)',
},
xAxis: {
type: 'category',
data: bands,
axisTick: {
show: true,
},
axisLine: {
show: false,
},
splitArea: {
show: true
}
},
yAxis: {
type: 'category',
data: continents,
axisTick: {
show: true,
},
axisLine: {
show: false,
},
splitArea: {
show: true
}
},
visualMap: {
calculable: true,
orient: 'vertical',
right: 'right',
top: 'center',
min: 0,
max: 30,
inRange : {
color: ['#ffffe6','yellow','red']
}
},
series: [
{
name: 'Spots',
type: 'heatmap',
data: dataMap,
label: {
show: false
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(100, 0, 0, 0.5)'
}
}
}
]
});
});
};
/**
* Chart creator
*
* @constructor
* @param {String} chart_id The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
* @param {Json} cont_cq The continent list( EU, SA, ...)
* @param {Json} band_freq The frequency band list (160, 80, 60... UHF, SHF)
*/
constructor(chart_id, end_point, cont_cq, band_freq) {
// Initialize the echarts instance based on the prepared dom
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
//populate continents array
var continents=[];
cont_cq.forEach(function myFunction(item, index) {
continents[index]=item['id'];
});
//populate bands array
var bands=[];
band_freq.forEach(function myFunction(item, index) {
bands[index]=item['id'];
});
//managing region
var selectedContinent=getCookie('user_region');
var selectedContinent_desc=getCookie('user_region_desc');
if (!selectedContinent) {
selectedContinent='EU';
selectedContinent_desc='Europe';
setCookie('user_region',selectedContinent,60);
setCookie('user_region_desc',selectedContinent_desc,60);
};
selectElement('continentInput', selectedContinent);
addEventHandler(document.getElementById('continentInput'), 'change', function() {
selectedContinent=this.value;
selectedContinent_desc=this.options[this.selectedIndex].text;
setCookie('user_region',selectedContinent,60);
setCookie('user_region_desc',selectedContinent_desc,60);
plot_ba.refresh(myChart, end_point, selectedContinent,bands,continents);
setText('txt_continent','\xa0 Based on DX SPOTS from stations in '+ selectedContinent_desc +' during the last 15 minutes, displayed by Continent and Band');
});
setText('txt_continent','\xa0 Based on DX SPOTS from stations in '+ selectedContinent_desc +' during the last 15 minutes, displayed by Continent and Band');
this.refresh(myChart, end_point, selectedContinent,bands,continents);
/* resize chart*/
var chart = echarts.init(document.querySelector('#'+chart_id), null);
window.addEventListener('resize',function(){
chart.resize();
});
}
}
//create object
let plot_ba = new band_activity ('chart-band_activity','/plot_get_heatmap_data',continents_cq,band_frequencies);
/*setInterval(function(){doRefresh('chart-band_activity','/plot_get_heatmap_data')}, 5000);
function doRefresh(chart_id,end_point){
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
plot_dst.refresh(myChart,end_point);
};
*/

View File

@ -0,0 +1,142 @@
/********************************************************************************
* javascript used to build dx spots per month chart
* ******************************************************************************/
class dx_spots_per_month {
/**
* refresh and populate chart
*
* @param {String} my_cart The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
refresh(my_chart,end_point) {
// Asynchronous Data Loading
//$.getJSON(end_point).done(function(data) {
fetch(end_point)
.then((response) => response.json())
.then((data) => {
// Fill in the data
var last_refresh=get_last_refresh(data);
var year_now = new Date().getFullYear();
var year_0 = (year_now - 0).toString();
var year_1 = (year_now - 1).toString();
var year_2 = (year_now - 2).toString();
var months_name = get_months_names();
var dataMap_year_0=[];
var dataMap_year_1=[];
var dataMap_year_2=[];
for (let i = 1; i < 13; i++) {
dataMap_year_0.push(data.spots_per_month[i].year_0);
dataMap_year_1.push(data.spots_per_month[i].year_1);
dataMap_year_2.push(data.spots_per_month[i].year_2);
}
//options
my_chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
title: {
text: 'DX SPOTS per month',
subtext: last_refresh,
top: 'top',
left:'left'
},
legend: {
data: [year_2, year_1, year_0],
bottom: 'bottom'
},
toolbox: {
show: true,
showTitle: false,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar', 'stack'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
xAxis: [
{
type: 'category',
axisTick: { show: false },
data: months_name
}
],
yAxis: [
{
type: 'value',
axisLabel: {
formatter: (function (value){
return format_u_k_m(value);
})
}
}
],
series: [
{
name: year_2,
type: 'bar',
barGap: 0,
emphasis: {
focus: 'series'
},
data: dataMap_year_2
},
{
name: year_1,
type: 'bar',
emphasis: {
focus: 'series'
},
data: dataMap_year_1
},
{
name: year_0,
type: 'bar',
emphasis: {
focus: 'series'
},
data: dataMap_year_0
},
]
});
});
}
/**
* Chart creator
*
* @constructor
* @param {String} chart_id The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
constructor(chart_id,end_point) {
// Initialize the echarts instance based on the prepared dom
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
this.refresh(myChart,end_point);
//resize
var chart = echarts.init(document.querySelector('#'+chart_id), null);
window.addEventListener('resize',function(){
chart.resize();
})
};
}
//create object
let plot_dspm = new dx_spots_per_month ('chart-dx_spots_x_month','/plot_get_dx_spots_per_month');

View File

@ -0,0 +1,136 @@
/********************************************************************************
* javascript used to build dx spots trend
* ******************************************************************************/
class dx_spots_trend {
/**
* refresh and populate chart
*
* @param {String} my_cart The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
refresh(my_chart,end_point) {
// Asynchronous Data Loading
//$.getJSON(end_point).done(function(data) {
fetch(end_point)
.then((response) => response.json())
.then((data) => {
// Fill in the data
var last_refresh=get_last_refresh(data);
var dataMap=[];
for (const [key, value] of Object.entries(data['spots_trend'])) {
var tuple=[];
tuple.push(key);
tuple.push(value);
dataMap.push(tuple);
}
//options
my_chart.setOption({
tooltip: {
trigger: 'axis',
position: function (pt) {
return [pt[0], '10%'];
}
},
title: {
text: 'DX SPOTS trend',
subtext: last_refresh,
top: 'top',
left:'left'
},
toolbox: {
show: true,
showTitle: false,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
dataView: { show: true, readOnly: false },
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
magicType: { show: true, type: ['line', 'bar'] },
saveAsImage: {},
}
},
xAxis: {
type: 'time',
boundaryGap: false
},
yAxis: {
type: 'value',
boundaryGap: [0, '10%'],
axisLabel: {
formatter: (function (value){
return format_u_k_m(value);
})
}
},
dataZoom: [
{
type: 'inside',
start: 65,
end: 100
},
{
start: 0,
end: 20
},
],
series: [
{
name: 'Spots',
type: 'line',
smooth: true,
symbol: 'none',
itemStyle: {
color: '#078513'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: '#57fa75'
},
{
offset: 1,
color: '#118226'
}
])
},
data: dataMap
}
]
})
})
}
/**
* Chart creator
*
* @constructor
* @param {String} chart_id The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
constructor(chart_id,end_point) {
// Initialize the echarts instance based on the prepared dom
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
this.refresh(myChart,end_point);
//resize
var chart = echarts.init(document.querySelector('#'+chart_id), null);
window.addEventListener('resize',function(){
chart.resize();
});
}
}
//create object
let plot_dst = new dx_spots_trend ('chart-dx_spots_trend','/plot_get_dx_spots_trend');

View File

@ -0,0 +1,152 @@
/********************************************************************************
* javascript used to build band/hour chart
* ******************************************************************************/
class hour_band {
/**
* refresh and populate chart
*
* @param {String} my_cart The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
* @param {Json} bands The frequency band list (160, 80, 60... UHF, SHF)
*/
refresh(my_chart,end_point,bands) {
// Asynchronous Data Loading
fetch(end_point)
.then((response) => response.json())
.then((data) => {
// Fill in the dat
var last_refresh=get_last_refresh(data);
//set hour indicator names
var hour_indicators=[];
//for (let i = 0; i < 24; i++) {
for (let i = 23; i > -1; i--) {
var hour_name={};
var s=i.toString();
hour_name['name']=s;
hour_indicators.push(hour_name);
}
//cycling whithin each bands and hours
var dataMap=[];
bands.forEach(band_item => {
var qso=[];
//for (let i = 0; i < 24; i++) {
for (let i = 23; i > -1; i--) {
try {
var value=data['hour_band'][band_item][i];
if (typeof value == 'undefined') {
value = 0;
}
qso.push(value);
} catch (TypeError) {
//TODO
}
}
var tot={'value':qso,'name':band_item};
dataMap.push(tot);
});
//options
my_chart.setOption({
legend: {
orient: 'horizontal',
left: 'left',
bottom: 'bottom'
},
title: {
text: 'DX SPOTS per hour in last month',
subtext: last_refresh,
top: 'top',
right: 'right',
},
tooltip: {
trigger: 'axis',
},
toolbox: {
show: true,
showTitle: false,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
mark: { show: true },
dataView: { show: true, readOnly: true },
restore: { show: true },
saveAsImage: { show: true }
}
},
radar: {
shape: 'circle',
//startAngle: 285, //0 on left side
startAngle: 105, //0 on top
indicator: hour_indicators,
center: ['47%', '46%'],
axisName: {
color: 'rgb(80,80,80)',
},
},
series: [
{
lineStyle: {
width: 2
},
type: 'radar',
symbol: 'none',
data: dataMap,
tooltip: {
trigger: 'item',
formatter: (params) => {
return 'Band: '+params.name;
},
},
emphasis: {
lineStyle: {
width: 4
}
},
}
]
})
})
}
/**
* Chart creator
*
* @constructor
* @param {String} chart_id The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
* @param {Json} band_freq The frequency band list (160, 80, 60... UHF, SHF)
*/
constructor(chart_id,end_point,band_freq) {
// Initialize the echarts instance based on the prepared dom
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
//populate bands array
var bands=[];
band_freq.forEach(function myFunction(item, index) {
bands[index]=item['id']
});
this.refresh(myChart,end_point,bands);
//resize
var chart = echarts.init(document.querySelector('#'+chart_id), null);
window.addEventListener('resize',function(){
chart.resize();
});
}
}
//create object
let plot_hb = new hour_band('chart-hour_band','/plot_get_hour_band',band_frequencies);

View File

@ -0,0 +1,161 @@
/********************************************************************************
* javascript used to build world dx spots live
* ******************************************************************************/
class world_dx_spots_live {
/**
* refresh and populate chart
*
* @param {String} my_cart The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
refresh(my_chart,end_point) {
// Asynchronous Data Loading
fetch(end_point)
.then((response) => response.json())
.then((data) => {
fetch('world.json')
.then(response => response.text())
.then(geoJson => {
var last_refresh=get_last_refresh(data);
var dataMap=[];
data['world_dx_spots_live'].forEach(function myFunction(item, index) {
//lon, lat, number of qso
dataMap.push({'value':[item['lat'],item['lon'],item['count']]});
});
my_chart.hideLoading();
echarts.registerMap('WR', geoJson);
my_chart.setOption( {
visualMap: {
show: false,
min: 0,
max: 50,
inRange: {
symbolSize: [5, 20]
}
},
geo: {
type: 'map',
map: 'WR',
roam: true,
zoom: 1.2,
aspectScale: 0.70,
layoutCenter: ['50%', '54%'],
layoutSize: '100%',
itemStyle: {
normal: {
areaColor: '#323c48',
borderColor: '#111'
},
emphasis: {
areaColor: '#2a333d'
}
},
label: {
emphasis: {
show: false
}
},
},
tooltip: {
trigger: 'item',
formatter: function(val) {
var out='Spots: <STRONG>'+ val.value[2] +'</STRONG>';
return out;
}
},
toolbox: {
show: true,
showTitle: false,
orient: 'vertical',
left: 'right',
top: 'center',
iconStyle: {
borderColor: '#fff',
},
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
restore: { show: true },
saveAsImage: { show: true }
}
},
legend: {
show: false
},
//backgroundColor: '#404a59',
backgroundColor: '#596475',
title: {
text: 'World DX SPOTS in last hour',
subtext: last_refresh,
top: 'top',
right:'right',
textStyle: {
color: '#fff'
},
subtextStyle: {
color: '#fff'
}
},
series: [
{
type: 'scatter',
coordinateSystem: 'geo',
data:dataMap,
label: {
emphasis: {
position: 'right',
show: false
}
},
itemStyle: {
normal: {
color: '#eea638'
}
},
/*
symbolSize: function (val) {
return val[2] / 4;
},
*/
}
]
}); //end options
}
);
});
}
/**
* Chart creator
*
* @constructor
* @param {String} chart_id The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
constructor(chart_id,end_point) {
// Initialize the echarts instance based on the prepared dom
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
this.refresh(myChart,end_point);
//resize
var chart = echarts.init(document.querySelector('#'+chart_id), null);
window.addEventListener('resize',function(){
chart.resize();
});
}
}
//create object
let plot_wdsl = new world_dx_spots_live ('chart-world_dx_spots_live','/plot_get_world_dx_spots_live');

360
static/js/dev/table.js Normal file
View File

@ -0,0 +1,360 @@
class table_builder {
/**
* Table builder constructor
* @param selector {string} The html identifier where build the spots table
*/
constructor(selector) {
this.selector = selector;
this.current_data = [];
}
/**
* @return last_rowid {integer} the last rowid
*/
getLastRowId() {
let last_rowid;
if (this.current_data == null) {
last_rowid = 0;
} else {
if (this.current_data.length < 1) {
last_rowid = 0;
} else {
last_rowid = this.current_data[0].rowid;
}
}
return last_rowid;
}
resetData() {
this.current_data = [];
}
/**
* @param line {object} with the data of a single spot
* @param isnew {boolean} is the new rows indicator
* @param dt_current {string} current date in dd/mm/yyyy format
* @param callsign {string} optional callsign
* @return a &lt;tr&gt; dom element
*/
#buildRow(line, isnew, dt_current, callsign = '') {
const row = document.createElement('tr');
row.id = line.rowid;
if (callsign.length > 0) {
if (callsign == line.de) {
row.id = line.rowid;
} else if (callsign == line.dx) {
row.id = line.rowid;
}
} else if (isnew) {
row.className = 'table-info';
}
//Column: DE search on QRZ
const i_qrzde = document.createElement('i');
i_qrzde.className = 'bi-search';
i_qrzde.role = 'button';
i_qrzde.ariaLabel = line.de;
const a_qrzde = document.createElement('a');
a_qrzde.href = qrz_url + line.de;
a_qrzde.target = '_blank';
a_qrzde.rel = 'noopener';
const span_qrzde = document.createElement('span');
//Mark DE if it found in callsign search
if (line.de == callsign) {
const mark_qrzde = document.createElement('mark');
mark_qrzde.textContent = line.de;
span_qrzde.appendChild(mark_qrzde);
} else {
span_qrzde.textContent = '\xa0' + line.de;
}
const td_qrzde = document.createElement('td');
a_qrzde.appendChild(i_qrzde);
td_qrzde.appendChild(a_qrzde);
td_qrzde.appendChild(span_qrzde);
row.append(td_qrzde);
//Column: frequency
var freq = Intl.NumberFormat('it-IT', {
style: 'decimal'
}).format(line.freq);
const span_freq = document.createElement('span');
span_freq.className = 'badge bg-warning text-dark badge-responsive';
span_freq.textContent = freq;
const td_freq = document.createElement('td');
td_freq.appendChild(span_freq);
row.appendChild(td_freq);
//Column: DX (with ADXO Management)
var adxo = findAdxo(my_adxo_events, line.dx);
var adxo_link = '<a href=' + adxo_url + ' target=_blank rel=noopener >NG3K Website</a>';
const i_qrzdx = document.createElement('i');
i_qrzdx.className = 'bi-search';
i_qrzdx.role = 'button';
i_qrzdx.ariaLabel = line.dx;
const a_qrzdx = document.createElement('a');
a_qrzdx.href = qrz_url + line.dx;
a_qrzdx.target = '_blank';
a_qrzdx.rel = 'noopener';
const span_qrzdx = document.createElement('span');
//Mark DX if it found in callsign search
const mark_qrzdx = document.createElement('mark');
mark_qrzdx.textContent = line.dx;
if (line.dx == callsign) {
span_qrzdx.appendChild(mark_qrzdx);
} else {
span_qrzdx.textContent = '\xa0' + line.dx;
}
if (adxo != undefined) {
const i_adxo = document.createElement('i');
i_adxo.tabIndex = 0;
i_adxo.className = 'bi-megaphone-fill';
i_adxo.style = 'color: cornflowerblue;';
i_adxo.role = 'button';
i_adxo.ariaLabel = 'dx_operations';
i_adxo.setAttribute('data-bs-container', 'body');
i_adxo.setAttribute('data-bs-toggle', 'popover');
i_adxo.setAttribute('data-bs-trigger', 'focus');
i_adxo.setAttribute('data-bs-sanitizer', 'true');
i_adxo.setAttribute('data-bs-placement', 'auto');
i_adxo.setAttribute('data-bs-html', 'true');
i_adxo.setAttribute('data-bs-title', 'Announced DX Op.: ' + adxo.summary);
i_adxo.setAttribute('data-bs-content', adxo.description + 'data from ' + adxo_link);
span_qrzdx.appendChild(i_adxo);
}
const td_qrzdx = document.createElement('td');
a_qrzdx.appendChild(i_qrzdx);
td_qrzdx.appendChild(a_qrzdx);
td_qrzdx.append(span_qrzdx);
row.appendChild(td_qrzdx);
//Column: Flag
try {
const span_flag = document.createElement('span');
span_flag.className = 'img-flag fi fi-' + line.iso;
span_flag.setAttribute('data-bs-container', 'body');
span_flag.setAttribute('data-bs-toggle', 'popover');
span_flag.setAttribute('data-bs-trigger', 'hover');
span_flag.setAttribute('data-bs-placement', 'left');
span_flag.setAttribute('data-bs-content', line.country);
const td_flag = document.createElement('td');
td_flag.appendChild(span_flag);
row.appendChild(td_flag);
} catch (err) {
console.log(err);
console.log('error creating flag');
const td_flag = document.createElement('td');
row.appendChild(td_flag);
}
//Column: Country
const td_country_code = document.createElement('td');
td_country_code.className = 'd-none d-lg-table-cell d-xl-table-cell';
td_country_code.textContent = line.country;
row.appendChild(td_country_code);
//Column: Comment
const td_comm = document.createElement('td');
td_comm.className = 'd-none d-lg-table-cell d-xl-table-cell';
td_comm.textContent = line.comm;
row.appendChild(td_comm);
//Column: UTC
let dt = new Date(line.time * 1000);
let hh = '00' + dt.getUTCHours();
hh = hh.substring(hh.length - 2, hh.length);
let mi = '00' + dt.getMinutes();
mi = mi.substring(mi.length - 2, mi.length);
let dd = '00' + dt.getUTCDate();
dd = dd.substring(dd.length - 2, dd.length);
let mo = '00' + (Number(dt.getUTCMonth()) + 1);
mo = mo.substring(mo.length - 2, mo.length);
let yy = dt.getUTCFullYear();
let tm = hh + ':' + mi;
dt = dd + '/' + mo + '/' + yy;
const div_date_time = document.createElement('div');
div_date_time.className = 'd-flex flex-column';
const p_time = document.createElement('div');
p_time.textContent = tm;
div_date_time.appendChild(p_time);
if (dt != dt_current) {
const p_date = document.createElement('div');
p_date.textContent = dt;
div_date_time.appendChild(p_date);
}
row.appendChild(div_date_time);
//Finally append the row created to the table
return row;
}
/**
* Build the table with the spot
*
* @param data {json} The payload with all the spots received from cluster
* @param rl {json} Row List
* @param callsign {string} An optional parameter with the callsign to search
*/
build(data, callsign) {
if (data != null) {
//get current date
let d = new Date();
let dd_current = '00' + d.getUTCDate();
dd_current = dd_current.substring(dd_current.length - 2, dd_current.length);
let mo_current = '00' + (Number(d.getUTCMonth()) + 1);
mo_current = mo_current.substring(mo_current.length - 2, mo_current.length);
let yy_current = d.getUTCFullYear();
let dt_current = dd_current + '/' + mo_current + '/' + yy_current;
//empty the table
document.getElementById(this.selector).replaceChildren();
//insert in table new elements
let merge_data = [];
for (let i = 0; i < data.length; i++) {
document.getElementById(this.selector).append(this.#buildRow(data[i], true, dt_current, callsign));
merge_data.push(data[i]);
}
//insert in html table previous elements
for (let i = 0; i < this.current_data.length - data.length; i++) {
document.getElementById(this.selector).append(this.#buildRow(this.current_data[i], false, dt_current, callsign));
merge_data.push(this.current_data[i]);
}
//replace current data with merge
this.current_data = merge_data;
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl);
});
}
}
} //end class
/********************************************************************************
* javascript used to popolate main table with spots
* ******************************************************************************/
const adxo_url = 'https://www.ng3k.com/misc/adxo.html';
const qrz_url = 'https://www.qrz.com/db/';
const tb = new table_builder('bodyspot');
var qryAll_sv = '';
/**
* Decode Announced Dx Operation (ng3k)
*
* @param adxo {adxo} This is the json containing all the dxo events
* @param callsign_to_find {callsign_to_find} The callsign of the current dx line
*/
function findAdxo(adxo, callsign_to_find) {
if (adxo) {
for (let i = 0; i < adxo.length; i++) {
if (adxo[i].callsign == callsign_to_find) {
return adxo[i];
}
}
}
}
/**
* Function to filter spot when pressed the search button on filter
* This function trigger the search, also triggered by timer
*/
function mySearch(event) {
event.preventDefault();
refresh_timer(); //force the call of query
}
/**
* Function for construct query string for single value selection
*
* @param id {string} The html identifier used for filter
* @param param {string}the parameter for the query
* @param len {number} The maximum number of element that could be selected; use -1 if the filter permits a single selection
* @param qrystr {string} Th initial query string to be completed with the new filter
*/
function getFilter(id, param, len, qrystr) {
let selectedFilter = [].map.call(document.getElementById(id).selectedOptions, option => option.value);
let qryFilter = '';
if (selectedFilter.length < len || len == -1) {
qryFilter = selectedFilter.map(function (el) {
if (el) {
return param + '=' + el;
} else {
return '';
}
}).join('&');
qrystr = qrystr.concat('&'.concat(qryFilter));
if (qrystr.substring(0, 1) == '&') {
qrystr = qrystr.substring(1);
}
}
return qrystr;
}
/**
* Search / Filter cluster spot based on filter settings
* Gets the filter values, constructs the query parameter and
* make the request to the server
*/
function refresh_timer() {
let qryAll = '';
//get other filters
qryAll = getFilter('band', 'b', 14, qryAll);
qryAll = getFilter('de_re', 'e', 7, qryAll);
qryAll = getFilter('dx_re', 'x', 7, qryAll);
qryAll = getFilter('mode', 'm', 3, qryAll);
qryAll = getFilter('cqdeInput', 'qe', -1, qryAll);
qryAll = getFilter('cqdxInput', 'qx', -1, qryAll);
//Composing query string
let qryString;
//If the filter is changed we need to reset the data table and restart from rowid=0
if (qryAll != qryAll_sv) {
tb.resetData();
qryAll_sv = qryAll;
}
qryString = ('spotlist?lr=').concat(tb.getLastRowId());
if (qryAll) {
qryString = qryString.concat('&'.concat(qryAll));
}
console.log(qryString);
//Open a new connection, using the GET request on the URL endpoint
fetch(qryString)
.then((response) => response.json())
.then((data_new) => {
try {
tb.build(data_new);
} catch (err) {
console.log(err);
console.log(err.stack);
console.log(data_new);
}
});
}

View File

@ -1 +0,0 @@
var my_adxo_events=jQuery.parseJSON(my_adxo_events_json.replaceAll("\t","")),rows_list=[];buildHtmlTable("#bodyspot",payload_json,rows_list);var myRefresh=setInterval(myTimer,timer_interval_json);window.onload=function(){document.getElementById("form-filters").addEventListener("submit",mySearch)};

View File

@ -1,17 +0,0 @@
/* this is used to instance service worker for PWA
* Check compatibility for the browser we're running this in
*/
if ("serviceWorker" in navigator) {
if (navigator.serviceWorker.controller) {
console.log("[PWA Builder] active service worker found, no need to register");
} else {
// Register the service worker
navigator.serviceWorker
.register("service-worker.js", {
scope: "./"
})
.then(function (reg) {
console.log("[PWA Builder] Service worker has been registered for scope: " + reg.scope);
});
}
}

View File

@ -1,4 +0,0 @@
/*
loadCSS. [c]2020 Filament Group, Inc. MIT License */
(function(l){var e=function(e,f,q,d){function m(a){if(b.body)return a();setTimeout(function(){m(a)})}function g(){a.addEventListener&&a.removeEventListener("load",g);a.media=q||"all"}var b=l.document,a=b.createElement("link");if(f)var c=f;else{var n=(b.body||b.getElementsByTagName("head")[0]).childNodes;c=n[n.length-1]}var p=b.styleSheets;if(d)for(var h in d)d.hasOwnProperty(h)&&a.setAttribute(h,d[h]);a.rel="stylesheet";a.href=e;a.media="only x";m(function(){c.parentNode.insertBefore(a,f?c:c.nextSibling)});
var k=function(b){for(var d=a.href,c=p.length;c--;)if(p[c].href===d)return b();setTimeout(function(){k(b)})};a.addEventListener&&a.addEventListener("load",g);a.onloadcssdefined=k;k(g);return a};"undefined"!==typeof exports?exports.loadCSS=e:l.loadCSS=e})("undefined"!==typeof global?global:this);

View File

@ -1,191 +0,0 @@
/********************************************************************************
* javascript used to build band_activity chart
* ******************************************************************************/
class band_activity {
/**
* refresh and populate chart
*
* @param {String} my_cart The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
* @param {string} region This is the continent name (EU,AF,NA...) of the selected
* @param {Json} bands The frequency band list (160, 80, 60... UHF, SHF)
* @param {Json} continents The continent list( EU, SA, ...)
*/
refresh(my_chart, end_point, region, bands, continents){
// Asynchronous Data Loading
fetch(end_point+'?continent='+region)
.then((response) => response.json())
.then((data) => {
// Fill in the data
var last_refresh=get_last_refresh(data);
var dataMap=Array.from(data["band activity"]).map(function (item) {
return [item[1], item[0], item[2] || '-'];
});
//options
my_chart.setOption({
tooltip: {
position: 'top',
formatter: function (p) {
var format = p.seriesName +' on ' + p.name +' band: '+'<strong>'+p.data[2]+'</strong>';
return format;
}
},
title: {
text:"Band activity",
subtext: last_refresh,
top: 'top',
left:'left'
},
toolbox: {
show: true,
showTitle: false,
orient: 'vertical',
right: 'right',
top : 'bottom',
feature: {
mark: { show: true },
dataView: { show: true, readOnly: true },
restore: { show: true },
saveAsImage: { show: true }
}
},
grid: {
height: '80%',
left: 25,
top: 50,
right: 60,
bottom: 0,
show: true,
backgroundColor: 'rgb(255, 255, 255)',
},
xAxis: {
type: 'category',
data: bands,
axisTick: {
show: true,
},
axisLine: {
show: false,
},
splitArea: {
show: true
}
},
yAxis: {
type: 'category',
data: continents,
axisTick: {
show: true,
},
axisLine: {
show: false,
},
splitArea: {
show: true
}
},
visualMap: {
calculable: true,
orient: 'vertical',
right: 'right',
top: 'center',
min: 0,
max: 30,
inRange : {
color: ['#ffffe6','yellow','red']
}
},
series: [
{
name: 'Spots',
type: 'heatmap',
data: dataMap,
label: {
show: false
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(100, 0, 0, 0.5)'
}
}
}
]
});
});
};
/**
* Chart creator
*
* @constructor
* @param {String} chart_id The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
* @param {Json} cont_cq The continent list( EU, SA, ...)
* @param {Json} band_freq The frequency band list (160, 80, 60... UHF, SHF)
*/
constructor(chart_id, end_point, cont_cq, band_freq) {
// Initialize the echarts instance based on the prepared dom
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
//populate continents array
var continents=[];
cont_cq.forEach(function myFunction(item, index) {
continents[index]=item['id']
});
//populate bands array
var bands=[];
band_freq.forEach(function myFunction(item, index) {
bands[index]=item['id']
});
//managing region
var selectedContinent=getCookie("user_region");
var selectedContinent_desc=getCookie("user_region_desc");
if (!selectedContinent) {
selectedContinent="EU";
selectedContinent_desc="Europe";
setCookie("user_region",selectedContinent,60);
setCookie("user_region_desc",selectedContinent_desc,60);
};
selectElement('continentInput', selectedContinent);
addEventHandler(document.getElementById('continentInput'), 'change', function() {
selectedContinent=this.value;
selectedContinent_desc=this.options[this.selectedIndex].text;
setCookie("user_region",selectedContinent,60);
setCookie("user_region_desc",selectedContinent_desc,60);
plot_ba.refresh(myChart, end_point, selectedContinent,bands,continents);
setText('txt_continent','\xa0 Based on DX SPOTS from stations in '+ selectedContinent_desc +' during the last 15 minutes, displayed by Continent and Band');
});
setText('txt_continent','\xa0 Based on DX SPOTS from stations in '+ selectedContinent_desc +' during the last 15 minutes, displayed by Continent and Band');
this.refresh(myChart, end_point, selectedContinent,bands,continents);
/* resize chart*/
var chart = echarts.init(document.querySelector("#"+chart_id), null);
window.addEventListener('resize',function(){
chart.resize();
})
}
}
//create object
let plot_ba = new band_activity ('chart-band_activity','/plot_get_heatmap_data',continents_cq,band_frequencies);
/*setInterval(function(){doRefresh('chart-band_activity','/plot_get_heatmap_data')}, 5000);
function doRefresh(chart_id,end_point){
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
plot_dst.refresh(myChart,end_point);
};
*/

View File

@ -1,10 +0,0 @@
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,c,b){a!=Array.prototype&&a!=Object.prototype&&(a[c]=b.value)};
$jscomp.getGlobal=function(a){a=["object"==typeof globalThis&&globalThis,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global,a];for(var c=0;c<a.length;++c){var b=a[c];if(b&&b.Math==Math)return b}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
$jscomp.polyfill=function(a,c,b,d){if(c){b=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];e in b||(b[e]={});b=b[e]}a=a[a.length-1];d=b[a];c=c(d);c!=d&&null!=c&&$jscomp.defineProperty(b,a,{configurable:!0,writable:!0,value:c})}};
$jscomp.polyfill("Array.from",function(a){return a?a:function(a,b,d){b=null!=b?b:function(a){return a};var c=[],f="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];if("function"==typeof f){a=f.call(a);for(var g=0;!(f=a.next()).done;)c.push(b.call(d,f.value,g++))}else for(f=a.length,g=0;g<f;g++)c.push(b.call(d,a[g],g));return c}},"es6","es3");
$jscomp.findInternal=function(a,c,b){a instanceof String&&(a=String(a));for(var d=a.length,e=0;e<d;e++){var f=a[e];if(c.call(b,f,e,a))return{i:e,v:f}}return{i:-1,v:void 0}};$jscomp.polyfill("Array.prototype.find",function(a){return a?a:function(a,b){return $jscomp.findInternal(this,a,b).v}},"es6","es3");
var band_activity=function(a,c,b,d){var e=document.getElementById(a),f=echarts.init(e),g=[];b.forEach(function(a,b){g[b]=a.id});var l=[];d.forEach(function(a,b){l[b]=a.id});var h=getCookie("user_region"),k=getCookie("user_region_desc");h||(h="EU",k="Europe",setCookie("user_region",h,60),setCookie("user_region_desc",k,60));$("select").val(h).change();$("select").on("change",function(){h=this.value;k=$(this).find("option:selected").text();setCookie("user_region",h,60);setCookie("user_region_desc",k,
60);plot_ba.refresh(f,c,h,l,g);$("#txt_continent").text("  Based on DX SPOTS from stations in "+k+" during the last 15 minutes, displayed by Continent and Band")});$("#txt_continent").text("  Based on DX SPOTS from stations in "+k+" during the last 15 minutes, displayed by Continent and Band");this.refresh(f,c,h,l,g);var m=echarts.init(document.querySelector("#"+a),null);window.addEventListener("resize",function(){m.resize()})};
band_activity.prototype.refresh=function(a,c,b,d,e){fetch(c+"?continent="+b).then(function(a){return a.json()}).then(function(b){var c=get_last_refresh(b);b=Array.from(b["band activity"]).map(function(a){return[a[1],a[0],a[2]||"-"]});a.setOption({tooltip:{position:"top",formatter:function(a){return a.seriesName+" on "+a.name+" band: <strong>"+a.data[2]+"</strong>"}},title:{text:"Band activity",subtext:c,top:"top",left:"left"},toolbox:{show:!0,showTitle:!1,orient:"vertical",right:"right",top:"bottom",
feature:{mark:{show:!0},dataView:{show:!0,readOnly:!0},restore:{show:!0},saveAsImage:{show:!0}}},grid:{height:"80%",left:25,top:50,right:60,bottom:0,show:!0,backgroundColor:"rgb(255, 255, 255)"},xAxis:{type:"category",data:d,axisTick:{show:!0},axisLine:{show:!1},splitArea:{show:!0}},yAxis:{type:"category",data:e,axisTick:{show:!0},axisLine:{show:!1},splitArea:{show:!0}},visualMap:{calculable:!0,orient:"vertical",right:"right",top:"center",min:0,max:30,inRange:{color:["#ffffe6","yellow","red"]}},series:[{name:"Spots",
type:"heatmap",data:b,label:{show:!1},emphasis:{itemStyle:{shadowBlur:10,shadowColor:"rgba(100, 0, 0, 0.5)"}}}]})})};var plot_ba=new band_activity("chart-band_activity","/plot_get_heatmap_data",continents_cq,band_frequencies);

View File

@ -1,119 +0,0 @@
/**
* Create a cookie
*
* @param {String} cname cookie name
* @param {string} cvalue cookie value
* @param {integer} exdays the number of the days for cookie expiration
*/
function setCookie(cname, cvalue, exdays) {
const d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
let expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/" + ";Samesite=Strict;Secure=True";
};
/**
* get a cookie
*
* @param {String} cname cookie name
* @returns {string} cookie value
*/
function getCookie(cname) {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';');
for(let i = 0; i <ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
/**
* format the data refresh string for exhibit on charts
*
* @param {String} date date of the last refresh
* @returns {string} string formatted
*/
function get_last_refresh(data){
var dt_refresh = new Date(0); // The 0 there is the key, which sets the date to the epoch
var dt_refresh;
dt_refresh.setUTCSeconds(data["last_refresh"]);
const pad = (n,s=2) => (`${new Array(s).fill(0)}${n}`).slice(-s);
var hours = pad (dt_refresh.getHours());
var minutes = pad(dt_refresh.getMinutes());
var months_names = get_months_names();
var month = months_names[dt_refresh.getMonth()];
var day = dt_refresh.getDate();
var year = dt_refresh.getFullYear();
var last_refresh = "Data refresh: "+day+" of "+month+" "+year+" at "+hours+":"+minutes;
return last_refresh;
}
/**
* format the data refresh string for exhibit on charts
*
* @returns {Array} list of months as short names
*/
function get_months_names() {
var months_name=[];
for (let monthNumber = 1; monthNumber < 13; monthNumber++) {
const date = new Date();
date.setMonth(monthNumber - 1);
months_name.push(date.toLocaleString('en-US', { month: 'short' }));
};
return months_name;
}
/**
* format the data refresh string for exhibit on charts
*
* @param {Integer} num Number to be formatted
* @returns {string} Number formatted with K for thousands and M for millions
*/
function format_u_k_m(num) {
let label;
let sign=1;
if (num<0){
sign=-1;
}
let abs_num=Math.abs(num);
if (abs_num==0) {
label=abs_num
} else {
if (abs_num>0 && abs_num <1000) {
label=abs_num*sign;
} else {
if (abs_num>=1000 && abs_num < 1000000) {
label=abs_num/1000*sign+"K";
} else {
if (abs_num>=1000000) {
label=abs_num/1000000*sign+"M";
}
}
}
}
return label;
}
/*
function doRefresh(){
var chartDom = document.getElementById('chart-dx_spots_trend');
var myChart = echarts.init(chartDom);
plot_dst.refresh(myChart,'/plot_get_dx_spots_trend');
};
setInterval(function(){doRefresh()}, 5000);
*/

View File

@ -1,6 +0,0 @@
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.createTemplateTagFirstArg=function(a){return a.raw=a};$jscomp.createTemplateTagFirstArgWithRaw=function(a,b){a.raw=b;return a};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){a!=Array.prototype&&a!=Object.prototype&&(a[b]=c.value)};
$jscomp.getGlobal=function(a){a=["object"==typeof globalThis&&globalThis,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global,a];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
$jscomp.polyfill=function(a,b,c,d){if(b){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];e in c||(c[e]={});c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})}};$jscomp.polyfill("Array.prototype.fill",function(a){return a?a:function(a,c,d){var b=this.length||0;0>c&&(c=Math.max(0,b+c));if(null==d||d>b)d=b;d=Number(d);0>d&&(d=Math.max(0,b+d));for(c=Number(c||0);c<d;c++)this[c]=a;return this}},"es6","es3");
function setCookie(a,b,c){var d=new Date;d.setTime(d.getTime()+864E5*c);c="expires="+d.toUTCString();document.cookie=a+"="+b+";"+c+";path=/;Samesite=Strict;Secure=True"}function getCookie(a){a+="=";for(var b=decodeURIComponent(document.cookie).split(";"),c=0;c<b.length;c++){for(var d=b[c];" "==d.charAt(0);)d=d.substring(1);if(0==d.indexOf(a))return d.substring(a.length,d.length)}return""}
function get_last_refresh(a){var b=new Date(0);b.setUTCSeconds(a.last_refresh);var c=function(a,b){b=void 0===b?2:b;return(""+Array(b).fill(0)+a).slice(-b)};a=c(b.getHours());c=c(b.getMinutes());var d=get_months_names()[b.getMonth()],e=b.getDate();b=b.getFullYear();return"Data refresh: "+e+" of "+d+" "+b+" at "+a+":"+c}function get_months_names(){for(var a=[],b=1;13>b;b++){var c=new Date;c.setMonth(b-1);a.push(c.toLocaleString("en-US",{month:"short"}))}return a}
function format_u_k_m(a){var b,c=1;0>a&&(c=-1);a=Math.abs(a);0==a?b=a:0<a&&1E3>a?b=a*c:1E3<=a&&1E6>a?b=a/1E3*c+"K":1E6<=a&&(b=a/1E6*c+"M");return b};

View File

@ -1,142 +0,0 @@
/********************************************************************************
* javascript used to build dx spots per month chart
* ******************************************************************************/
class dx_spots_per_month {
/**
* refresh and populate chart
*
* @param {String} my_cart The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
refresh(my_chart,end_point) {
// Asynchronous Data Loading
//$.getJSON(end_point).done(function(data) {
fetch(end_point)
.then((response) => response.json())
.then((data) => {
// Fill in the data
var last_refresh=get_last_refresh(data);
var year_now = new Date().getFullYear();
var year_0 = (year_now - 0).toString();
var year_1 = (year_now - 1).toString();
var year_2 = (year_now - 2).toString();
var months_name = get_months_names();
var dataMap_year_0=[];
var dataMap_year_1=[];
var dataMap_year_2=[];
for (let i = 1; i < 13; i++) {
dataMap_year_0.push(data.spots_per_month[i].year_0);
dataMap_year_1.push(data.spots_per_month[i].year_1);
dataMap_year_2.push(data.spots_per_month[i].year_2);
}
//options
my_chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
title: {
text: "DX SPOTS per month",
subtext: last_refresh,
top: 'top',
left:'left'
},
legend: {
data: [year_2, year_1, year_0],
bottom: "bottom"
},
toolbox: {
show: true,
showTitle: false,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar', 'stack'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
xAxis: [
{
type: 'category',
axisTick: { show: false },
data: months_name
}
],
yAxis: [
{
type: 'value',
axisLabel: {
formatter: (function (value){
return format_u_k_m(value)
})
}
}
],
series: [
{
name: year_2,
type: 'bar',
barGap: 0,
emphasis: {
focus: 'series'
},
data: dataMap_year_2
},
{
name: year_1,
type: 'bar',
emphasis: {
focus: 'series'
},
data: dataMap_year_1
},
{
name: year_0,
type: 'bar',
emphasis: {
focus: 'series'
},
data: dataMap_year_0
},
]
})
})
}
/**
* Chart creator
*
* @constructor
* @param {String} chart_id The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
constructor(chart_id,end_point) {
// Initialize the echarts instance based on the prepared dom
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
this.refresh(myChart,end_point);
//resize
var chart = echarts.init(document.querySelector("#"+chart_id), null);
window.addEventListener('resize',function(){
chart.resize();
})
};
}
//create object
let plot_dspm = new dx_spots_per_month ('chart-dx_spots_x_month','/plot_get_dx_spots_per_month');

View File

@ -1,4 +0,0 @@
var dx_spots_per_month=function(e,c){var a=document.getElementById(e);a=echarts.init(a);this.refresh(a,c);var m=echarts.init(document.querySelector("#"+e),null);window.addEventListener("resize",function(){m.resize()})};
dx_spots_per_month.prototype.refresh=function(e,c){fetch(c).then(function(a){return a.json()}).then(function(a){var c=get_last_refresh(a),b=(new Date).getFullYear(),f=(b-0).toString(),g=(b-1).toString();b=(b-2).toString();for(var n=get_months_names(),h=[],k=[],l=[],d=1;13>d;d++)h.push(a.spots_per_month[d].year_0),k.push(a.spots_per_month[d].year_1),l.push(a.spots_per_month[d].year_2);e.setOption({tooltip:{trigger:"axis",axisPointer:{type:"shadow"}},title:{text:"DX SPOTS per month",subtext:c,top:"top",
left:"left"},legend:{data:[b,g,f],bottom:"bottom"},toolbox:{show:!0,showTitle:!1,orient:"vertical",left:"right",top:"center",feature:{mark:{show:!0},dataView:{show:!0,readOnly:!1},magicType:{show:!0,type:["line","bar","stack"]},restore:{show:!0},saveAsImage:{show:!0}}},xAxis:[{type:"category",axisTick:{show:!1},data:n}],yAxis:[{type:"value",axisLabel:{formatter:function(a){return format_u_k_m(a)}}}],series:[{name:b,type:"bar",barGap:0,emphasis:{focus:"series"},data:l},{name:g,type:"bar",emphasis:{focus:"series"},
data:k},{name:f,type:"bar",emphasis:{focus:"series"},data:h}]})})};var plot_dspm=new dx_spots_per_month("chart-dx_spots_x_month","/plot_get_dx_spots_per_month");

View File

@ -1,136 +0,0 @@
/********************************************************************************
* javascript used to build dx spots trend
* ******************************************************************************/
class dx_spots_trend {
/**
* refresh and populate chart
*
* @param {String} my_cart The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
refresh(my_chart,end_point) {
// Asynchronous Data Loading
//$.getJSON(end_point).done(function(data) {
fetch(end_point)
.then((response) => response.json())
.then((data) => {
// Fill in the data
var last_refresh=get_last_refresh(data);
var dataMap=[];
for (const [key, value] of Object.entries(data["spots_trend"])) {
var tuple=[];
tuple.push(key);
tuple.push(value);
dataMap.push(tuple);
}
//options
my_chart.setOption({
tooltip: {
trigger: 'axis',
position: function (pt) {
return [pt[0], '10%'];
}
},
title: {
text: "DX SPOTS trend",
subtext: last_refresh,
top: 'top',
left:'left'
},
toolbox: {
show: true,
showTitle: false,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
dataView: { show: true, readOnly: false },
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
magicType: { show: true, type: ['line', 'bar'] },
saveAsImage: {},
}
},
xAxis: {
type: 'time',
boundaryGap: false
},
yAxis: {
type: 'value',
boundaryGap: [0, '10%'],
axisLabel: {
formatter: (function (value){
return format_u_k_m(value)
})
}
},
dataZoom: [
{
type: 'inside',
start: 65,
end: 100
},
{
start: 0,
end: 20
},
],
series: [
{
name: 'Spots',
type: 'line',
smooth: true,
symbol: 'none',
itemStyle: {
color: '#078513'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: '#57fa75'
},
{
offset: 1,
color: '#118226'
}
])
},
data: dataMap
}
]
})
})
}
/**
* Chart creator
*
* @constructor
* @param {String} chart_id The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
constructor(chart_id,end_point) {
// Initialize the echarts instance based on the prepared dom
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
this.refresh(myChart,end_point);
//resize
var chart = echarts.init(document.querySelector("#"+chart_id), null);
window.addEventListener('resize',function(){
chart.resize();
})
};
}
//create object
let plot_dst = new dx_spots_trend ('chart-dx_spots_trend','/plot_get_dx_spots_trend');

View File

@ -1,8 +0,0 @@
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}};$jscomp.arrayIterator=function(a){return{next:$jscomp.arrayIteratorImpl(a)}};$jscomp.makeIterator=function(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):$jscomp.arrayIterator(a)};$jscomp.owns=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};$jscomp.ASSUME_ES5=!1;
$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){a!=Array.prototype&&a!=Object.prototype&&(a[b]=c.value)};
$jscomp.getGlobal=function(a){a=["object"==typeof globalThis&&globalThis,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global,a];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
$jscomp.polyfill=function(a,b,c,d){if(b){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];e in c||(c[e]={});c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})}};$jscomp.polyfill("Object.entries",function(a){return a?a:function(a){var c=[],b;for(b in a)$jscomp.owns(a,b)&&c.push([b,a[b]]);return c}},"es8","es3");
var dx_spots_trend=function(a,b){var c=document.getElementById(a);c=echarts.init(c);this.refresh(c,b);var d=echarts.init(document.querySelector("#"+a),null);window.addEventListener("resize",function(){d.resize()})};
dx_spots_trend.prototype.refresh=function(a,b){fetch(b).then(function(a){return a.json()}).then(function(b){var c=get_last_refresh(b),e=[];b=$jscomp.makeIterator(Object.entries(b.spots_trend));for(var f=b.next();!f.done;f=b.next()){var g=$jscomp.makeIterator(f.value);f=g.next().value;g=g.next().value;var h=[];h.push(f);h.push(g);e.push(h)}a.setOption({tooltip:{trigger:"axis",position:function(a){return[a[0],"10%"]}},title:{text:"DX SPOTS trend",subtext:c,top:"top",left:"left"},toolbox:{show:!0,showTitle:!1,
orient:"vertical",left:"right",top:"center",feature:{dataView:{show:!0,readOnly:!1},dataZoom:{yAxisIndex:"none"},restore:{},magicType:{show:!0,type:["line","bar"]},saveAsImage:{}}},xAxis:{type:"time",boundaryGap:!1},yAxis:{type:"value",boundaryGap:[0,"10%"],axisLabel:{formatter:function(a){return format_u_k_m(a)}}},dataZoom:[{type:"inside",start:65,end:100},{start:0,end:20}],series:[{name:"Spots",type:"line",smooth:!0,symbol:"none",itemStyle:{color:"#078513"},areaStyle:{color:new echarts.graphic.LinearGradient(0,
0,0,1,[{offset:0,color:"#57fa75"},{offset:1,color:"#118226"}])},data:e}]})})};var plot_dst=new dx_spots_trend("chart-dx_spots_trend","/plot_get_dx_spots_trend");

View File

@ -1,152 +0,0 @@
/********************************************************************************
* javascript used to build band/hour chart
* ******************************************************************************/
class hour_band {
/**
* refresh and populate chart
*
* @param {String} my_cart The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
* @param {Json} bands The frequency band list (160, 80, 60... UHF, SHF)
*/
refresh(my_chart,end_point,bands) {
// Asynchronous Data Loading
fetch(end_point)
.then((response) => response.json())
.then((data) => {
// Fill in the dat
var last_refresh=get_last_refresh(data);
//set hour indicator names
var hour_indicators=[];
//for (let i = 0; i < 24; i++) {
for (let i = 23; i > -1; i--) {
var hour_name={};
var s=i.toString();
hour_name["name"]=s;
hour_indicators.push(hour_name);
}
//cycling whithin each bands and hours
var dataMap=[];
bands.forEach(band_item => {
var qso=[];
//for (let i = 0; i < 24; i++) {
for (let i = 23; i > -1; i--) {
try {
var value=data["hour_band"][band_item][i];
if (typeof value == 'undefined') {
value = 0;
}
qso.push(value);
} catch (TypeError) {
//TODO
}
}
var tot={"value":qso,"name":band_item};
dataMap.push(tot);
});
//options
my_chart.setOption({
legend: {
orient: 'horizontal',
left: 'left',
bottom: 'bottom'
},
title: {
text: "DX SPOTS per hour in last month",
subtext: last_refresh,
top: 'top',
right: 'right',
},
tooltip: {
trigger: "axis",
},
toolbox: {
show: true,
showTitle: false,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
mark: { show: true },
dataView: { show: true, readOnly: true },
restore: { show: true },
saveAsImage: { show: true }
}
},
radar: {
shape: 'circle',
//startAngle: 285, //0 on left side
startAngle: 105, //0 on top
indicator: hour_indicators,
center: ['47%', '46%'],
axisName: {
color: 'rgb(80,80,80)',
},
},
series: [
{
lineStyle: {
width: 2
},
type: 'radar',
symbol: 'none',
data: dataMap,
tooltip: {
trigger: 'item',
formatter: (params) => {
return "Band: "+params.name;
},
},
emphasis: {
lineStyle: {
width: 4
}
},
}
]
})
})
}
/**
* Chart creator
*
* @constructor
* @param {String} chart_id The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
* @param {Json} band_freq The frequency band list (160, 80, 60... UHF, SHF)
*/
constructor(chart_id,end_point,band_freq) {
// Initialize the echarts instance based on the prepared dom
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
//populate bands array
var bands=[];
band_freq.forEach(function myFunction(item, index) {
bands[index]=item['id']
});
this.refresh(myChart,end_point,bands);
//resize
var chart = echarts.init(document.querySelector("#"+chart_id), null);
window.addEventListener('resize',function(){
chart.resize();
})
};
}
//create object
let plot_hb = new hour_band('chart-hour_band','/plot_get_hour_band',band_frequencies);

View File

@ -1,4 +0,0 @@
var hour_band=function(c,a,f){var b=document.getElementById(c);b=echarts.init(b);var d=[];f.forEach(function(b,c){d[c]=b.id});this.refresh(b,a,d);var e=echarts.init(document.querySelector("#"+c),null);window.addEventListener("resize",function(){e.resize()})};
hour_band.prototype.refresh=function(c,a,f){fetch(a).then(function(b){return b.json()}).then(function(b){for(var d=get_last_refresh(b),e=[],a=23;-1<a;a--){var g={};g.name=a.toString();e.push(g)}var h=[];f.forEach(function(a){for(var c=[],d=23;-1<d;d--)try{var e=b.hour_band[a][d];"undefined"==typeof e&&(e=0);c.push(e)}catch(k){}h.push({value:c,name:a})});c.setOption({legend:{orient:"horizontal",left:"left",bottom:"bottom"},title:{text:"DX SPOTS per hour in last month",subtext:d,top:"top",right:"right"},
tooltip:{trigger:"axis"},toolbox:{show:!0,showTitle:!1,orient:"vertical",left:"right",top:"center",feature:{mark:{show:!0},dataView:{show:!0,readOnly:!0},restore:{show:!0},saveAsImage:{show:!0}}},radar:{shape:"circle",startAngle:105,indicator:e,center:["47%","46%"],axisName:{color:"rgb(80,80,80)"}},series:[{lineStyle:{width:2},type:"radar",symbol:"none",data:h,tooltip:{trigger:"item",formatter:function(a){return"Band: "+a.name}},emphasis:{lineStyle:{width:4}}}]})})};
var plot_hb=new hour_band("chart-hour_band","/plot_get_hour_band",band_frequencies);

View File

@ -1,163 +0,0 @@
/********************************************************************************
* javascript used to build world dx spots live
* ******************************************************************************/
class world_dx_spots_live {
/**
* refresh and populate chart
*
* @param {String} my_cart The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
refresh(my_chart,end_point) {
// Asynchronous Data Loading
fetch(end_point)
.then((response) => response.json())
.then((data) => {
fetch('world.json')
.then(response => response.text())
.then(geoJson => {
var last_refresh=get_last_refresh(data);
var dataMap=[];
data["world_dx_spots_live"].forEach(function myFunction(item, index) {
//lon, lat, number of qso
dataMap.push({"value":[item["lat"],item["lon"],item["count"]]});
});
my_chart.hideLoading();
echarts.registerMap('WR', geoJson);
my_chart.setOption( {
visualMap: {
show: false,
min: 0,
max: 50,
inRange: {
symbolSize: [5, 20]
}
},
geo: {
type: 'map',
map: 'WR',
roam: true,
zoom: 1.2,
aspectScale: 0.70,
layoutCenter: ['50%', '54%'],
layoutSize: '100%',
itemStyle: {
normal: {
areaColor: '#323c48',
borderColor: '#111'
},
emphasis: {
areaColor: '#2a333d'
}
},
label: {
emphasis: {
show: false
}
},
},
tooltip: {
trigger: 'item',
formatter: function(val) {
var out="lat: "+val.value[0]+
" lon: "+val.value[1]+"</BR>"+
"Spots: <STRONG>"+ val.value[2] +"</STRONG></BR>";
return out;
}
},
toolbox: {
show: true,
showTitle: false,
orient: 'vertical',
left: 'right',
top: 'center',
iconStyle: {
borderColor: '#fff',
},
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
restore: { show: true },
saveAsImage: { show: true }
}
},
legend: {
show: false
},
//backgroundColor: '#404a59',
backgroundColor: '#596475',
title: {
text: "World DX SPOTS in last hour",
subtext: last_refresh,
top: 'top',
right:'right',
textStyle: {
color: '#fff'
},
subtextStyle: {
color: '#fff'
}
},
series: [
{
type: 'scatter',
coordinateSystem: 'geo',
data:dataMap,
label: {
emphasis: {
position: 'right',
show: false
}
},
itemStyle: {
normal: {
color: '#eea638'
}
},
/*
symbolSize: function (val) {
return val[2] / 4;
},
*/
}
]
}) //end options
}
)
})
}
/**
* Chart creator
*
* @constructor
* @param {String} chart_id The HTML id where place chart
* @param {string} end_point This is backend end-point for chart datas
*/
constructor(chart_id,end_point) {
// Initialize the echarts instance based on the prepared dom
var chartDom = document.getElementById(chart_id);
var myChart = echarts.init(chartDom);
this.refresh(myChart,end_point);
//resize
var chart = echarts.init(document.querySelector("#"+chart_id), null);
window.addEventListener('resize',function(){
chart.resize();
})
}
}
//create object
let plot_wdsl = new world_dx_spots_live ('chart-world_dx_spots_live','/plot_get_world_dx_spots_live');

View File

@ -1,4 +0,0 @@
var world_dx_spots_live=function(b,c){var a=document.getElementById(b);a=echarts.init(a);this.refresh(a,c);var d=echarts.init(document.querySelector("#"+b),null);window.addEventListener("resize",function(){d.resize()})};
world_dx_spots_live.prototype.refresh=function(b,c){fetch(c).then(function(a){return a.json()}).then(function(a){fetch("world.json").then(function(a){return a.text()}).then(function(c){var d=get_last_refresh(a),e=[];a.world_dx_spots_live.forEach(function(a,b){e.push({value:[a.lat,a.lon,a.count]})});b.hideLoading();echarts.registerMap("WR",c);b.setOption({visualMap:{show:!1,min:0,max:50,inRange:{symbolSize:[5,20]}},geo:{type:"map",map:"WR",roam:!0,zoom:1.2,aspectScale:.7,layoutCenter:["50%","54%"],
layoutSize:"100%",itemStyle:{normal:{areaColor:"#323c48",borderColor:"#111"},emphasis:{areaColor:"#2a333d"}},label:{emphasis:{show:!1}}},tooltip:{trigger:"item",formatter:function(a){return"lat: "+a.value[0]+" lon: "+a.value[1]+"</BR>Spots: <STRONG>"+a.value[2]+"</STRONG></BR>"}},toolbox:{show:!0,showTitle:!1,orient:"vertical",left:"right",top:"center",iconStyle:{borderColor:"#fff"},feature:{mark:{show:!0},dataView:{show:!0,readOnly:!1},restore:{show:!0},saveAsImage:{show:!0}}},legend:{show:!1},backgroundColor:"#596475",
title:{text:"World DX SPOTS in last hour",subtext:d,top:"top",right:"right",textStyle:{color:"#fff"},subtextStyle:{color:"#fff"}},series:[{type:"scatter",coordinateSystem:"geo",data:e,label:{emphasis:{position:"right",show:!1}},itemStyle:{normal:{color:"#eea638"}}}]})})})};var plot_wdsl=new world_dx_spots_live("chart-world_dx_spots_live","/plot_get_world_dx_spots_live");

2
static/js/rel/README.md Normal file
View File

@ -0,0 +1,2 @@
This folder `static/js/rel/` contains the javascript modules for **release** environment.
You **CANNOT change** these scripts. Make changes only in `static/js/dev/` folder

1
static/js/rel/callsign_inline.min.js vendored Normal file
View File

@ -0,0 +1 @@
var my_adxo_events=JSON.parse(my_adxo_events_json.replaceAll("\t","")),qryString="spotlist?c="+my_callsign;fetch(qryString).then(l=>l.json()).then(o=>{try{tb.build(o,my_callsign)}catch(l){console.log(l),console.log(l.stack),console.log(o)}});

1
static/js/rel/callsign_search.min.js vendored Normal file
View File

@ -0,0 +1 @@
function myCallsignSearch(){0<(callsign=document.getElementById("callsignInput").value).replace(/\s/g,"").length&&(location.href="/callsign.html?c=".concat(callsign.trim().toUpperCase()))}

1
static/js/rel/clock.min.js vendored Normal file
View File

@ -0,0 +1 @@
function showTime(){var e=new Date;let t=new Date(e.getTime()+6e4*e.getTimezoneOffset()).toTimeString().split(" ")[0];t=t.split(":")[0]+":"+t.split(":")[1],document.getElementById("MyClockDisplay").innerText=t,document.getElementById("MyClockDisplay").textContent=t,setTimeout(showTime,1e3)}showTime();

1
static/js/rel/common.min.js vendored Normal file
View File

@ -0,0 +1 @@
function setCookie(e,t,n){var r=new Date,n=(r.setTime(r.getTime()+24*n*60*60*1e3),"expires="+r.toUTCString());document.cookie=e+"="+t+";"+n+";path=/;Samesite=Strict;Secure=True"}function getCookie(e){var n=e+"=",r=decodeURIComponent(document.cookie).split(";");for(let t=0;t<r.length;t++){let e=r[t];for(;" "==e.charAt(0);)e=e.substring(1);if(0==e.indexOf(n))return e.substring(n.length,e.length)}return""}function get_last_refresh(e){var t=new Date(0),e=(t.setUTCSeconds(e.last_refresh),(e,t=2)=>(""+new Array(t).fill(0)+e).slice(-t)),n=e(t.getHours()),e=e(t.getMinutes()),r=get_months_names()[t.getMonth()];return"Data refresh: "+t.getDate()+" of "+r+" "+t.getFullYear()+" at "+n+":"+e}function get_months_names(){var t=[];for(let e=1;e<13;e++){var n=new Date;n.setMonth(e-1),t.push(n.toLocaleString("en-US",{month:"short"}))}return t}function format_u_k_m(e){let t,n=1;e<0&&(n=-1);e=Math.abs(e);return 0==e?t=e:0<e&&e<1e3?t=e*n:1e3<=e&&e<1e6?t=e/1e3*n+"K":1e6<=e&&(t=e/1e6*n+"M"),t}function selectElement(e,t){document.getElementById(e).value=t}function addEventHandler(e,t,n){e.addEventListener?e.addEventListener(t,n,!1):e.attachEvent&&e.attachEvent("on"+t,n)}function setText(e,t){document.getElementById(e).innerHTML=t}

1
static/js/rel/cookie_consent.min.js vendored Normal file
View File

@ -0,0 +1 @@
let cookie_modal=new bootstrap.Modal(document.getElementById("cookie_consent_modal"),{keyboard:!1});cookie_modal.show(),document.getElementById("cookie_consent_btn").onclick=function(){setCookie("cookie_consent",!0,30),cookie_modal.hide()};

1
static/js/rel/index_inline.min.js vendored Normal file
View File

@ -0,0 +1 @@
var my_adxo_events=JSON.parse(my_adxo_events_json.replaceAll("\t","")),myRefresh=(refresh_timer(),setInterval(refresh_timer,timer_interval_json));window.onload=()=>{document.getElementById("form-filters").addEventListener("submit",mySearch)};

View File

@ -1 +1 @@
"serviceWorker"in navigator&&(navigator.serviceWorker.controller?console.log("[PWA Builder] active service worker found, no need to register"):navigator.serviceWorker.register("service-worker.js",{scope:"./"}).then(function(a){console.log("[PWA Builder] Service worker has been registered for scope: "+a.scope)}));
"serviceWorker"in navigator&&(navigator.serviceWorker.controller?console.log("[PWA Builder] active service worker found, no need to register"):navigator.serviceWorker.register("service-worker.js",{scope:"./"}).then(function(e){console.log("[PWA Builder] Service worker has been registered for scope: "+e.scope)}));

1
static/js/rel/load_css.min.js vendored Normal file
View File

@ -0,0 +1 @@
!function(c){"use strict";function e(e,t,n,o){var i,r,d=c.document,a=d.createElement("link"),f=(r=t||(i=(d.body||d.getElementsByTagName("head")[0]).childNodes)[i.length-1],d.styleSheets);if(o)for(var l in o)o.hasOwnProperty(l)&&a.setAttribute(l,o[l]);function s(e){for(var t=a.href,n=f.length;n--;)if(f[n].href===t)return e();setTimeout(function(){s(e)})}function u(){a.addEventListener&&a.removeEventListener("load",u),a.media=n||"all"}return a.rel="stylesheet",a.href=e,a.media="only x",function e(t){if(d.body)return t();setTimeout(function(){e(t)})}(function(){r.parentNode.insertBefore(a,t?r:r.nextSibling)}),a.addEventListener&&a.addEventListener("load",u),a.onloadcssdefined=s,s(u),a}"undefined"!=typeof exports?exports.loadCSS=e:c.loadCSS=e}("undefined"!=typeof global?global:this);

View File

@ -0,0 +1 @@
class band_activity{refresh(n,t,e,o,i){fetch(t+"?continent="+e).then(t=>t.json()).then(t=>{var e=get_last_refresh(t),t=Array.from(t["band activity"]).map(function(t){return[t[1],t[0],t[2]||"-"]});n.setOption({tooltip:{position:"top",formatter:function(t){return t.seriesName+" on "+t.name+" band: <strong>"+t.data[2]+"</strong>"}},title:{text:"Band activity",subtext:e,top:"top",left:"left"},toolbox:{show:!0,showTitle:!1,orient:"vertical",right:"right",top:"bottom",feature:{mark:{show:!0},dataView:{show:!0,readOnly:!0},restore:{show:!0},saveAsImage:{show:!0}}},grid:{height:"80%",left:25,top:50,right:60,bottom:0,show:!0,backgroundColor:"rgb(255, 255, 255)"},xAxis:{type:"category",data:o,axisTick:{show:!0},axisLine:{show:!1},splitArea:{show:!0}},yAxis:{type:"category",data:i,axisTick:{show:!0},axisLine:{show:!1},splitArea:{show:!0}},visualMap:{calculable:!0,orient:"vertical",right:"right",top:"center",min:0,max:30,inRange:{color:["#ffffe6","yellow","red"]}},series:[{name:"Spots",type:"heatmap",data:t,label:{show:!1},emphasis:{itemStyle:{shadowBlur:10,shadowColor:"rgba(100, 0, 0, 0.5)"}}}]})})}constructor(t,e,n,o){var i=document.getElementById(t),s=echarts.init(i),a=[],r=(n.forEach(function(t,e){a[e]=t.id}),[]),c=(o.forEach(function(t,e){r[e]=t.id}),getCookie("user_region")),d=getCookie("user_region_desc"),h=(c||(c="EU",d="Europe",setCookie("user_region",c,60),setCookie("user_region_desc",d,60)),selectElement("continentInput",c),addEventHandler(document.getElementById("continentInput"),"change",function(){c=this.value,d=this.options[this.selectedIndex].text,setCookie("user_region",c,60),setCookie("user_region_desc",d,60),plot_ba.refresh(s,e,c,r,a),setText("txt_continent","  Based on DX SPOTS from stations in "+d+" during the last 15 minutes, displayed by Continent and Band")}),setText("txt_continent","  Based on DX SPOTS from stations in "+d+" during the last 15 minutes, displayed by Continent and Band"),this.refresh(s,e,c,r,a),echarts.init(document.querySelector("#"+t),null));window.addEventListener("resize",function(){h.resize()})}}let plot_ba=new band_activity("chart-band_activity","/plot_get_heatmap_data",continents_cq,band_frequencies);

View File

@ -0,0 +1 @@
class dx_spots_per_month{refresh(h,t){fetch(t).then(t=>t.json()).then(e=>{var t=get_last_refresh(e),s=(new Date).getFullYear(),o=(+s).toString(),r=(s-1).toString(),s=(s-2).toString(),a=get_months_names(),n=[],i=[],p=[];for(let t=1;t<13;t++)n.push(e.spots_per_month[t].year_0),i.push(e.spots_per_month[t].year_1),p.push(e.spots_per_month[t].year_2);h.setOption({tooltip:{trigger:"axis",axisPointer:{type:"shadow"}},title:{text:"DX SPOTS per month",subtext:t,top:"top",left:"left"},legend:{data:[s,r,o],bottom:"bottom"},toolbox:{show:!0,showTitle:!1,orient:"vertical",left:"right",top:"center",feature:{mark:{show:!0},dataView:{show:!0,readOnly:!1},magicType:{show:!0,type:["line","bar","stack"]},restore:{show:!0},saveAsImage:{show:!0}}},xAxis:[{type:"category",axisTick:{show:!1},data:a}],yAxis:[{type:"value",axisLabel:{formatter:function(t){return format_u_k_m(t)}}}],series:[{name:s,type:"bar",barGap:0,emphasis:{focus:"series"},data:p},{name:r,type:"bar",emphasis:{focus:"series"},data:i},{name:o,type:"bar",emphasis:{focus:"series"},data:n}]})})}constructor(t,e){var s=document.getElementById(t),s=echarts.init(s),o=(this.refresh(s,e),echarts.init(document.querySelector("#"+t),null));window.addEventListener("resize",function(){o.resize()})}}let plot_dspm=new dx_spots_per_month("chart-dx_spots_x_month","/plot_get_dx_spots_per_month");

View File

@ -0,0 +1 @@
class dx_spots_trend{refresh(a,t){fetch(t).then(t=>t.json()).then(t=>{var e,o,r=get_last_refresh(t),s=[];for([e,o]of Object.entries(t.spots_trend)){var n=[];n.push(e),n.push(o),s.push(n)}a.setOption({tooltip:{trigger:"axis",position:function(t){return[t[0],"10%"]}},title:{text:"DX SPOTS trend",subtext:r,top:"top",left:"left"},toolbox:{show:!0,showTitle:!1,orient:"vertical",left:"right",top:"center",feature:{dataView:{show:!0,readOnly:!1},dataZoom:{yAxisIndex:"none"},restore:{},magicType:{show:!0,type:["line","bar"]},saveAsImage:{}}},xAxis:{type:"time",boundaryGap:!1},yAxis:{type:"value",boundaryGap:[0,"10%"],axisLabel:{formatter:function(t){return format_u_k_m(t)}}},dataZoom:[{type:"inside",start:65,end:100},{start:0,end:20}],series:[{name:"Spots",type:"line",smooth:!0,symbol:"none",itemStyle:{color:"#078513"},areaStyle:{color:new echarts.graphic.LinearGradient(0,0,0,1,[{offset:0,color:"#57fa75"},{offset:1,color:"#118226"}])},data:s}]})})}constructor(t,e){var o=document.getElementById(t),o=echarts.init(o),r=(this.refresh(o,e),echarts.init(document.querySelector("#"+t),null));window.addEventListener("resize",function(){r.resize()})}}let plot_dst=new dx_spots_trend("chart-dx_spots_trend","/plot_get_dx_spots_trend");

1
static/js/rel/plot_hour_band.min.js vendored Normal file
View File

@ -0,0 +1 @@
class hour_band{refresh(i,t,s){fetch(t).then(t=>t.json()).then(a=>{var t=get_last_refresh(a),e=[];for(let t=23;-1<t;t--){var r={},o=t.toString();r.name=o,e.push(r)}var n=[];s.forEach(e=>{var r=[];for(let t=23;-1<t;t--)try{var o=a.hour_band[e][t];r.push(o=void 0===o?0:o)}catch(t){}n.push({value:r,name:e})}),i.setOption({legend:{orient:"horizontal",left:"left",bottom:"bottom"},title:{text:"DX SPOTS per hour in last month",subtext:t,top:"top",right:"right"},tooltip:{trigger:"axis"},toolbox:{show:!0,showTitle:!1,orient:"vertical",left:"right",top:"center",feature:{mark:{show:!0},dataView:{show:!0,readOnly:!0},restore:{show:!0},saveAsImage:{show:!0}}},radar:{shape:"circle",startAngle:105,indicator:e,center:["47%","46%"],axisName:{color:"rgb(80,80,80)"}},series:[{lineStyle:{width:2},type:"radar",symbol:"none",data:n,tooltip:{trigger:"item",formatter:t=>"Band: "+t.name},emphasis:{lineStyle:{width:4}}}]})})}constructor(t,e,r){var o=document.getElementById(t),o=echarts.init(o),a=[],n=(r.forEach(function(t,e){a[e]=t.id}),this.refresh(o,e,a),echarts.init(document.querySelector("#"+t),null));window.addEventListener("resize",function(){n.resize()})}}let plot_hb=new hour_band("chart-hour_band","/plot_get_hour_band",band_frequencies);

View File

@ -0,0 +1 @@
class world_dx_spots_live{refresh(s,e){fetch(e).then(e=>e.json()).then(r=>{fetch("world.json").then(e=>e.text()).then(e=>{var t=get_last_refresh(r),o=[];r.world_dx_spots_live.forEach(function(e,t){o.push({value:[e.lat,e.lon,e.count]})}),s.hideLoading(),echarts.registerMap("WR",e),s.setOption({visualMap:{show:!1,min:0,max:50,inRange:{symbolSize:[5,20]}},geo:{type:"map",map:"WR",roam:!0,zoom:1.2,aspectScale:.7,layoutCenter:["50%","54%"],layoutSize:"100%",itemStyle:{normal:{areaColor:"#323c48",borderColor:"#111"},emphasis:{areaColor:"#2a333d"}},label:{emphasis:{show:!1}}},tooltip:{trigger:"item",formatter:function(e){return"Spots: <STRONG>"+e.value[2]+"</STRONG>"}},toolbox:{show:!0,showTitle:!1,orient:"vertical",left:"right",top:"center",iconStyle:{borderColor:"#fff"},feature:{mark:{show:!0},dataView:{show:!0,readOnly:!1},restore:{show:!0},saveAsImage:{show:!0}}},legend:{show:!1},backgroundColor:"#596475",title:{text:"World DX SPOTS in last hour",subtext:t,top:"top",right:"right",textStyle:{color:"#fff"},subtextStyle:{color:"#fff"}},series:[{type:"scatter",coordinateSystem:"geo",data:o,label:{emphasis:{position:"right",show:!1}},itemStyle:{normal:{color:"#eea638"}}}]})})})}constructor(e,t){var o=document.getElementById(e),o=echarts.init(o),r=(this.refresh(o,t),echarts.init(document.querySelector("#"+e),null));window.addEventListener("resize",function(){r.resize()})}}let plot_wdsl=new world_dx_spots_live("chart-world_dx_spots_live","/plot_get_world_dx_spots_live");

1
static/js/rel/table.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,353 +0,0 @@
class table_builder {
/**
* Table builder constructor
* @param selector {string} The html identifier where build the spots table
*/
constructor(selector) {
this.selector=selector;
this.current_data=[];
}
/**
* @return last_rowid {integer} the last rowid
*/
getLastRowId() {
let last_rowid;
if (this.current_data == null) {
last_rowid=0;
} else {
if (this.current_data.length < 1) {
last_rowid=0;
} else {
last_rowid=this.current_data[0].rowid;
}
}
return last_rowid;
}
/**
* @param line {object} with the data of a single spot
* @param isnew {boolean} is the new rows indicator
* @param dt_current {string} current date in dd/mm/yyyy format
* @param callsign {string} optional callsign
* @return a &lt;tr&gt; dom element
*/
#buildRow(line, isnew, dt_current,callsign=""){
const row = document.createElement("tr");
row.id= line.rowid;
if (callsign.length>0) {
if (callsign == line.de) {
row.id= line.rowid;
} else if (callsign == line.dx) {
row.id= line.rowid;
}
} else if (isnew) {
row.className="table-info";
}
//Column: DE search on QRZ
const i_qrzde = document.createElement("i");
i_qrzde.className="bi-search";
i_qrzde.role="button";
i_qrzde.ariaLabel=line.de;
const a_qrzde = document.createElement("a");
a_qrzde.href=qrz_url + line.de;
a_qrzde.target="_blank";
a_qrzde.rel="noopener";
const span_qrzde = document.createElement("span");
//Mark DE if it found in callsign search
if (line.de == callsign) {
const mark_qrzde = document.createElement("mark");
mark_qrzde.textContent = line.de;
span_qrzde.appendChild(mark_qrzde)
} else {
span_qrzde.textContent='\xa0' + line.de;
}
const td_qrzde = document.createElement("td");
a_qrzde.appendChild(i_qrzde);
td_qrzde.appendChild(a_qrzde);
td_qrzde.appendChild(span_qrzde);
row.append(td_qrzde);
//Column: frequency
var freq = Intl.NumberFormat('it-IT', {
style: 'decimal'
}).format(line.freq);
const span_freq = document.createElement("span");
span_freq.className="badge bg-warning text-dark badge-responsive";
span_freq.textContent=freq;
const td_freq = document.createElement("td");
td_freq.appendChild(span_freq);
row.appendChild(td_freq);
//Column: DX (with ADXO Management)
var adxo = findAdxo(my_adxo_events, line.dx);
var adxo_link = '<a href=' + adxo_url + ' target=_blank rel=noopener >NG3K Website</a>'
const i_qrzdx = document.createElement("i");
i_qrzdx.className="bi-search";
i_qrzdx.role="button";
i_qrzdx.ariaLabel=line.dx;
const a_qrzdx = document.createElement("a");
a_qrzdx.href=qrz_url + line.dx;
a_qrzdx.target="_blank";
a_qrzdx.rel="noopener";
const span_qrzdx = document.createElement("span");
//Mark DX if it found in callsign search
const mark_qrzdx = document.createElement("mark");
mark_qrzdx.textContent = line.dx;
if (line.dx == callsign) {
span_qrzdx.appendChild(mark_qrzdx)
} else {
span_qrzdx.textContent='\xa0' + line.dx;
}
if (adxo != undefined) {
const i_adxo = document.createElement("i");
i_adxo.tabIndex=0;
i_adxo.className="bi-megaphone-fill";
i_adxo.style="color: cornflowerblue;";
i_adxo.role="button";
i_adxo.ariaLabel="dx_operations";
i_adxo.setAttribute('data-bs-container', "body" );
i_adxo.setAttribute('data-bs-toggle', "popover" );
i_adxo.setAttribute('data-bs-trigger', "focus" );
i_adxo.setAttribute('data-bs-sanitizer', "true");
i_adxo.setAttribute('data-bs-placement', "auto");
i_adxo.setAttribute('data-bs-html', "true");
i_adxo.setAttribute('data-bs-title', "Announced DX Op.: " + adxo.summary);
i_adxo.setAttribute('data-bs-content', adxo.description + "data from " + adxo_link);
span_qrzdx.appendChild(i_adxo);
}
const td_qrzdx = document.createElement("td");
a_qrzdx.appendChild(i_qrzdx);
td_qrzdx.appendChild(a_qrzdx);
td_qrzdx.append(span_qrzdx);
row.appendChild(td_qrzdx);
//Column: Flag
try {
const span_flag=document.createElement("span");
span_flag.className="img-flag fi fi-" + line.iso;
span_flag.setAttribute('data-bs-container', "body" );
span_flag.setAttribute('data-bs-toggle', "popover" );
span_flag.setAttribute('data-bs-trigger', "hover" );
span_flag.setAttribute('data-bs-placement', "left");
span_flag.setAttribute('data-bs-content', line.country );
const td_flag = document.createElement("td");
td_flag.appendChild(span_flag);
row.appendChild(td_flag);
} catch (err) {
console.log(err);
console.log("error creating flag");
const td_flag = document.createElement("td");
row.appendChild(td_flag);
}
//Column: Country
const td_country_code = document.createElement("td");
td_country_code.className="d-none d-lg-table-cell d-xl-table-cell";
td_country_code.textContent = line.country;
row.appendChild(td_country_code);
//Column: Comment
const td_comm = document.createElement("td");
td_comm.className="d-none d-lg-table-cell d-xl-table-cell";
td_comm.textContent = line.comm;
row.appendChild(td_comm);
//Column: UTC
let dt = new Date(line.time * 1000);
let hh = '00' + dt.getUTCHours();
hh = hh.substring(hh.length - 2, hh.length);
let mi = '00' + dt.getMinutes();
mi = mi.substring(mi.length - 2, mi.length);
let dd = '00' + dt.getUTCDate();
dd = dd.substring(dd.length - 2, dd.length);
let mo = '00' + (Number(dt.getUTCMonth()) + 1);
mo = mo.substring(mo.length - 2, mo.length);
let yy = dt.getUTCFullYear();
let tm = hh + ':' + mi;
dt = dd + '/' + mo + '/' + yy;
const div_date_time = document.createElement("div");
div_date_time.className="d-flex flex-column";
const p_time = document.createElement("div");
p_time.textContent=tm;
div_date_time.appendChild(p_time);
if (dt != dt_current) {
const p_date = document.createElement("div");
p_date.textContent=dt;
div_date_time.appendChild(p_date);
}
row.appendChild(div_date_time);
//Finally append the row created to the table
return row;
}
/**
* Build the table with the spot
*
* @param data {json} The payload with all the spots received from cluster
* @param rl {json} Row List
* @param callsign {string} An optional parameter with the callsign to search
*/
build(data, callsign) {
if (data != null) {
//get current date
let d = new Date();
let dd_current = '00' + d.getUTCDate();
dd_current = dd_current.substring(dd_current.length - 2, dd_current.length);
let mo_current = '00' + (Number(d.getUTCMonth()) + 1);
mo_current = mo_current.substring(mo_current.length - 2, mo_current.length);
let yy_current = d.getUTCFullYear();
let dt_current = dd_current + '/' + mo_current + '/' + yy_current;
//empty the table
document.getElementById(this.selector).replaceChildren();
//insert in table new elements
let merge_data = [];
for (let i = 0; i < data.length; i++) {
document.getElementById(this.selector).append(this.#buildRow(data[i],true,dt_current,callsign));
merge_data.push(data[i]);
}
//insert in html table previous elements
for (let i = 0; i < this.current_data.length-data.length; i++) {
document.getElementById(this.selector).append(this.#buildRow(this.current_data[i],false,dt_current,callsign));
merge_data.push(this.current_data[i]);
}
//replace current data with merge
this.current_data=merge_data;
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'))
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl)
})
try {
return;
} catch (err) {
return;
}
}
}
} //end class
/********************************************************************************
* javascript used to popolate main table with spots
* ******************************************************************************/
const adxo_url = 'https://www.ng3k.com/misc/adxo.html'
const qrz_url = 'https://www.qrz.com/db/'
const tb = new table_builder('bodyspot');
/**
* Decode Announced Dx Operation (ng3k)
*
* @param adxo {adxo} This is the json containing all the dxo events
* @param callsign_to_find {callsign_to_find} The callsign of the current dx line
*/
function findAdxo(adxo, callsign_to_find) {
if (adxo) {
for (let i = 0; i < adxo.length; i++) {
if (adxo[i].callsign == callsign_to_find) {
return adxo[i];
}
}
}
}
/**
* Function to filter spot when pressed the search button on filter
* This function trigger the search, also triggered by timer
*/
function mySearch(event) {
event.preventDefault();
refresh_timer(); //force the call of query
}
/**
* Function for construct query string for single value selection
*
* @param id {string} The html identifier used for filter
* @param param {string}the parameter for the query
* @param len {number} The maximum number of element that could be selected; use -1 if the filter permits a single selection
* @param qrystr {string} Th initial query string to be completed with the new filter
*/
function getFilter(id, param, len, qrystr) {
selectedFilter = [].map.call(document.getElementById(id).selectedOptions, option => option.value);
var qryFilter = '';
if (selectedFilter.length < len || len == -1) {
qryFilter = selectedFilter.map(function(el) {
if (el) {
return param + '=' + el;
} else {
return '';
}
}).join('&');
qrystr = qrystr.concat('&'.concat(qryFilter));
if (qrystr.substring(0, 1) == '&') {
qrystr = qrystr.substring(1)
}
}
return qrystr;
}
/**
* Search / Filter cluster spot based on filter settings
* Gets the filter values, constructs the query parameter and
* make the request to the server
*/
function refresh_timer() {
let qryAll = '';
//get other filters
qryAll = getFilter('band', 'b', 14, qryAll);
qryAll = getFilter('de_re', 'e', 7, qryAll);
qryAll = getFilter('dx_re', 'x', 7, qryAll);
qryAll = getFilter('mode', 'm', 3, qryAll);
qryAll = getFilter('cqdeInput', 'qe', -1, qryAll);
qryAll = getFilter('cqdxInput', 'qx', -1, qryAll);
//Composing query string
let qryString = ('spotlist?lr=').concat(tb.getLastRowId());
if (qryAll) {
qryString = qryString.concat(qryAll);
}
console.log(qryString)
//Open a new connection, using the GET request on the URL endpoint
fetch(qryString)
.then((response) => response.json())
.then((data_new) => {
try {
tb.build(data_new);
} catch (err) {
console.log(err);
console.log(err.stack);
console.log(data_new);
}
})
}

View File

@ -1,13 +0,0 @@
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(a,b,c){a instanceof String&&(a=String(a));for(var d=a.length,k=0;k<d;k++){var g=a[k];if(b.call(c,g,k,a))return{i:k,v:g}}return{i:-1,v:void 0}};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;
$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){a!=Array.prototype&&a!=Object.prototype&&(a[b]=c.value)};$jscomp.getGlobal=function(a){a=["object"==typeof globalThis&&globalThis,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global,a];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
$jscomp.polyfill=function(a,b,c,d){if(b){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var k=a[d];k in c||(c[k]={});c=c[k]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})}};$jscomp.polyfill("Array.prototype.find",function(a){return a?a:function(a,c){return $jscomp.findInternal(this,a,c).v}},"es6","es3");
$jscomp.polyfill("Array.from",function(a){return a?a:function(a,c,d){c=null!=c?c:function(a){return a};var b=[],g="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];if("function"==typeof g){a=g.call(a);for(var f=0;!(g=a.next()).done;)b.push(c.call(d,g.value,f++))}else for(g=a.length,f=0;f<g;f++)b.push(c.call(d,a[f],f));return b}},"es6","es3");var adxo_url="https://www.ng3k.com/misc/adxo.html",qrz_url="https://www.qrz.com/db/";
function findAdxo(a,b){if(a)for(var c=0;c<a.length;c++)if(a[c].callsign==b)return a[c]}
function buildHtmlTable(a,b,c,d){if(null!=b){var k=[],g=new Date,f="00"+g.getUTCDate();f=f.substring(f.length-2,f.length);var h="00"+(Number(g.getUTCMonth())+1);h=h.substring(h.length-2,h.length);g=g.getUTCFullYear();dt_current=f+"/"+h+"/"+g;$(a).empty();for(var e=0;e<b.length;e++){k[e]=b[e].rowid;f=$('<tr id="'+b[e].rowid+'"/>');h=c.find(function(a){return a==b[e].rowid});void 0!=d?d==b[e].de?f=$('<tr id="'+b[e].rowid+'"/>'):d==b[e].dx&&(f=$('<tr id="'+b[e].rowid+'"/>')):void 0==h&&0<c.length&&(f=
$('<tr class="table-info" id="'+b[e].rowid+'"/>'));de=b[e].de==d?"<mark>"+b[e].de+"</mark>":b[e].de;f.append($("<td/>").html('<a href="'+qrz_url+b[e].de+'" target="_blank" rel="noopener"><i class="bi-search" role="button" aria-label="'+b[e].de+'"></i></a><span>&nbsp'+de+"</span></b>"));h=Intl.NumberFormat("it-IT",{style:"decimal"}).format(b[e].freq);f.append($("<td/>").html('<span class="badge bg-warning text-dark badge-responsive">'+h+"</span>"));dx=b[e].dx==d?"<mark>"+b[e].dx+"</mark>":b[e].dx;
h=findAdxo(my_adxo_events,b[e].dx);g="<a href="+adxo_url+" target=_blank rel=noopener >NG3K Website</a>";void 0!=h&&(dx=dx+'&nbsp<i tabindex="0" class="bi-megaphone-fill" style="color: cornflowerblue; " role="button" aria-label="dx_operations" data-bs-container="body" data-bs-toggle="popover" data-bs-trigger="focus" data-bs-sanitize="true" data-bs-placement="auto" data-bs-html="true" data-bs-title="Announced DX Op.: '+h.summary+'" data-bs-content="'+h.description+" data from &nbsp"+g+'"></i>');f.append($("<td/>").html('<a href="'+
qrz_url+b[e].dx+'" target="_blank" rel="noopener"><i class="bi-search" role="button" aria-label="'+b[e].dx+'"></i></a><span>&nbsp'+dx+"</span>"));try{f.append($("<td/>").html('<span class="img-flag fi fi-'+b[e].iso+'" data-bs-container="body" data-bs-toggle="popover" data-bs-trigger="hover" data-bs-placement="left" data-bs-content="'+b[e].country+'"></span>'))}catch(p){f.append($("<td/>"))}f.append($('<td class="d-none d-lg-table-cell d-xl-table-cell"/>').html(b[e].country));f.append($('<td class="d-none d-lg-table-cell d-xl-table-cell"/>').html(b[e].comm));
var l=new Date(1E3*b[e].time);h="00"+l.getUTCHours();h=h.substring(h.length-2,h.length);g="00"+l.getMinutes();g=g.substring(g.length-2,g.length);var m="00"+l.getUTCDate();m=m.substring(m.length-2,m.length);var n="00"+(Number(l.getUTCMonth())+1);n=n.substring(n.length-2,n.length);l=l.getUTCFullYear();tm=h+":"+g;l=m+"/"+n+"/"+l;l==dt_current?f.append($("<td/>").html(tm)):f.append($("<td/>").html('<table class="table-sm table-borderless"><tbody><tr style="background-color:transparent"><td>'+tm+"</td></tr><tr><td>"+
l+"</td></tr></tbody></table>"));$(a).append(f)}$(function(){$('[data-bs-toggle="popover"]').popover({container:a})});try{return Array.from(k)}catch(p){}}}function mySearch(a){a.preventDefault();myTimer()}
function getFilter(a,b,c,d){selectedFilter=[].map.call(document.getElementById(a).selectedOptions,function(a){return a.value});a="";if(selectedFilter.length<c||-1==c)a=selectedFilter.map(function(a){return a?b+"="+a:""}).join("&"),d=d.concat("&".concat(a)),"&"==d.substring(0,1)&&(d=d.substring(1));return d}
function myTimer(){var a=getFilter("band","b",14,"");a=getFilter("de_re","e",7,a);a=getFilter("dx_re","x",7,a);a=getFilter("mode","m",3,a);a=getFilter("cqdeInput","qe",-1,a);a=getFilter("cqdxInput","qx",-1,a);var b="spotlist";a&&(b=b.concat("?".concat(a)));fetch(b).then(function(a){return a.json()}).then(function(a){try{rows_list=buildHtmlTable("#bodyspot",a,rows_list)}catch(d){console.log(d),console.log(d.stack),console.log(a)}})};

View File

@ -13,7 +13,7 @@
<link rel="icon" href="/static/images/icons/spider_ico_master.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/images/icons/icon-apple.png">
<link rel="manifest" href="/static/manifest.webmanifest">
<link rel="stylesheet" href="/static/css/style.min.css">
<link rel="stylesheet" href="/static/css/rel/style.min.css">
<link rel="preload" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" as="style" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous" onload="this.rel='stylesheet' ">
<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css"></noscript>
@ -21,7 +21,7 @@
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/6.6.6/css/flag-icons.min.css" as="style" integrity="sha512-uvXdJud8WaOlQFjlz9B15Yy2Au/bMAvz79F7Xa6OakCl2jvQPdHD0hb3dEqZRdSwG4/sknePXlE7GiarwA/9Wg==" crossorigin="anonymous" onload="this.rel='stylesheet'" >
<noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/6.6.6/css/flag-icons.min.css"></noscript>
<script class="spiderscript" src="static/js/load_css.min.js"></script>
<script src="static/js/rel/load_css.min.js"></script>
{% endblock head %}
</head>
<body>
@ -89,21 +89,18 @@
<span id="version">v2.4.1</span>
</div>
</footer>
<script async class="spiderscript" src="static/js/clock.min.js"></script>
<script async class="spiderscript" src="static/js/copy_date.min.js"></script>
<script async class="spiderscript" src="static/js/load-sw.min.js"></script>
<script async src="static/js/rel/clock.min.js"></script>
<script async src="static/js/rel/copy_date.min.js"></script>
<script async src="static/js/rel/load-sw.min.js"></script>
<script nonce="sedfGFG32xs">
{% block app_data %}
var my_callsign='{{callsign}}';
{% endblock app_data %}
</script>
<script defer class="spiderscript" src="static/js/common.js"></script>
<!--
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
-->
<script defer src="static/js/rel/common.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>
{% block app_scripts %}
<script async class="spiderscript" src="static/js/callsign_search.js"></script>
<script async src="static/js/rel/callsign_search.min.js"></script>
{% endblock app_scripts %}
{% block inline_scripts %}
{% endblock inline_scripts %}
@ -131,7 +128,7 @@
</div>
</div>
<script defer class="spiderscript" src="static/js/cookie_consent.js"></script>
<script defer src="static/js/rel/cookie_consent.min.js"></script>
{% endif %}
</body>
</html>

View File

@ -1,16 +1,17 @@
{% extends "index.html" %}
<head>
{% block title %}
<title>Spot search for a specific Callsign</title>
{% endblock %}
</head>
{% block titles %}
<h1 class="display-4 text-white">{{callsign}}</h1>
<p class="lead text-light">Some statistics about this callsign</p>
{% endblock %}
{% block filters %}
<div class="row mx-auto">
{% block titles %}
<h1 class="display-4 text-white">{{callsign}}</h1>
<p class="lead text-light">Some statistics about this callsign</p>
{% endblock %}
{% block filters %}
<div class="row mx-auto">
{% endblock filters %}
{% block inline_scripts %}
<script defer class="spiderscript" src="static/js/callsign_inline.js"></script>
<script defer src="static/js/rel/callsign_inline.min.js"></script>
{% endblock %}

View File

@ -1,5 +0,0 @@
<input type="checkbox" id="category_{{ category.name }}"
{% if category.default %}checked="checked"{% endif %}
{% if category.is_required %}disabled="disabled"{% endif %}
name="flask_consent_category" value="{{ category.name }}"/>
<label for="category_{{ category.name }}">{{ category.title }}</label>

View File

@ -1,4 +1,5 @@
{% extends "_base.html" %}
<head>
{% block title %}
<title>DX Cluster from IU1BOW: Cookies</title>
@ -7,27 +8,40 @@
{{ super() }}
{% endblock %}
</head>
{% block titles %}
<h1 class="display-4 text-white">COOKIES</h1>
<p class="lead text-light">WEB DX Cluster For HAM Radio</p>
{% endblock %}
{% block filters %}
{% endblock %}
{% block contents %}
<div class="col mr-3 px-2">
<p class="text-justify"><span class="font-weight-bold">Cookies</span> are small text files that can be used by websites to make a user's experience more efficient. This site uses different types of cookies. You can at any time change or withdraw
your consent from the Cookies page on my website. Some cookies are placed by third party services that appear on our pages, for example if you view or listen to any embedded audio or video content. I don't control the setting of these cookies, so
please check the websites of these third parties for more information about their cookies and how they manage them.</p>
<p class="text-justify"><span class="font-weight-bold">Necessary</span> cookies help make a website usable by enabling basic functions like page navigation and access to secure areas of the website. The website cannot function properly without
{% block titles %}
<h1 class="display-4 text-white">COOKIES</h1>
<p class="lead text-light">WEB DX Cluster For HAM Radio</p>
{% endblock %}
{% block filters %}
{% endblock %}
{% block contents %}
<div class="col mr-3 px-2">
<p class="text-justify"><span class="font-weight-bold">Cookies</span> are small text files that can be used by
websites to make a user's experience more efficient. This site uses different types of cookies. You can at any time
change or withdraw
your consent from the Cookies page on my website. Some cookies are placed by third party services that appear on our
pages, for example if you view or listen to any embedded audio or video content. I don't control the setting of
these cookies, so
please check the websites of these third parties for more information about their cookies and how they manage them.
</p>
<p class="text-justify"><span class="font-weight-bold">Necessary</span> cookies help make a website usable by enabling
basic functions like page navigation and access to secure areas of the website. The website cannot function properly
without
these cookies.</p>
<p class="text-justify"><span class="font-weight-bold">Preference</span> cookies enable a website to remember information that changes the way the website behaves or looks, like your preferred language or the region that you are in.</p>
<p class="text-justify"><span class="font-weight-bold">Statistic</span> cookies help website owners to understand how visitors interact with websites by collecting and reporting information anonymously.</p>
<p class="text-justify"><span class="font-weight-bold">Marketing</span> cookies are used to track visitors across websites. The intention is to display content, as well as ads that are relevant and engaging for the individual user and thereby more
<p class="text-justify"><span class="font-weight-bold">Preference</span> cookies enable a website to remember
information that changes the way the website behaves or looks, like your preferred language or the region that you
are in.</p>
<p class="text-justify"><span class="font-weight-bold">Statistic</span> cookies help website owners to understand how
visitors interact with websites by collecting and reporting information anonymously.</p>
<p class="text-justify"><span class="font-weight-bold">Marketing</span> cookies are used to track visitors across
websites. The intention is to display content, as well as ads that are relevant and engaging for the individual user
and thereby more
valuable for publishers and third party advertisers.</p>
<p class="text-justify"><span class="font-weight-bold">Unclassified</span> cookies are cookies that we are in the process of classifying, together with the providers of individual cookies.</p>
</div>
{% endblock %}
{% block app_data %}
{% endblock %}
{% block app_scritps %}
{% endblock %}
<p class="text-justify"><span class="font-weight-bold">Unclassified</span> cookies are cookies that we are in the
process of classifying, together with the providers of individual cookies.</p>
</div>
{% endblock %}
{% block app_data %}
{% endblock %}
{% block app_scritps %}
{% endblock %}

View File

@ -1,4 +1,5 @@
{% extends "_base.html" %}
<head>
{% block title %}
<title>DX Cluster / DX Spot for Hamradio</title>
@ -7,23 +8,24 @@
{{ super() }}
{% endblock %}
</head>
{% block titles %}
<h1 class="display-4 text-white">WEB DX Cluster</h1>
<p class="lead text-light">Spots list</p>
{% endblock %}
{% block filters %}
<div class="row mx-auto justify-content-between align-middle">
{% block titles %}
<h1 class="display-4 text-white">WEB DX Cluster</h1>
<p class="lead text-light">Spots list</p>
{% endblock %}
{% block filters %}
<div class="row mx-auto justify-content-between align-middle">
<div class="mx-auto">
<button class="btn btn-primary btn-sm" type="button" data-toggle="collapse" aria-expanded="false" aria-label="filter" data-bs-toggle="collapse" data-bs-target="#collapseFilters">
<button class="btn btn-primary btn-sm" type="button" data-toggle="collapse" aria-expanded="false"
aria-label="filter" data-bs-toggle="collapse" data-bs-target="#collapseFilters">
<span class="bi-funnel-fill" role="button" aria-label="funnel-fill"></span>
</button>
</div>
</div>
<div class="row mx-auto">
</div>
<div class="row mx-auto">
<!--Sidebar content-->
<div class="col-md-auto ml-2 collapse rounded-sm shadow mb-5 bg-body" id="collapseFilters">
<form method="POST" id="form-filters" enctype="multipart/form-data" >
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<form method="POST" id="form-filters" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<strong>Band</strong>
<select class="form-select overflow-hidden" id="band" size="14" multiple>
{% for dict_item in bands['bands']|reverse %}
@ -65,48 +67,48 @@
<div class="row">
<strong>CQ De zone</strong>
<div class="col">
<select class="form-select" aria-label="cqde" aria-decribedby="cqde" id="cqdeInput" >
<option value="" selected >All</option>
<option value="1" >1 - northwestern zone of NA</option>
<option value="2" >2 - northeastern zone of NA</option>
<option value="3" >3 - western zone of NA</option>
<option value="4" >4 - central zone of NA</option>
<option value="5" >5 - eastern zone of NA</option>
<option value="6" >6 - southern zone of NA</option>
<option value="7" >7 - central american zone</option>
<option value="8" >8 - west indies zone</option>
<option value="9" >9 - northern zone of SA</option>
<option value="10" >10 - western zone of SA</option>
<option value="11" >11 - central zone of SA</option>
<option value="12" >12 - southwest zone of SA</option>
<option value="13" >13 - southeast zone of SA</option>
<option value="14" >14 - western zone of EU</option>
<option value="15" >15 - central EU zone</option>
<option value="16" >16 - eastern zone of EU</option>
<option value="17" >17 - western zone of siberia</option>
<option value="18" >18 - central siberian zone</option>
<option value="19" >19 - eastern siberian zone</option>
<option value="20" >20 - balkan zone</option>
<option value="21" >21 - southwestern zone of AS</option>
<option value="22" >22 - southern zone of AS</option>
<option value="23" >23 - central zone of AS</option>
<option value="24" >24 - eastern zone of AS</option>
<option value="25" >25 - japanese zone</option>
<option value="26" >26 - southeastern zone of AS</option>
<option value="27" >27 - philippine zone</option>
<option value="28" >28 - indonesian zone</option>
<option value="29" >29 - western zone of australia</option>
<option value="30" >30 - eastern zone of australia</option>
<option value="31" >31 - central pacific zone</option>
<option value="32" >32 - new zealand zone</option>
<option value="33" >33 - northwestern zone of AF</option>
<option value="34" >34 - northeastern zone of AF</option>
<option value="35" >35 - central zone of AF</option>
<option value="36" >36 - equatorial zone of AF</option>
<option value="37" >37 - eastern zone of AF</option>
<option value="38" >38 - south AF zone</option>
<option value="39" >39 - madagascar zone</option>
<option value="40" >40 - north atlantic zone</option>
<select class="form-select" aria-label="cqde" aria-decribedby="cqde" id="cqdeInput">
<option value="" selected>All</option>
<option value="1">1 - northwestern zone of NA</option>
<option value="2">2 - northeastern zone of NA</option>
<option value="3">3 - western zone of NA</option>
<option value="4">4 - central zone of NA</option>
<option value="5">5 - eastern zone of NA</option>
<option value="6">6 - southern zone of NA</option>
<option value="7">7 - central american zone</option>
<option value="8">8 - west indies zone</option>
<option value="9">9 - northern zone of SA</option>
<option value="10">10 - western zone of SA</option>
<option value="11">11 - central zone of SA</option>
<option value="12">12 - southwest zone of SA</option>
<option value="13">13 - southeast zone of SA</option>
<option value="14">14 - western zone of EU</option>
<option value="15">15 - central EU zone</option>
<option value="16">16 - eastern zone of EU</option>
<option value="17">17 - western zone of siberia</option>
<option value="18">18 - central siberian zone</option>
<option value="19">19 - eastern siberian zone</option>
<option value="20">20 - balkan zone</option>
<option value="21">21 - southwestern zone of AS</option>
<option value="22">22 - southern zone of AS</option>
<option value="23">23 - central zone of AS</option>
<option value="24">24 - eastern zone of AS</option>
<option value="25">25 - japanese zone</option>
<option value="26">26 - southeastern zone of AS</option>
<option value="27">27 - philippine zone</option>
<option value="28">28 - indonesian zone</option>
<option value="29">29 - western zone of australia</option>
<option value="30">30 - eastern zone of australia</option>
<option value="31">31 - central pacific zone</option>
<option value="32">32 - new zealand zone</option>
<option value="33">33 - northwestern zone of AF</option>
<option value="34">34 - northeastern zone of AF</option>
<option value="35">35 - central zone of AF</option>
<option value="36">36 - equatorial zone of AF</option>
<option value="37">37 - eastern zone of AF</option>
<option value="38">38 - south AF zone</option>
<option value="39">39 - madagascar zone</option>
<option value="40">40 - north atlantic zone</option>
</select>
</div>
</div>
@ -114,55 +116,57 @@
<div class="row">
<strong>CQ Dx zone</strong>
<div class="col">
<select class="form-select" aria-label="cqdx" aria-decribedby="cqdx" id="cqdxInput" >
<select class="form-select" aria-label="cqdx" aria-decribedby="cqdx" id="cqdxInput">
<option value="" selected>All</option>
<option value="1" >1 - northwestern zone of NA</option>
<option value="2" >2 - northeastern zone of NA</option>
<option value="3" >3 - western zone of NA</option>
<option value="4" >4 - central zone of NA</option>
<option value="5" >5 - eastern zone of NA</option>
<option value="6" >6 - southern zone of NA</option>
<option value="7" >7 - central american zone</option>
<option value="8" >8 - west indies zone</option>
<option value="9" >9 - northern zone of SA</option>
<option value="10" >10 - western zone of SA</option>
<option value="11" >11 - central zone of SA</option>
<option value="12" >12 - southwest zone of SA</option>
<option value="13" >13 - southeast zone of SA</option>
<option value="14" >14 - western zone of EU</option>
<option value="15" >15 - central EU zone</option>
<option value="16" >16 - eastern zone of EU</option>
<option value="17" >17 - western zone of siberia</option>
<option value="18" >18 - central siberian zone</option>
<option value="19" >19 - eastern siberian zone</option>
<option value="20" >20 - balkan zone</option>
<option value="21" >21 - southwestern zone of AS</option>
<option value="22" >22 - southern zone of AS</option>
<option value="23" >23 - central zone of AS</option>
<option value="24" >24 - eastern zone of AS</option>
<option value="25" >25 - japanese zone</option>
<option value="26" >26 - southeastern zone of AS</option>
<option value="27" >27 - philippine zone</option>
<option value="28" >28 - indonesian zone</option>
<option value="29" >29 - western zone of australia</option>
<option value="30" >30 - eastern zone of australia</option>
<option value="31" >31 - central pacific zone</option>
<option value="32" >32 - new zealand zone</option>
<option value="33" >33 - northwestern zone of AF</option>
<option value="34" >34 - northeastern zone of AF</option>
<option value="35" >35 - central zone of AF</option>
<option value="36" >36 - equatorial zone of AF</option>
<option value="37" >37 - eastern zone of AF</option>
<option value="38" >38 - south AF zone</option>
<option value="39" >39 - madagascar zone</option>
<option value="40" >40 - north atlantic zone</option>
<option value="1">1 - northwestern zone of NA</option>
<option value="2">2 - northeastern zone of NA</option>
<option value="3">3 - western zone of NA</option>
<option value="4">4 - central zone of NA</option>
<option value="5">5 - eastern zone of NA</option>
<option value="6">6 - southern zone of NA</option>
<option value="7">7 - central american zone</option>
<option value="8">8 - west indies zone</option>
<option value="9">9 - northern zone of SA</option>
<option value="10">10 - western zone of SA</option>
<option value="11">11 - central zone of SA</option>
<option value="12">12 - southwest zone of SA</option>
<option value="13">13 - southeast zone of SA</option>
<option value="14">14 - western zone of EU</option>
<option value="15">15 - central EU zone</option>
<option value="16">16 - eastern zone of EU</option>
<option value="17">17 - western zone of siberia</option>
<option value="18">18 - central siberian zone</option>
<option value="19">19 - eastern siberian zone</option>
<option value="20">20 - balkan zone</option>
<option value="21">21 - southwestern zone of AS</option>
<option value="22">22 - southern zone of AS</option>
<option value="23">23 - central zone of AS</option>
<option value="24">24 - eastern zone of AS</option>
<option value="25">25 - japanese zone</option>
<option value="26">26 - southeastern zone of AS</option>
<option value="27">27 - philippine zone</option>
<option value="28">28 - indonesian zone</option>
<option value="29">29 - western zone of australia</option>
<option value="30">30 - eastern zone of australia</option>
<option value="31">31 - central pacific zone</option>
<option value="32">32 - new zealand zone</option>
<option value="33">33 - northwestern zone of AF</option>
<option value="34">34 - northeastern zone of AF</option>
<option value="35">35 - central zone of AF</option>
<option value="36">36 - equatorial zone of AF</option>
<option value="37">37 - eastern zone of AF</option>
<option value="38">38 - south AF zone</option>
<option value="39">39 - madagascar zone</option>
<option value="40">40 - north atlantic zone</option>
</select>
</div>
</div>
{% endif %}
<p></p>
<div class="mx-auto">
<button type="submit" class="btn btn-primary btn-block w-100" aria-pressed="true" data-toggle="collapse" data-target="#collapseFilters" aria-expanded="false" aria-controls="collapseFilters" aria-label="filter">Search</button>
<button type="submit" class="btn btn-primary btn-block w-100" aria-pressed="true" data-toggle="collapse"
data-target="#collapseFilters" aria-expanded="false" aria-controls="collapseFilters"
aria-label="filter">Search</button>
</div>
</form>
</div>
@ -187,24 +191,21 @@
</tbody>
</table>
</div>
</div>
{% endblock contents %}
{% block app_data %}
{{ super() }}
var timer_interval_json = {{timer_interval}};
var my_adxo_events_json='{{ adxo_events|tojson|safe }}';
var continents_cq={{continents["continents"]|tojson|safe}};
var band_frequencies={{bands["bands"]|tojson|safe}};
{% endblock app_data %}
{% block app_scripts %}
{{ super() }}
<script defer class="spiderscript" src="static/js/table.js"></script>
{% endblock %}
{% block inline_scripts %}
<script defer class="spiderscript" src="static/js/index_inline.js"></script>
{% endblock %}
</div>
{% endblock contents %}
{% block app_data %}
{{ super() }}
var timer_interval_json = {{timer_interval}};
var my_adxo_events_json='{{ adxo_events|tojson|safe }}';
var continents_cq={{continents["continents"]|tojson|safe}};
var band_frequencies={{bands["bands"]|tojson|safe}};
{% endblock app_data %}
{% block app_scripts %}
{{ super() }}
<script defer src="static/js/rel/table.min.js"></script>
{% endblock %}
{% block inline_scripts %}
<script defer src="static/js/rel/index_inline.min.js"></script>
{% endblock %}

View File

@ -1,4 +1,5 @@
{% extends "_base.html" %}
<head>
{% block title %}
<title>DX Cluster from IU1BOW: OFFLINE</title>
@ -8,33 +9,34 @@
{{ super() }}
{% endblock %}
</head>
{% block menu %}
{{ super() }}
{% block callsign %}
{% endblock callsign %}
{% endblock menu %}
{% block titles %}
<h1 class="display-4 text-white">WEB DX Cluster</h1>
<p class="lead text-light">Spots list</p>
{% endblock %}
{% block filters %}
{% endblock %}
{% block contents %}
{% block menu %}
{{ super() }}
{% block callsign %}
{% endblock callsign %}
{% endblock menu %}
{% block titles %}
<h1 class="display-4 text-white">WEB DX Cluster</h1>
<p class="lead text-light">Spots list</p>
{% endblock %}
{% block filters %}
{% endblock %}
{% block contents %}
<!-- <div class="container">
<div class="row">
<div class="col align-self-center"> -->
<div class="jumbotron alert alert-warning" role="alert">
<div class="jumbotron alert alert-warning" role="alert">
<h2 class="display-4">No internet connection</h2>
<p class="lead">The features in this area require Internet connectivity. Please connect your computer to the Internet</p>
<p class="lead">The features in this area require Internet connectivity. Please connect your computer to the
Internet</p>
<p class="lead">
<a class="btn btn-primary btn-lg" href="/" role="button">Try again</a>
</p>
<!-- </div>
<!-- </div>
</div>
</div> -->
</div>
{% endblock %}
{% block app_data %}
{% endblock %}
{% block app_scritps %}
{% endblock %}
{% endblock %}
{% block app_data %}
{% endblock %}
{% block app_scritps %}
{% endblock %}

View File

@ -1,4 +1,5 @@
{% extends "_base.html" %}
<head>
{% block title %}
<title>Some charts end stats from the dx clustes node</title>
@ -6,28 +7,30 @@
{% block head %}
{{ super() }}
<meta http-equiv="refresh" content="300">
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.1/echarts.min.js" integrity="sha512-OTbGFYPLe3jhy4bUwbB8nls0TFgz10kn0TLkmyA+l3FyivDs31zsXCjOis7YGDtE2Jsy0+fzW+3/OVoPVujPmQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.1/echarts.min.js"
integrity="sha512-OTbGFYPLe3jhy4bUwbB8nls0TFgz10kn0TLkmyA+l3FyivDs31zsXCjOis7YGDtE2Jsy0+fzW+3/OVoPVujPmQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
{% endblock %}
</head>
{% block titles %}
<h1 class="display-4 text-white">PLOTS & STATS</h1>
<p class="lead text-light">Some statistics about this node</p>
{% endblock %}
{% block filters %}
{% endblock %}
{% block contents %}
{% block titles %}
<h1 class="display-4 text-white">PLOTS & STATS</h1>
<p class="lead text-light">Some statistics about this node</p>
{% endblock %}
{% block filters %}
{% endblock %}
{% block contents %}
<div class="d-flex flex-wrap" id="dashboard">
<div class="d-flex flex-wrap" id="dashboard">
<div class="shadow-lg bg-body mb-5 rounded" id="form-band_activity">
<div class="d-flex flex-column">
<form method="POST" id="form-continents" enctype="multipart/form-data" >
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<form method="POST" id="form-continents" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="container">
Your continent is:
<select class="form-select flex-shrink" aria-label="continent" id="continentInput">
{% for dict_item in continents['continents']%}
<option value="{{dict_item['id']}}" >{{dict_item["description"]}}</option>
<option value="{{dict_item['id']}}">{{dict_item["description"]}}</option>
{% endfor %}
</select>
</div>
@ -42,16 +45,14 @@
<div class="shadow-lg mb-5 bg-body rounded" id="chart-hour_band"></div>
<a class="shadow-lg mb-5 bg-body rounded" href="https://sidc.be/silso/" target="_blank" rel="noopener noreferrer">
<img src="https://sidc.be/silso/IMAGES/GRAPHICS/prediSC.png"class="img-fluid" id="silo-propagation-img" alt="propagation trend">
<img src="https://sidc.be/silso/IMAGES/GRAPHICS/prediSC.png" class="img-fluid" id="silo-propagation-img"
alt="propagation trend">
</a>
<div class="shadow-lg mb-5 bg-body rounded" id="chart-dx_spots_x_month"></div>
<div class="shadow-lg mb-5 bg-body rounded" id="chart-dx_spots_trend"></div>
<div class="container-fluid">
<div class="shadow-lg mb-5 bg-body rounded">
<strong>Physically connected callsigns to {{ mycallsign }}</strong>
@ -80,21 +81,21 @@
</table>
</div>
</div>
</div>
{% endblock contents %}
</div>
{% endblock contents %}
{% block app_data %}
{{ super() }}
var continents_cq={{continents["continents"]|tojson|safe}};
var band_frequencies={{bands["bands"]|tojson|safe}};
{% endblock app_data %}
{% block app_data %}
{{ super() }}
var continents_cq={{continents["continents"]|tojson|safe}};
var band_frequencies={{bands["bands"]|tojson|safe}};
{% endblock app_data %}
{% block app_scripts %}
{{ super() }}
<script defer class="spiderscript" src="static/js/plot_band_activity.js"></script>
<script defer class="spiderscript" src="static/js/plot_world_dx_spots_live.js"></script>
<script defer class="spiderscript" src="static/js/plot_hour_band.js"></script>
<script defer class="spiderscript" src="static/js/plot_dx_spots_trend.js"></script>
<script defer class="spiderscript" src="static/js/plot_dx_spots_per_month.js"></script>
{% block app_scripts %}
{{ super() }}
<script defer src="static/js/rel/plot_band_activity.min.js"></script>
<script defer src="static/js/rel/plot_world_dx_spots_live.min.js"></script>
<script defer src="static/js/rel/plot_hour_band.min.js"></script>
<script defer src="static/js/rel/plot_dx_spots_trend.min.js"></script>
<script defer src="static/js/rel/plot_dx_spots_per_month.min.js"></script>
{% endblock app_scripts %}
{% endblock app_scripts %}

View File

@ -1,4 +1,5 @@
{% extends "_base.html" %}
<head>
{% block title %}
<title>DX Cluster from IU1BOW: Privacy</title>
@ -7,69 +8,113 @@
{{ super() }}
{% endblock %}
</head>
{% block titles %}
<h1 class="display-4 text-white">PRIVACY</h1>
<p class="lead text-light">WEB DX Cluster For HAM Radio</p>
{% endblock %}
{% block filters %}
{% endblock %}
{% block contents %}
<div class="col mr-3 px-2">
<h1>Privacy Policy for this web site</h1>
<p>At this web site, one of our main priorities is the privacy of our visitors. This Privacy Policy document contains types of information that is collected and recorded by this web site and how we use it.</p>
<p>If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact us.</p>
<p>This Privacy Policy applies only to our online activities and is valid for visitors to our website with regards to the information that they shared and/or collect in this web site. This policy is not applicable to any information collected offline or via channels other than this website. Our Privacy Policy was created with the help of the <a href="https://www.privacypolicygenerator.info">Privacy Policy Generator</a> and the <a href="https://www.generateprivacypolicy.com/">Free Privacy Policy Generator</a>.</p>
<h2>Consent</h2>
<p>By using our website, you hereby consent to our Privacy Policy and agree to its terms. For our Terms and Conditions, please visit the <a href="https://www.privacypolicyonline.com/terms-conditions-generator/">Terms & Conditions Generator</a>.</p>
<h2>Information we collect</h2>
<p>The personal information that you are asked to provide, and the reasons why you are asked to provide it, will be made clear to you at the point we ask you to provide your personal information.</p>
<p>If you contact us directly, we may receive additional information about you such as your name, email address, phone number, the contents of the message and/or attachments you may send us, and any other information you may choose to provide.</p>
<p>When you register for an Account, we may ask for your contact information, including items such as name, company name, address, email address, and telephone number.</p>
<h2>How we use your information</h2>
<p>We use the information we collect in various ways, including to:</p>
<ul>
<li>Provide, operate, and maintain our webste</li>
<li>Improve, personalize, and expand our webste</li>
<li>Understand and analyze how you use our webste</li>
<li>Develop new products, services, features, and functionality</li>
<li>Communicate with you, either directly or through one of our partners, including for customer service, to provide you with updates and other information relating to the webste, and for marketing and promotional purposes</li>
<li>Send you emails</li>
<li>Find and prevent fraud</li>
</ul>
<h2>Log Files</h2>
<p>this web site follows a standard procedure of using log files. These files log visitors when they visit websites. All hosting companies do this and a part of hosting services' analytics. The information collected by log files include internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp, referring/exit pages, and possibly the number of clicks. These are not linked to any information that is personally identifiable. The purpose of the information is for analyzing trends, administering the site, tracking users' movement on the website, and gathering demographic information.</p>
<h2>Cookies and Web Beacons</h2>
<p>Like any other website, this web site uses 'cookies'. These cookies are used to store information including visitors' preferences, and the pages on the website that the visitor accessed or visited. The information is used to optimize the users' experience by customizing our web page content based on visitors' browser type and/or other information.</p>
<p>For more general information on cookies, please read <a href="https://www.privacypolicies.com/blog/cookies/">"What Are Cookies"</a>.</p>
<h2>Advertising Partners Privacy Policies</h2>
<P>You may consult this list to find the Privacy Policy for each of the advertising partners of this web site.</p>
<p>Third-party ad servers or ad networks uses technologies like cookies, JavaScript, or Web Beacons that are used in their respective advertisements and links that appear on this web site, which are sent directly to users' browser. They automatically receive your IP address when this occurs. These technologies are used to measure the effectiveness of their advertising campaigns and/or to personalize the advertising content that you see on websites that you visit.</p>
<p>Note that this web site has no access to or control over these cookies that are used by third-party advertisers.</p>
<h2>Third Party Privacy Policies</h2>
<p>this web site's Privacy Policy does not apply to other advertisers or websites. Thus, we are advising you to consult the respective Privacy Policies of these third-party ad servers for more detailed information. It may include their practices and instructions about how to opt-out of certain options. </p>
<p>You can choose to disable cookies through your individual browser options. To know more detailed information about cookie management with specific web browsers, it can be found at the browsers' respective websites.</p>
<h2>CCPA Privacy Rights (Do Not Sell My Personal Information)</h2>
<p>Under the CCPA, among other rights, California consumers have the right to:</p>
<p>Request that a business that collects a consumer's personal data disclose the categories and specific pieces of personal data that a business has collected about consumers.</p>
<p>Request that a business delete any personal data about the consumer that a business has collected.</p>
<p>Request that a business that sells a consumer's personal data, not sell the consumer's personal data.</p>
<p>If you make a request, we have one month to respond to you. If you would like to exercise any of these rights, please contact us.</p>
<h2>GDPR Data Protection Rights</h2>
<p>We would like to make sure you are fully aware of all of your data protection rights. Every user is entitled to the following:</p>
<p>The right to access You have the right to request copies of your personal data. We may charge you a small fee for this service.</p>
<p>The right to rectification You have the right to request that we correct any information you believe is inaccurate. You also have the right to request that we complete the information you believe is incomplete.</p>
<p>The right to erasure You have the right to request that we erase your personal data, under certain conditions.</p>
<p>The right to restrict processing You have the right to request that we restrict the processing of your personal data, under certain conditions.</p>
<p>The right to object to processing You have the right to object to our processing of your personal data, under certain conditions.</p>
<p>The right to data portability You have the right to request that we transfer the data that we have collected to another organization, or directly to you, under certain conditions.</p>
<p>If you make a request, we have one month to respond to you. If you would like to exercise any of these rights, please contact us.</p>
<h2>Children's Information</h2>
<p>Another part of our priority is adding protection for children while using the internet. We encourage parents and guardians to observe, participate in, and/or monitor and guide their online activity.</p>
<p>this web site does not knowingly collect any Personal Identifiable Information from children under the age of 13. If you think that your child provided this kind of information on our website, we strongly encourage you to contact us immediately and we will do our best efforts to promptly remove such information from our records.</p>
{% block titles %}
<h1 class="display-4 text-white">PRIVACY</h1>
<p class="lead text-light">WEB DX Cluster For HAM Radio</p>
{% endblock %}
{% block filters %}
{% endblock %}
{% block contents %}
<div class="col mr-3 px-2">
<h1>Privacy Policy for this web site</h1>
<p>At this web site, one of our main priorities is the privacy of our visitors. This Privacy Policy document
contains types of information that is collected and recorded by this web site and how we use it.</p>
<p>If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact
us.</p>
<p>This Privacy Policy applies only to our online activities and is valid for visitors to our website with regards
to the information that they shared and/or collect in this web site. This policy is not applicable to any
information collected offline or via channels other than this website. Our Privacy Policy was created with the
help of the <a href="https://www.privacypolicygenerator.info">Privacy Policy Generator</a> and the <a
href="https://www.generateprivacypolicy.com/">Free Privacy Policy Generator</a>.</p>
<h2>Consent</h2>
<p>By using our website, you hereby consent to our Privacy Policy and agree to its terms. For our Terms and
Conditions, please visit the <a href="https://www.privacypolicyonline.com/terms-conditions-generator/">Terms &
Conditions Generator</a>.</p>
<h2>Information we collect</h2>
<p>The personal information that you are asked to provide, and the reasons why you are asked to provide it, will be
made clear to you at the point we ask you to provide your personal information.</p>
<p>If you contact us directly, we may receive additional information about you such as your name, email address,
phone number, the contents of the message and/or attachments you may send us, and any other information you may
choose to provide.</p>
<p>When you register for an Account, we may ask for your contact information, including items such as name, company
name, address, email address, and telephone number.</p>
<h2>How we use your information</h2>
<p>We use the information we collect in various ways, including to:</p>
<ul>
<li>Provide, operate, and maintain our webste</li>
<li>Improve, personalize, and expand our webste</li>
<li>Understand and analyze how you use our webste</li>
<li>Develop new products, services, features, and functionality</li>
<li>Communicate with you, either directly or through one of our partners, including for customer service, to
provide you with updates and other information relating to the webste, and for marketing and promotional
purposes</li>
<li>Send you emails</li>
<li>Find and prevent fraud</li>
</ul>
<h2>Log Files</h2>
<p>this web site follows a standard procedure of using log files. These files log visitors when they visit websites.
All hosting companies do this and a part of hosting services' analytics. The information collected by log files
include internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp,
referring/exit pages, and possibly the number of clicks. These are not linked to any information that is
personally identifiable. The purpose of the information is for analyzing trends, administering the site,
tracking users' movement on the website, and gathering demographic information.</p>
<h2>Cookies and Web Beacons</h2>
<p>Like any other website, this web site uses 'cookies'. These cookies are used to store information including
visitors' preferences, and the pages on the website that the visitor accessed or visited. The information is
used to optimize the users' experience by customizing our web page content based on visitors' browser type
and/or other information.</p>
<p>For more general information on cookies, please read <a
href="https://www.privacypolicies.com/blog/cookies/">"What Are Cookies"</a>.</p>
<h2>Advertising Partners Privacy Policies</h2>
<P>You may consult this list to find the Privacy Policy for each of the advertising partners of this web site.</p>
<p>Third-party ad servers or ad networks uses technologies like cookies, JavaScript, or Web Beacons that are used in
their respective advertisements and links that appear on this web site, which are sent directly to users'
browser. They automatically receive your IP address when this occurs. These technologies are used to measure the
effectiveness of their advertising campaigns and/or to personalize the advertising content that you see on
websites that you visit.</p>
<p>Note that this web site has no access to or control over these cookies that are used by third-party advertisers.
</p>
<h2>Third Party Privacy Policies</h2>
<p>this web site's Privacy Policy does not apply to other advertisers or websites. Thus, we are advising you to
consult the respective Privacy Policies of these third-party ad servers for more detailed information. It may
include their practices and instructions about how to opt-out of certain options. </p>
<p>You can choose to disable cookies through your individual browser options. To know more detailed information
about cookie management with specific web browsers, it can be found at the browsers' respective websites.</p>
<h2>CCPA Privacy Rights (Do Not Sell My Personal Information)</h2>
<p>Under the CCPA, among other rights, California consumers have the right to:</p>
<p>Request that a business that collects a consumer's personal data disclose the categories and specific pieces of
personal data that a business has collected about consumers.</p>
<p>Request that a business delete any personal data about the consumer that a business has collected.</p>
<p>Request that a business that sells a consumer's personal data, not sell the consumer's personal data.</p>
<p>If you make a request, we have one month to respond to you. If you would like to exercise any of these rights,
please contact us.</p>
<h2>GDPR Data Protection Rights</h2>
<p>We would like to make sure you are fully aware of all of your data protection rights. Every user is entitled to
the following:</p>
<p>The right to access You have the right to request copies of your personal data. We may charge you a small fee
for this service.</p>
<p>The right to rectification You have the right to request that we correct any information you believe is
inaccurate. You also have the right to request that we complete the information you believe is incomplete.</p>
<p>The right to erasure You have the right to request that we erase your personal data, under certain conditions.
</p>
<p>The right to restrict processing You have the right to request that we restrict the processing of your personal
data, under certain conditions.</p>
<p>The right to object to processing You have the right to object to our processing of your personal data, under
certain conditions.</p>
<p>The right to data portability You have the right to request that we transfer the data that we have collected to
another organization, or directly to you, under certain conditions.</p>
<p>If you make a request, we have one month to respond to you. If you would like to exercise any of these rights,
please contact us.</p>
<h2>Children's Information</h2>
<p>Another part of our priority is adding protection for children while using the internet. We encourage parents and
guardians to observe, participate in, and/or monitor and guide their online activity.</p>
<p>this web site does not knowingly collect any Personal Identifiable Information from children under the age of 13.
If you think that your child provided this kind of information on our website, we strongly encourage you to
contact us immediately and we will do our best efforts to promptly remove such information from our records.</p>
</div>
{% endblock %}
{% block app_data %}
{% endblock %}
{% block app_scritps %}
{% endblock %}
</div>
{% endblock %}
{% block app_data %}
{% endblock %}
{% block app_scritps %}
{% endblock %}

21
test.sh
View File

@ -1,8 +1,21 @@
if [ "$1" == "-b" ]; then
if [ $# -gt 0 ]
then
cd scripts || exit
./build.sh
if ! ./build.sh ${1}
then
cd ..
echo "terminated"
exit 1
fi
cd ..
fi
#python3 webapp.py
flask --app webapp.py run
if [ "$1" == "-d" ]; then
flask --app webapp.py --debug run
else
flask --app webapp.py run
fi

434
webapp.py
View File

@ -1,4 +1,4 @@
__author__ = 'IU1BOW - Corrado'
__author__ = "IU1BOW - Corrado"
import os
import flask
from flask import request, render_template, jsonify
@ -23,140 +23,162 @@ logger = logging.getLogger(__name__)
logger.info("Start")
app = flask.Flask(__name__)
app.config["DEBUG"] = False
app.config['SECRET_KEY'] = 'secret!'
app.config["SECRET_KEY"] = "secret!"
app.config.update(
SESSION_COOKIE_SECURE=True, #If you use http change it to False
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Strict',
SESSION_COOKIE_SAMESITE="Strict",
)
csrf = CSRFProtect(app)
#minify(app=app, html=True, js=True,cssless=False)
minify(app=app, html=False, js=False,cssless=False)
logger.debug(app.config)
#load config file
with open('cfg/config.json') as json_data_file:
if app.config["DEBUG"]:
minify(app=app, html=False, js=False, cssless=False)
else:
minify(app=app, html=True, js=True, cssless=False)
# load config file
with open("cfg/config.json") as json_data_file:
cfg = json.load(json_data_file)
logging.debug("CFG:")
logging.debug(cfg)
#load bands file
with open('cfg/bands.json') as json_bands:
# load bands file
with open("cfg/bands.json") as json_bands:
band_frequencies = json.load(json_bands)
#load mode file
with open('cfg/modes.json') as json_modes:
# load mode file
with open("cfg/modes.json") as json_modes:
modes_frequencies = json.load(json_modes)
#load continents-cq file
with open('cfg/continents.json') as json_continents:
# load continents-cq file
with open("cfg/continents.json") as json_continents:
continents_cq = json.load(json_continents)
#read and set default for enabling cq filter
if cfg.get('enable_cq_filter'):
enable_cq_filter=cfg['enable_cq_filter'].upper()
# read and set default for enabling cq filter
if cfg.get("enable_cq_filter"):
enable_cq_filter = cfg["enable_cq_filter"].upper()
else:
enable_cq_filter='N'
enable_cq_filter = "N"
#define country table for search info on callsigns
pfxt=prefix_table()
# define country table for search info on callsigns
pfxt = prefix_table()
#create object query manager
qm=query_manager()
# create object query manager
qm = query_manager()
#find id in json : ie frequency / continent
# find id in json : ie frequency / continent
def find_id_json(json_object, name):
return [obj for obj in json_object if obj['id']==name][0]
return [obj for obj in json_object if obj["id"] == name][0]
def query_build_callsign(callsign):
query_string=''
if len(callsign)<=14:
query_string="(SELECT rowid, spotter AS de, freq, spotcall AS dx, comment AS comm, time, spotdxcc from dxcluster.spot WHERE spotter='"+callsign+"'"
query_string+=" ORDER BY rowid desc limit 10)"
query_string+=" UNION "
query_string+="(SELECT rowid, spotter AS de, freq, spotcall AS dx, comment AS comm, time, spotdxcc from dxcluster.spot WHERE spotcall='"+callsign+"'"
query_string+=" ORDER BY rowid desc limit 10);"
query_string = ""
if len(callsign) <= 14:
query_string = (
"(SELECT rowid, spotter AS de, freq, spotcall AS dx, comment AS comm, time, spotdxcc from dxcluster.spot WHERE spotter='"
+ callsign
+ "'"
)
query_string += " ORDER BY rowid desc limit 10)"
query_string += " UNION "
query_string += (
"(SELECT rowid, spotter AS de, freq, spotcall AS dx, comment AS comm, time, spotdxcc from dxcluster.spot WHERE spotcall='"
+ callsign
+ "'"
)
query_string += " ORDER BY rowid desc limit 10);"
else:
logging.warning('callsign too long')
logging.warning("callsign too long")
return query_string
def query_build():
try:
#get url parameters
last_rowid=request.args.get('lr') #Last rowid fetched by front end
band=(request.args.getlist('b')) #band filter
dere=(request.args.getlist('e')) #DE continent filter
dxre=(request.args.getlist('x')) #Dx continent filter
mode=(request.args.getlist('m')) #mode filter
decq=(request.args.getlist('qe')) #DE cq zone filter
dxcq=(request.args.getlist('qx')) #DX cq zone filter
# get url parameters
last_rowid = request.args.get("lr") # Last rowid fetched by front end
band = request.args.getlist("b") # band filter
dere = request.args.getlist("e") # DE continent filter
dxre = request.args.getlist("x") # Dx continent filter
mode = request.args.getlist("m") # mode filter
decq = request.args.getlist("qe") # DE cq zone filter
dxcq = request.args.getlist("qx") # DX cq zone filter
query_string = ""
query_string=''
#construct band query decoding frequencies with json file
band_qry_string = ' AND (('
# construct band query decoding frequencies with json file
band_qry_string = " AND (("
for i, item_band in enumerate(band):
freq=find_id_json(band_frequencies["bands"],item_band)
freq = find_id_json(band_frequencies["bands"], item_band)
if i > 0:
band_qry_string += ') OR ('
band_qry_string += ") OR ("
band_qry_string += 'freq BETWEEN ' + str(freq["min"]) + ' AND ' + str(freq["max"])
band_qry_string += (
"freq BETWEEN " + str(freq["min"]) + " AND " + str(freq["max"])
)
band_qry_string += '))'
band_qry_string += "))"
#construct mode query
mode_qry_string = ' AND (('
for i,item_mode in enumerate(mode):
single_mode=find_id_json(modes_frequencies["modes"],item_mode)
# construct mode query
mode_qry_string = " AND (("
for i, item_mode in enumerate(mode):
single_mode = find_id_json(modes_frequencies["modes"], item_mode)
if i > 0:
mode_qry_string +=') OR ('
mode_qry_string += ") OR ("
for j in range(len(single_mode["freq"])):
if j > 0:
mode_qry_string +=') OR ('
mode_qry_string += 'freq BETWEEN ' +str(single_mode["freq"][j]["min"]) + ' AND ' + str(single_mode["freq"][j]["max"])
mode_qry_string += ") OR ("
mode_qry_string += (
"freq BETWEEN "
+ str(single_mode["freq"][j]["min"])
+ " AND "
+ str(single_mode["freq"][j]["max"])
)
mode_qry_string += '))'
mode_qry_string += "))"
#construct DE continent region query
dere_qry_string = ' AND spottercq IN ('
# construct DE continent region query
dere_qry_string = " AND spottercq IN ("
for i, item_dere in enumerate(dere):
continent=find_id_json(continents_cq["continents"],item_dere)
continent = find_id_json(continents_cq["continents"], item_dere)
if i > 0:
dere_qry_string +=','
dere_qry_string += ","
dere_qry_string += str(continent["cq"])
dere_qry_string +=')'
dere_qry_string += ")"
#construct DX continent region query
dxre_qry_string = ' AND spotcq IN ('
# construct DX continent region query
dxre_qry_string = " AND spotcq IN ("
for i, item_dxre in enumerate(dxre):
continent=find_id_json(continents_cq["continents"],item_dxre)
continent = find_id_json(continents_cq["continents"], item_dxre)
if i > 0:
dxre_qry_string +=','
dxre_qry_string += ","
dxre_qry_string += str(continent["cq"])
dxre_qry_string +=')'
dxre_qry_string += ")"
if enable_cq_filter == 'Y':
#construct de cq query
decq_qry_string = ''
if len(decq)==1:
if enable_cq_filter == "Y":
# construct de cq query
decq_qry_string = ""
if len(decq) == 1:
if decq[0].isnumeric():
decq_qry_string = ' AND spottercq =' + decq[0]
#construct dx cq query
dxcq_qry_string = ''
if len(dxcq)==1:
decq_qry_string = " AND spottercq =" + decq[0]
# construct dx cq query
dxcq_qry_string = ""
if len(dxcq) == 1:
if dxcq[0].isnumeric():
dxcq_qry_string = ' AND spotcq =' + dxcq[0]
dxcq_qry_string = " AND spotcq =" + dxcq[0]
if last_rowid is None:
last_rowid = "0"
if not last_rowid.isnumeric():
last_rowid = 0
query_string="SELECT rowid, spotter AS de, freq, spotcall AS dx, comment AS comm, time, spotdxcc from dxcluster.spot WHERE rowid > "+last_rowid
query_string = (
"SELECT rowid, spotter AS de, freq, spotcall AS dx, comment AS comm, time, spotdxcc from dxcluster.spot WHERE rowid > "
+ last_rowid
)
if len(band) > 0:
query_string += band_qry_string
@ -170,7 +192,7 @@ def query_build():
if len(dxre) > 0:
query_string += dxre_qry_string
if enable_cq_filter == 'Y':
if enable_cq_filter == "Y":
if len(decq_qry_string) > 0:
query_string += decq_qry_string
@ -181,42 +203,43 @@ def query_build():
except Exception as e:
logger.error(e)
query_string = ''
query_string = ""
return query_string
#the main query to show spots
#it gets url parameter in order to apply the build the right query
#and apply the filter required. It returns a json with the spots
# the main query to show spots
# it gets url parameter in order to apply the build the right query
# and apply the filter required. It returns a json with the spots
def spotquery():
try:
callsign=request.args.get('c') #search specific callsign
callsign = request.args.get("c") # search specific callsign
if callsign:
query_string=query_build_callsign(callsign)
query_string = query_build_callsign(callsign)
else:
query_string=query_build()
query_string = query_build()
qm.qry(query_string)
data=qm.get_data()
row_headers=qm.get_headers()
data = qm.get_data()
row_headers = qm.get_headers()
logger.debug("query done")
logger.debug (data)
logger.debug(data)
if data is None or len(data)==0:
if data is None or len(data) == 0:
logger.warning("no data found")
payload=[]
payload = []
for result in data:
# create dictionary from recorset
main_result=dict(zip(row_headers,result))
main_result = dict(zip(row_headers, result))
# find the country in prefix table
search_prefix=pfxt.find(main_result["dx"])
search_prefix = pfxt.find(main_result["dx"])
# merge recordset and contry prefix
main_result["country"]=search_prefix["country"]
main_result["iso"]=search_prefix["iso"]
main_result["country"] = search_prefix["country"]
main_result["iso"] = search_prefix["iso"]
payload.append({**main_result})
@ -224,151 +247,228 @@ def spotquery():
except Exception as e:
logger.error(e)
#find adxo events
adxo_events=None
# find adxo events
adxo_events = None
def get_adxo():
global adxo_events
adxo_events=get_adxo_events()
threading.Timer(12*3600,get_adxo).start()
adxo_events = get_adxo_events()
threading.Timer(12 * 3600, get_adxo).start()
get_adxo()
#create data provider for charts
heatmap_cbp=ContinentsBandsProvider(logger,qm,continents_cq,band_frequencies)
bar_graph_spm=SpotsPerMounthProvider(logger,qm)
line_graph_st=SpotsTrend(logger,qm)
bubble_graph_hb=HourBand(logger,qm,band_frequencies)
geo_graph_wdsl=WorldDxSpotsLive(logger,qm,pfxt)
# create data provider for charts
heatmap_cbp = ContinentsBandsProvider(logger, qm, continents_cq, band_frequencies)
bar_graph_spm = SpotsPerMounthProvider(logger, qm)
line_graph_st = SpotsTrend(logger, qm)
bubble_graph_hb = HourBand(logger, qm, band_frequencies)
geo_graph_wdsl = WorldDxSpotsLive(logger, qm, pfxt)
#ROUTINGS
@app.route('/spotlist', methods=['GET'])
# ROUTINGS
@app.route("/spotlist", methods=["GET"])
def spotlist():
response=flask.Response(json.dumps(spotquery()))
response = flask.Response(json.dumps(spotquery()))
return response
def who_is_connected():
host_port=cfg['telnet'].split(':')
response=who(host_port[0],host_port[1],cfg['mycallsign'])
host_port = cfg["telnet"].split(":")
response = who(host_port[0], host_port[1], cfg["mycallsign"])
return response
@app.route('/', methods=['GET'])
@app.route('/index.html', methods=['GET'])
@app.route("/", methods=["GET"])
@app.route("/index.html", methods=["GET"])
def spots():
#payload=spotquery()
response=flask.Response(render_template('index.html',mycallsign=cfg['mycallsign'],telnet=cfg['telnet'],mail=cfg['mail'],menu_list=cfg['menu']['menu_list'],enable_cq_filter=enable_cq_filter,timer_interval=cfg['timer']['interval'],adxo_events=adxo_events,continents=continents_cq,bands=band_frequencies))
# payload=spotquery()
response = flask.Response(
render_template(
"index.html",
mycallsign=cfg["mycallsign"],
telnet=cfg["telnet"],
mail=cfg["mail"],
menu_list=cfg["menu"]["menu_list"],
enable_cq_filter=enable_cq_filter,
timer_interval=cfg["timer"]["interval"],
adxo_events=adxo_events,
continents=continents_cq,
bands=band_frequencies,
)
)
return response
@app.route('/service-worker.js', methods=['GET'])
@app.route("/service-worker.js", methods=["GET"])
def sw():
return app.send_static_file('service-worker.js')
return app.send_static_file("service-worker.js")
@app.route('/offline.html')
@app.route("/offline.html")
def root():
return app.send_static_file('html/offline.html')
return app.send_static_file("html/rel/offline.html")
@app.route('/world.json')
@app.route("/world.json")
def world_data():
return app.send_static_file('data/world.json')
return app.send_static_file("data/world.json")
@app.route('/plots.html')
@app.route("/plots.html")
def plots():
whoj=who_is_connected()
response=flask.Response(render_template('plots.html',mycallsign=cfg['mycallsign'],telnet=cfg['telnet'],mail=cfg['mail'],menu_list=cfg['menu']['menu_list'],who=whoj,continents=continents_cq,bands=band_frequencies))
whoj = who_is_connected()
response = flask.Response(
render_template(
"plots.html",
mycallsign=cfg["mycallsign"],
telnet=cfg["telnet"],
mail=cfg["mail"],
menu_list=cfg["menu"]["menu_list"],
who=whoj,
continents=continents_cq,
bands=band_frequencies,
)
)
return response
@app.route('/cookies.html', methods=['GET'])
@app.route("/cookies.html", methods=["GET"])
def cookies():
response=flask.Response(render_template('cookies.html',mycallsign=cfg['mycallsign'],telnet=cfg['telnet'],mail=cfg['mail'],menu_list=cfg['menu']['menu_list']))
response = flask.Response(
render_template(
"cookies.html",
mycallsign=cfg["mycallsign"],
telnet=cfg["telnet"],
mail=cfg["mail"],
menu_list=cfg["menu"]["menu_list"],
)
)
return response
@app.route('/privacy.html', methods=['GET'])
@app.route("/privacy.html", methods=["GET"])
def privacy():
response=flask.Response(render_template('privacy.html',mycallsign=cfg['mycallsign'],telnet=cfg['telnet'],mail=cfg['mail'],menu_list=cfg['menu']['menu_list']))
response = flask.Response(
render_template(
"privacy.html",
mycallsign=cfg["mycallsign"],
telnet=cfg["telnet"],
mail=cfg["mail"],
menu_list=cfg["menu"]["menu_list"],
)
)
return response
@app.route('/sitemap.xml')
@app.route("/sitemap.xml")
def sitemap():
return app.send_static_file('sitemap.xml')
return app.send_static_file("sitemap.xml")
@app.route('/callsign.html', methods=['GET'])
@app.route("/callsign.html", methods=["GET"])
def callsign():
#payload=spotquery()
callsign=request.args.get('c')
response=flask.Response(render_template('callsign.html',mycallsign=cfg['mycallsign'],telnet=cfg['telnet'],mail=cfg['mail'],menu_list=cfg['menu']['menu_list'],timer_interval=cfg['timer']['interval'],callsign=callsign,adxo_events=adxo_events,continents=continents_cq,bands=band_frequencies))
# payload=spotquery()
callsign = request.args.get("c")
response = flask.Response(
render_template(
"callsign.html",
mycallsign=cfg["mycallsign"],
telnet=cfg["telnet"],
mail=cfg["mail"],
menu_list=cfg["menu"]["menu_list"],
timer_interval=cfg["timer"]["interval"],
callsign=callsign,
adxo_events=adxo_events,
continents=continents_cq,
bands=band_frequencies,
)
)
return response
#API that search a callsign and return all informations about that
@app.route('/callsign', methods=['GET'])
# API that search a callsign and return all informations about that
@app.route("/callsign", methods=["GET"])
def find_callsign():
callsign=request.args.get('c')
response=pfxt.find(callsign)
callsign = request.args.get("c")
response = pfxt.find(callsign)
if response is None:
response=flask.Response(status=204)
response = flask.Response(status=204)
return response
@app.route('/plot_get_heatmap_data', methods=['GET'])
@app.route("/plot_get_heatmap_data", methods=["GET"])
def get_heatmap_data():
continent=request.args.get('continent')
response=flask.Response(json.dumps(heatmap_cbp.get_data(continent)))
continent = request.args.get("continent")
response = flask.Response(json.dumps(heatmap_cbp.get_data(continent)))
logger.debug(response)
if response is None:
response=flask.Response(status=204)
response = flask.Response(status=204)
return response
@app.route('/plot_get_dx_spots_per_month', methods=['GET'])
@app.route("/plot_get_dx_spots_per_month", methods=["GET"])
def get_dx_spots_per_month():
response=flask.Response(json.dumps(bar_graph_spm.get_data()))
response = flask.Response(json.dumps(bar_graph_spm.get_data()))
logger.debug(response)
if response is None:
response=flask.Response(status=204)
response = flask.Response(status=204)
return response
@app.route('/plot_get_dx_spots_trend', methods=['GET'])
@app.route("/plot_get_dx_spots_trend", methods=["GET"])
def get_dx_spots_trend():
response=flask.Response(json.dumps(line_graph_st.get_data()))
response = flask.Response(json.dumps(line_graph_st.get_data()))
logger.debug(response)
if response is None:
response=flask.Response(status=204)
response = flask.Response(status=204)
return response
@app.route('/plot_get_hour_band', methods=['GET'])
@app.route("/plot_get_hour_band", methods=["GET"])
def get_dx_hour_band():
response=flask.Response(json.dumps(bubble_graph_hb.get_data()))
response = flask.Response(json.dumps(bubble_graph_hb.get_data()))
logger.debug(response)
if response is None:
response=flask.Response(status=204)
response = flask.Response(status=204)
return response
@app.route('/plot_get_world_dx_spots_live', methods=['GET'])
@app.route("/plot_get_world_dx_spots_live", methods=["GET"])
def get_world_dx_spots_live():
response=flask.Response(json.dumps(geo_graph_wdsl.get_data()))
response = flask.Response(json.dumps(geo_graph_wdsl.get_data()))
logger.debug(response)
if response is None:
response=flask.Response(status=204)
response = flask.Response(status=204)
return response
@app.context_processor
def inject_template_scope():
injections = dict()
def cookies_check():
value = request.cookies.get('cookie_consent')
return value == 'true'
value = request.cookies.get("cookie_consent")
return value == "true"
injections.update(cookies_check=cookies_check)
return injections
@app.after_request
def add_security_headers(resp):
resp.headers['Strict-Transport-Security']='max-age=1000'
resp.headers['X-Xss-Protection']='1; mode=block'
resp.headers['X-Frame-Options']='SAMEORIGIN'
resp.headers['X-Content-Type-Options']='nosniff'
resp.headers['Referrer-Policy']='strict-origin-when-cross-origin'
resp.headers['Cache-Control']='public, no-cache'
resp.headers['Pragma']='no-cache'
resp.headers["Strict-Transport-Security"] = "max-age=1000"
resp.headers["X-Xss-Protection"] = "1; mode=block"
resp.headers["X-Frame-Options"] = "SAMEORIGIN"
resp.headers["X-Content-Type-Options"] = "nosniff"
resp.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
resp.headers["Cache-Control"] = "public, no-cache"
resp.headers["Pragma"] = "no-cache"
resp.headers['Content-Security-Policy']="\
resp.headers[
"Content-Security-Policy"
] = "\
default-src 'self';\
script-src 'self' cdnjs.cloudflare.com cdn.jsdelivr.net 'unsafe-inline';\
style-src 'self' cdnjs.cloudflare.com cdn.jsdelivr.net 'unsafe-inline';\
@ -384,10 +484,10 @@ def add_security_headers(resp):
"
return resp
# style-src 'self' cdnjs.cloudflare.com cdn.jsdelivr.net 'sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx' 'sha512-uvXdJud8WaOlQFjlz9B15Yy2Au/bMAvz79F7Xa6OakCl2jvQPdHD0hb3dEqZRdSwG4/sknePXlE7GiarwA/9Wg==';\
# style-src 'self' cdnjs.cloudflare.com cdn.jsdelivr.net 'unsafe-inline';\
#script-src 'self' cdnjs.cloudflare.com cdn.jsdelivr.net 'unsafe-inline'
if __name__ == '__main__':
app.run(host='0.0.0.0')
# script-src 'self' cdnjs.cloudflare.com cdn.jsdelivr.net 'unsafe-inline'
if __name__ == "__main__":
app.run(host="0.0.0.0")

View File

@ -2,5 +2,5 @@ import bjoern
from webapp import app
bjoern.listen(app, '0.0.0.0', 8080)
bjoern.listen(app, "0.0.0.0", 8080)
bjoern.run()