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 config.json
__init__ __init__
__pycache__ __pycache__
.vscode/

View File

@ -13,7 +13,7 @@
- **Release:** v2.4.1 - **Release:** v2.4.1
- **Author:** Corrado Gerbaldo - [IU1BOW](https://www.qrz.com/db/IU1BOW) - **Author:** Corrado Gerbaldo - [IU1BOW](https://www.qrz.com/db/IU1BOW)
- **Mail:** <corrado.gerbaldo@gmail.com> - **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 - **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 ### API
**Spot list** **Spot list**
You can retrive last spots calling "**/spotlist**"; For example [www.iu1bow.com/spotlist](https://www.iu1bow.com/spotlist) 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) 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 ### Screenshots
---------- ----------

View File

@ -8,10 +8,11 @@ keys=stream_handler,file_handler
keys=formatter keys=formatter
[logger_root] [logger_root]
level=INFO level=INFO
handlers=stream_handler,file_handler handlers=stream_handler,file_handler
[handler_stream_handler] [handler_stream_handler]
level=INFO
class=StreamHandler class=StreamHandler
formatter=formatter formatter=formatter
args=(sys.stderr,) args=(sys.stderr,)

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
#************************************************************************************* # *************************************************************************************
# Module used to download cty.dat country file and search callsign in it # Module used to download cty.dat country file and search callsign in it
#************************************************************************************* # *************************************************************************************
__author__ = 'IU1BOW - Corrado' __author__ = "IU1BOW - Corrado"
import requests import requests
import logging import logging
import os import os
import time import time
@ -10,124 +10,147 @@ from threading import Timer
from datetime import datetime from datetime import datetime
import json import json
logging.basicConfig(level=logging.INFO,format='%(asctime)s [%(levelname)s]: %(message)s',datefmt='%m/%d/%Y %I:%M:%S') logging.basicConfig(
#TODO: url from conf parameter level=logging.INFO,
url = 'https://www.country-files.com/cty/cty_wt_mod.dat' format="%(asctime)s [%(levelname)s]: %(message)s",
cty_local=os.path.dirname(__file__)+'/../cfg/cty_wt_mod.dat' datefmt="%m/%d/%Y %I:%M:%S",
country_file=os.path.dirname(__file__)+'/../cfg/country.json' )
#------------------------------------------------------------------------------------- # 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 # download country files cty.dat
#------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
def download_cty(url,cty_local): def download_cty(url, cty_local):
try: try:
logging.info('connection to: '+url) logging.info("connection to: " + url)
req=requests.get(url) req = requests.get(url)
f=open(cty_local,'wb') f = open(cty_local, "wb")
f.write(req.content) f.write(req.content)
f.close() f.close()
logging.info('cty file saved in: '+cty_local) logging.info("cty file saved in: " + cty_local)
return 0 return 0
except Exception as e1: except Exception as e1:
logging.error(e1) logging.error(e1)
return 1 return 1
#-------------------------------------------------------------------------------------
# get age of a file in days # -------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------- # get age of a file in days
# -------------------------------------------------------------------------------------
def file_age_in_days(pathname): def file_age_in_days(pathname):
return (time.time() - os.stat(pathname).st_ctime)/(24*3600) return (time.time() - os.stat(pathname).st_ctime) / (24 * 3600)
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# manage file cty.dat # manage file cty.dat
#------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
def get_cty(url,local): def get_cty(url, local):
if os.path.isfile(local): if os.path.isfile(local):
age=file_age_in_days(local) age = file_age_in_days(local)
if age>7: if age > 7:
logging.info(cty_local+' too old ('+str(round(age,0))+' days): proceding to download it') logging.info(
return download_cty(url,local) cty_local
# else: + " too old ("
# logging.info(cty_local+' updated ('+str(round(age,0))+' days), is not necessary to download it') + str(round(age, 0))
# return 0 + " days): proceding to download it"
logging.info(cty_local+' updated ('+str(round(age,0))+' days), is not necessary 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 return 0
# else: # else:
# logging.info(cty_local+' not present: proceding to download it') # logging.info(cty_local+' not present: proceding to download it')
# return download_cty(url,local) # return download_cty(url,local)
logging.info(cty_local+' not present: proceding to download it') logging.info(cty_local + " not present: proceding to download it")
return download_cty(url,local) return download_cty(url, local)
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# parsing alias and get exceptions # parsing alias and get exceptions
#------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
def parse_alias(alias,master): def parse_alias(alias, master):
try: try:
#create a dictionary of array, with start and end position of each exception # create a dictionary of array, with start and end position of each exception
find_dict={} find_dict = {}
find_dict['pos_cq']=[alias.find('('),alias.find(')')] find_dict["pos_cq"] = [alias.find("("), alias.find(")")]
find_dict['pos_itu']=[alias.find('['),alias.find(']')] find_dict["pos_itu"] = [alias.find("["), alias.find("]")]
find_dict['pos_lat_lon']=[alias.find('<'),alias.find('>')] find_dict["pos_lat_lon"] = [alias.find("<"), alias.find(">")]
find_dict['pos_continent']=[alias.find('{'),alias.find('}')] find_dict["pos_continent"] = [alias.find("{"), alias.find("}")]
find_dict['pos_time']=[alias.find('~'),alias[:alias.find('~')].find('~')] find_dict["pos_time"] = [alias.find("~"), alias[: alias.find("~")].find("~")]
first=9999 first = 9999
parsed={} parsed = {}
#assign default values from master callsing # assign default values from master callsing
parsed["country"]=master["country"] parsed["country"] = master["country"]
parsed["cq"]=master["cq"] parsed["cq"] = master["cq"]
parsed["itu"]=master["itu"] parsed["itu"] = master["itu"]
parsed["continent"]=master["continent"] parsed["continent"] = master["continent"]
parsed["lat"]=master["lat"] parsed["lat"] = master["lat"]
parsed["lon"]=master["lon"] parsed["lon"] = master["lon"]
parsed["time_loc"]=master["time_loc"] parsed["time_loc"] = master["time_loc"]
parsed["full"]=master["full"] parsed["full"] = master["full"]
parsed["darc_waedc"]=master["darc_waedc"] parsed["darc_waedc"] = master["darc_waedc"]
#extract override cq # extract override cq
if find_dict['pos_cq'][0]>=0: if find_dict["pos_cq"][0] >= 0:
parsed["cq"]=alias[find_dict['pos_cq'][0]+1:find_dict['pos_cq'][1]] parsed["cq"] = alias[find_dict["pos_cq"][0] + 1 : find_dict["pos_cq"][1]]
if find_dict['pos_cq'][0] < first: if find_dict["pos_cq"][0] < first:
first=find_dict['pos_cq'][0] first = find_dict["pos_cq"][0]
#extract override itu # extract override itu
if find_dict['pos_itu'][0]>=0: if find_dict["pos_itu"][0] >= 0:
parsed["itu"]=alias[find_dict['pos_itu'][0]+1:find_dict['pos_itu'][1]] parsed["itu"] = alias[find_dict["pos_itu"][0] + 1 : find_dict["pos_itu"][1]]
if find_dict['pos_itu'][0] < first: if find_dict["pos_itu"][0] < first:
first=find_dict['pos_itu'][0] first = find_dict["pos_itu"][0]
#extract override lat_lon # extract override lat_lon
if find_dict['pos_lat_lon'][0]>=0: if find_dict["pos_lat_lon"][0] >= 0:
lat_lon=alias[find_dict['pos_lat_lon'][0]+1:find_dict['pos_lat_lon'][1]] lat_lon = alias[
parsed["lat"]=lat_lon[0:].split('/')[0] find_dict["pos_lat_lon"][0] + 1 : find_dict["pos_lat_lon"][1]
parsed["lon"]=lat_lon[:len(lat_lon)].split('/')[1] ]
if find_dict['pos_lat_lon'][0] < first: parsed["lat"] = lat_lon[0:].split("/")[0]
first=find_dict['pos_lat_lon'][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 # extract override continent
if find_dict['pos_continent'][0]>=0: if find_dict["pos_continent"][0] >= 0:
parsed["continent"]=alias[find_dict['pos_continent'][0]+1:find_dict['pos_continent'][1]] parsed["continent"] = alias[
if find_dict['pos_continent'][0] < first: find_dict["pos_continent"][0] + 1 : find_dict["pos_continent"][1]
first=find_dict['pos_continent'][0] ]
if find_dict["pos_continent"][0] < first:
first = find_dict["pos_continent"][0]
#extract override time # extract override time
if find_dict['pos_time'][0]>=0: if find_dict["pos_time"][0] >= 0:
parsed["time_loc"]=alias[find_dict['pos_time'][0]+1:find_dict['pos_time'][1]] parsed["time_loc"] = alias[
if find_dict['pos_time'][0] < first: find_dict["pos_time"][0] + 1 : find_dict["pos_time"][1]
first=find_dict['pos_time'][0] ]
if find_dict["pos_time"][0] < first:
first = find_dict["pos_time"][0]
#extract callsign # extract callsign
callsing=alias[:first].upper() callsing = alias[:first].upper()
if callsing.startswith('='): if callsing.startswith("="):
parsed["full"]='y' parsed["full"] = "y"
callsing=callsing[1:] callsing = callsing[1:]
if callsing.startswith('*'): if callsing.startswith("*"):
parsed["darc_waedc"]='y' parsed["darc_waedc"] = "y"
callsing=callsing[1:] callsing = callsing[1:]
return callsing, parsed return callsing, parsed
@ -138,43 +161,47 @@ def parse_alias(alias,master):
logging.error(alias) logging.error(alias)
return -1 return -1
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# load file from configuration, containing all world country, with related ISO codes # load file from configuration, containing all world country, with related ISO codes
#------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
def load_country(): def load_country():
with open(country_file) as json_country: with open(country_file) as json_country:
return json.load(json_country) return json.load(json_country)
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# search for ISO code, transcoding the country description # search for ISO code, transcoding the country description
#------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
def add_country(table): def add_country(table):
country_data=load_country() country_data = load_country()
for key, value in table.items(): for key, value in table.items():
found=0 found = 0
for i in country_data['country_codes']: for i in country_data["country_codes"]:
if i['desc'].upper()==value['country'].upper(): if i["desc"].upper() == value["country"].upper():
value["iso"]=i["ISO"] value["iso"] = i["ISO"]
value["wpx"]=i["WPX"] value["wpx"] = i["WPX"]
found=1 found = 1
break break
if found==0: if found == 0:
logging.warning('country "'+value['country']+'" not found in cfg/country.json') logging.warning(
'country "' + value["country"] + '" not found in cfg/country.json'
)
return return
class prefix_table: class prefix_table:
# -------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------
# init of the class # init of the class
# - download file cty.dat # - download file cty.dat
# - parse file and create prefix_master with all prefixies and attributes # - parse file and create prefix_master with all prefixies and attributes
#..................................................................................... # .....................................................................................
# CTY.DAT Format # CTY.DAT Format
# #
# reference: https://www.country-files.com/cty-dat-format/ # reference: https://www.country-files.com/cty-dat-format/
# #
# Note that the fields are aligned in columns and spaced out for readability only. It # Note that the fields are aligned in columns and spaced out for readability only. It
# is the “:” at the end of each field that acts as a delimiter for that field: # is the “:” at the end of each field that acts as a delimiter for that field:
# Column Length Description # Column Length Description
# 1 26 Country Name # 1 26 Country Name
@ -185,17 +212,17 @@ class prefix_table:
# 51 10 Longitude in degrees, + for West # 51 10 Longitude in degrees, + for West
# 61 9 Local time offset from GMT # 61 9 Local time offset from GMT
# 70 6 Primary DXCC Prefix (A “*” preceding this prefix indicates that the country # 70 6 Primary DXCC Prefix (A “*” preceding this prefix indicates that the country
# is on the DARC WAEDC list, and counts in CQ-sponsored contests, but not # is on the DARC WAEDC list, and counts in CQ-sponsored contests, but not
# ARRL-sponsored contests). # ARRL-sponsored contests).
# #
# Alias DXCC prefixes (including the primary one) follow on consecutive lines, # Alias DXCC prefixes (including the primary one) follow on consecutive lines,
# separated by commas (,). Multiple lines are OK; a line to be continued should end with # separated by commas (,). Multiple lines are OK; a line to be continued should end with
# comma (,) though its not required. A semi-colon (;) terminates the last alias # comma (,) though its not required. A semi-colon (;) terminates the last alias
# prefix in the list. # prefix in the list.
# #
# If an alias prefix is preceded by =, this indicates that the prefix is to be treated # If an alias prefix is preceded by =, this indicates that the prefix is to be treated
# as a full callsign, i.e. must be an exact match. # as a full callsign, i.e. must be an exact match.
# #
# The following special characters can be applied after an alias prefix: # The following special characters can be applied after an alias prefix:
# (#) Override CQ Zone # (#) Override CQ Zone
# [#] Override ITU Zone # [#] Override ITU Zone
@ -203,123 +230,127 @@ class prefix_table:
# {aa} Override Continent # {aa} Override Continent
# ~#~ Override local time offset from GMT # ~#~ Override local time offset from GMT
# #
#------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
def __init__(self): def __init__(self):
global prefix_master global prefix_master
prefix_master=dict() prefix_master = dict()
initialization() initialization()
return return
global initialization global initialization
def initialization(): def initialization():
refresh() refresh()
global timer 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() timer.start()
return return
# -------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------- # refresh data
# refresh data # -------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------
global refresh global refresh
def refresh(): def refresh():
logging.info('CTY: start initialization') logging.info("CTY: start initialization")
if get_cty(url,cty_local)>0: if get_cty(url, cty_local) > 0:
logging.error('there is a problem during downloading country files!') logging.error("there is a problem during downloading country files!")
logging.info('continue with previous file') logging.info("continue with previous file")
logging.info('check the connectivity, or put manually the file '+cty_local) logging.info(
line_num=0 "check the connectivity, or put manually the file " + cty_local
line_num_valid=0 )
entities_number=0 line_num = 0
data='' line_num_valid = 0
table=[] entities_number = 0
data = ""
table = []
prefix_master.clear() prefix_master.clear()
try: try:
with open(cty_local, 'r') as f: with open(cty_local, "r") as f:
for line in f: for line in f:
line_num+=1 line_num += 1
li=line.strip() li = line.strip()
#remove comments # remove comments
if not li.startswith("#"): if not li.startswith("#"):
line_num_valid+=1 line_num_valid += 1
data+=li data += li
logging.info('number of lines reads: '+str(line_num)) logging.info("number of lines reads: " + str(line_num))
logging.info('number of valid lines: '+str(line_num_valid)) 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(',')
for sb in sub_prefixies:
values={}
callsign, values=parse_alias(sb,single_prefix)
prefix_master[callsign]=values
logging.info('number of entities: '+str(entities_number)) # split in array of lines terminated with semicolon
logging.info('number of single alias: '+str(len(prefix_master))) 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
logging.info("number of entities: " + str(entities_number))
logging.info("number of single alias: " + str(len(prefix_master)))
add_country(prefix_master) add_country(prefix_master)
logging.info('memory used for prefix: '+str(prefix_master.__sizeof__())+' bytes') logging.info(
logging.info('CTY: initialization complete') "memory used for prefix: " + str(prefix_master.__sizeof__()) + " bytes"
)
logging.info("CTY: initialization complete")
return return
except Exception as e1: except Exception as e1:
template = "An exception of type {0} occurred. Arguments:\n{1!r}" template = "An exception of type {0} occurred. Arguments:\n{1!r}"
message = template.format(type(e1).__name__, e1.args) message = template.format(type(e1).__name__, e1.args)
logging.error(message) logging.error(message)
return return
# -------------------------------------------------------------------------------------
#-------------------------------------------------------------------------------------
# find a callsign # find a callsign
#------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
def find(self,callsign): def find(self, callsign):
try: try:
data=dict() data = dict()
i=len(callsign) i = len(callsign)
callsign=callsign.strip().upper() callsign = callsign.strip().upper()
while i>0: while i > 0:
try: try:
data=prefix_master[callsign[:i]] data = prefix_master[callsign[:i]]
data['match']=callsign[:i] data["match"] = callsign[:i]
return data return data
except KeyError: except KeyError:
pass pass
i-=1 i -= 1
except Exception as e1: except Exception as e1:
template = "An exception of type {0} occurred. Arguments:\n{1!r}" template = "An exception of type {0} occurred. Arguments:\n{1!r}"
message = template.format(type(e1).__name__, e1.args) message = template.format(type(e1).__name__, e1.args)
logging.error(message) logging.error(message)
return data return data
#not found # not found
data["country"]="unknown country" data["country"] = "unknown country"
data["iso"]="xx" data["iso"] = "xx"
return data return data
def __del__(self): def __del__(self):
timer.cancel() timer.cancel()
logging.info('prefix_table destroyed') logging.info("prefix_table destroyed")
return return

View File

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

View File

@ -1,9 +1,9 @@
#************************************************************************************* # *************************************************************************************
# Module used to convert dxcluster band file to band configuration file modes for # Module used to convert dxcluster band file to band configuration file modes for
# spiderweb # spiderweb
# you can use it in build step # you can use it in build step
#************************************************************************************* # *************************************************************************************
__author__ = 'IU1BOW - Corrado' __author__ = "IU1BOW - Corrado"
import logging import logging
import os import os
import time import time
@ -12,47 +12,53 @@ import json
import re import re
import sys import sys
logging.basicConfig(level=logging.INFO,format='%(asctime)s [%(levelname)s]: %(message)s',datefmt='%m/%d/%Y %I:%M:%S') logging.basicConfig(
#dxspider_band='/home/sysop/spider/data/bands.pl' level=logging.INFO,
output_modes='../cfg/modes.json' 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 # reads bands file and convert to json
#------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
def parse(input_file): def parse(input_file):
line_num=0 line_num = 0
line_num_valid=0 line_num_valid = 0
data='' data = ""
band_trigger = False band_trigger = False
try: try:
with open(input_file, 'r') as f: with open(input_file, "r") as f:
for line in f: for line in f:
line_num+=1 line_num += 1
li=line.strip() li = line.strip()
#remove comments # remove comments
if li.startswith("%bands = "): if li.startswith("%bands = "):
band_trigger = True band_trigger = True
if li.endswith(");"): if li.endswith(");"):
band_trigger = False band_trigger = False
if not li.startswith("#") and band_trigger == True: if not li.startswith("#") and band_trigger == True:
line_num_valid+=1 line_num_valid += 1
data+=li data += li
logging.debug("first step parsing output: ") logging.debug("first step parsing output: ")
logging.debug(data) logging.debug(data)
#replacing strings in order to obtain a json # replacing strings in order to obtain a json
data=data.lower() data = data.lower()
data=data.replace(" ","") data = data.replace(" ", "")
data=data.replace("bless","") data = data.replace("bless", "")
data=data.replace("%bands=","") data = data.replace("%bands=", "")
data=data.replace(",'bands'","") data = data.replace(",'bands'", "")
data=re.sub(r"([\"'])(?:(?=(\\?))\2.)*?\1=>", "", data) #remove token like '136khz' => data = re.sub(
data=data.replace("=>",":") r"([\"'])(?:(?=(\\?))\2.)*?\1=>", "", data
data=data.replace("(","") ) # remove token like '136khz' =>
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.replace(",}","}") data = data.replace(")", "")
data="["+data+"]" data = re.sub(r"([a-zA-Z]+):", r'"\1":', data) # add quotation around words
data=data.replace(",]","]") data = data.replace(",}", "}")
data = "[" + data + "]"
data = data.replace(",]", "]")
logging.debug("second step parsing output: ") logging.debug("second step parsing output: ")
logging.debug(data) logging.debug(data)
@ -65,18 +71,21 @@ def parse(input_file):
logging.error(input_file) logging.error(input_file)
return "" return ""
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# add min max freq element to the related mode in final json # add min max freq element to the related mode in final json
#------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
def add_freq(mode, freq, json_modes): def add_freq(mode, freq, json_modes):
try: try:
for modes in json_modes['modes']: for modes in json_modes["modes"]:
if modes["id"]==mode: if modes["id"] == mode:
ind_freq = 0 ind_freq = 0
while ind_freq < len(freq): while ind_freq < len(freq):
modes["freq"].append({"min":freq[ind_freq],"max":freq[ind_freq+1]}) modes["freq"].append(
ind_freq +=2 {"min": freq[ind_freq], "max": freq[ind_freq + 1]}
)
ind_freq += 2
return json_modes return json_modes
except Exception as e1: except Exception as e1:
@ -85,24 +94,27 @@ def add_freq(mode, freq, json_modes):
logging.error(message) logging.error(message)
return {} return {}
#-------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------
# reads bands file and convert to json # reads bands file and convert to json
#------------------------------------------------------------------------------------- # -------------------------------------------------------------------------------------
def create_output(data): 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: try:
for element in data: for element in data:
for mode in element: for mode in element:
if mode == "band": if mode == "band":
pass pass
elif mode == "cw": elif mode == "cw":
json_modes=add_freq("cw",element[mode],json_modes) json_modes = add_freq("cw", element[mode], json_modes)
elif mode == "ssb": elif mode == "ssb":
json_modes=add_freq("phone",element[mode],json_modes) json_modes = add_freq("phone", element[mode], json_modes)
else: 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("final step output: ")
logging.debug(json.dumps(json_modes)) logging.debug(json.dumps(json_modes))
@ -115,23 +127,24 @@ def create_output(data):
logging.error(input_file) logging.error(input_file)
return {} return {}
#*************************************************************************************
# *************************************************************************************
# Main # Main
#************************************************************************************* # *************************************************************************************
logging.info('RDxSpider band file conversion starting...') logging.info("RDxSpider band file conversion starting...")
if len(sys.argv)!=2: if len(sys.argv) != 2:
logging.error("argument invalid. Specify dxcluster band file") logging.error("argument invalid. Specify dxcluster band file")
logging.error("use: python get_dxcluster_modes.py input_file") logging.error("use: python get_dxcluster_modes.py input_file")
raise Exception() raise Exception()
dxspider_band=sys.argv[1] dxspider_band = sys.argv[1]
parsed = parse(dxspider_band) parsed = parse(dxspider_band)
if parsed != "" : if parsed != "":
json_output=create_output(parsed) json_output = create_output(parsed)
with open(output_modes,'w') as outfile: with open(output_modes, "w") as outfile:
json.dump(json_output, outfile,indent=4) json.dump(json_output, outfile, indent=4)
logging.info('modes saved to: '+output_modes) logging.info("modes saved to: " + output_modes)
logging.info('DxSpider band file conversion completed') logging.info("DxSpider band file conversion completed")
else: else:
logging.error("error on parsing input file") 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 # Module that contain classess for providing data to plotting front-end page
#*********************************************************************************** # ***********************************************************************************
__author__ = 'IU1BOW - Corrado' __author__ = "IU1BOW - Corrado"
import threading import threading
import time import time
from lib.qry import query_manager from lib.qry import query_manager
import pandas as pd import pandas as pd
import json import json
#----------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------
# Base class (as template for other classes) # Base class (as template for other classes)
#----------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------
class BaseDataProvider: 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_data
global glb_last_refresh global glb_last_refresh
global glb_response 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 # time for refresh
def refresh(self): 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 {}
#return data to the caller
def get_data(self):
self.glb_response = {}
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)
# 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.qm = qm
self.continents=continents
self.bands=bands
self.refresh()
self.logger.info("Class: %s init end",self.__class__.__name__)
return
#----------------------------------------------------------------------------------- # return data to the caller
def get_data(self):
self.glb_response = {}
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)
# 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.qm = qm
self.continents = continents
self.bands = bands
self.refresh()
self.logger.info("Class: %s init end", self.__class__.__name__)
return
# -----------------------------------------------------------------------------------
# Class for managing data for Continent/Band chart # Class for managing data for Continent/Band chart
#----------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------
class ContinentsBandsProvider(BaseDataProvider): class ContinentsBandsProvider(BaseDataProvider):
def __init__(self, logger, qm, continents, bands):
# Calling constructor of base class
super().__init__(logger, qm, continents, bands)
def __init__(self,logger, qm, continents, bands): def __load_data(self, band_frequencies, continents_cq):
# Calling constructor of base class
super().__init__(logger,qm,continents,bands)
def __load_data(self,band_frequencies, continents_cq): self.logger.info("Start")
self.logger.info("doing query...")
self.logger.info("Start") # construct bands query
self.logger.info("doing 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"] + '"'
#construct bands query # construct continent region query
bands_qry_string = 'CASE ' spottercq_qry_string = "CASE "
self.logger.debug(band_frequencies) spotcq_qry_string = "CASE "
for i in range(len(band_frequencies["bands"])): for i in range(len(continents_cq["continents"])):
bands_qry_string+=' WHEN freq between '+str(band_frequencies["bands"][i]["min"])+' AND '+ str(band_frequencies["bands"][i]["max"]) spottercq_qry_string += (
bands_qry_string+=' THEN "'+band_frequencies["bands"][i]["id"]+'"' " 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 continent region query # construct final query string
spottercq_qry_string = 'CASE ' qry_string = (
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"]+'"'
#construct final query string
qry_string ="""
SELECT SELECT
"""+spottercq_qry_string+""" ELSE spottercq END, """
"""+spotcq_qry_string+""" ELSE spotcq END, + spottercq_qry_string
"""+bands_qry_string+""" END, + """ ELSE spottercq END,
"""
+ spotcq_qry_string
+ """ ELSE spotcq END,
"""
+ bands_qry_string
+ """ END,
count(0) number count(0) number
from spot from spot
where where
@ -86,91 +104,97 @@ class ContinentsBandsProvider(BaseDataProvider):
group by 1, 2, 3 group by 1, 2, 3
; ;
""" """
)
self.logger.debug(qry_string) self.logger.debug(qry_string)
self.qm.qry(qry_string) self.qm.qry(qry_string)
data=self.qm.get_data() data = self.qm.get_data()
if len(data)==0: if len(data) == 0:
self.logger.warning("no data found") self.logger.warning("no data found")
self.logger.info("query done") self.logger.info("query done")
self.logger.debug (data) self.logger.debug(data)
return data return data
# function for search continent in the global data returned by query and making a cartesian product # function for search continent in the global data returned by query and making a cartesian product
# in order to prepare data for heatmap # in order to prepare data for heatmap
def __normalize_continent(self,data_list,continent,continents_list, band_list): def __normalize_continent(self, data_list, continent, continents_list, band_list):
data_filtered=[] data_filtered = []
for i, item_data in enumerate(data_list): for i, item_data in enumerate(data_list):
if item_data[0]==continent and not (item_data[3] is None): if item_data[0] == continent and not (item_data[3] is None):
element=[] element = []
element.append(item_data[1]) element.append(item_data[1])
element.append(item_data[2]) element.append(item_data[2])
element.append(item_data[3]) element.append(item_data[3])
data_filtered.append(element) data_filtered.append(element)
cartesian_product = [] cartesian_product = []
for j, item_continent in enumerate(continents_list): for j, item_continent in enumerate(continents_list):
for k, item_band in enumerate(band_list): for k, item_band in enumerate(band_list):
found=0 found = 0
for lis, item_filtered in enumerate(data_filtered): for lis, item_filtered in enumerate(data_filtered):
if item_filtered[0]==item_continent["id"] and item_filtered[1]==item_band["id"]: if (
#cartesian_product.append(item_filtered) item_filtered[0] == item_continent["id"]
element=[] and item_filtered[1] == item_band["id"]
element.append(j) ):
element.append(k) # cartesian_product.append(item_filtered)
element.append(item_filtered[2]) element = []
cartesian_product.append(element) element.append(j)
found=1 element.append(k)
if found==0: element.append(item_filtered[2])
element=[] cartesian_product.append(element)
element.append(j) found = 1
element.append(k) if found == 0:
element.append(0) element = []
cartesian_product.append(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) self.logger.debug(cartesian_product)
return cartesian_product return cartesian_product
def refresh(self): def refresh(self):
super().refresh() super().refresh()
lcl_data={} lcl_data = {}
qry_data=self.__load_data(self.bands, self.continents) qry_data = self.__load_data(self.bands, self.continents)
for i, item in enumerate(self.continents["continents"]): for i, item in enumerate(self.continents["continents"]):
continent=item["id"] continent = item["id"]
data_de=self.__normalize_continent(qry_data,continent,self.continents["continents"],self.bands["bands"]) data_de = self.__normalize_continent(
lcl_data.update({continent:data_de}) qry_data, continent, self.continents["continents"], self.bands["bands"]
)
self.glb_data=lcl_data lcl_data.update({continent: data_de})
self.glb_last_refresh=time.time()
threading.Timer(15*60,self.refresh).start() #periodic refresh: set time self.glb_data = lcl_data
return self.glb_last_refresh = time.time()
def get_data(self,continent_filter):
super().get_data()
self.glb_response.update({"band activity": self.glb_data[continent_filter]})
return self.glb_response
#----------------------------------------------------------------------------------- threading.Timer(15 * 60, self.refresh).start() # periodic refresh: set time
return
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 for managing data for Spots per months chart
#----------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------
class SpotsPerMounthProvider(BaseDataProvider): class SpotsPerMounthProvider(BaseDataProvider):
def __init__(self, logger, qm):
# Calling constructor of base class
super().__init__(logger, qm, [], [])
def __init__(self,logger,qm): def __load_data(self):
# Calling constructor of base class
super().__init__(logger,qm,[],[])
def __load_data(self): self.logger.info("Start")
self.logger.info("doing query...")
self.logger.info("Start") # construct final query string
self.logger.info("doing query...") qry_string = """
#construct final query string
qry_string="""
select month(s1.ym) as referring_month, select month(s1.ym) as referring_month,
cast(sum( cast(sum(
case case
@ -226,55 +250,59 @@ class SpotsPerMounthProvider(BaseDataProvider):
group by referring_month group by referring_month
; ;
""" """
self.logger.debug(qry_string) self.logger.debug(qry_string)
self.qm.qry(qry_string) self.qm.qry(qry_string)
data=self.qm.get_data() data = self.qm.get_data()
if len(data)==0: if len(data) == 0:
self.logger.warning("no data found") self.logger.warning("no data found")
self.logger.info("query done") self.logger.info("query done")
self.logger.debug (data) self.logger.debug(data)
return data return data
def refresh(self): def refresh(self):
super().refresh() super().refresh()
lcl_data={} lcl_data = {}
qry_data=self.__load_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})
self.logger.debug(lcl_data) for i, item in enumerate(qry_data):
year_data = {"year_0": item[1], "year_1": item[2], "year_2": item[3]}
self.glb_data=lcl_data lcl_data.update({item[0]: year_data})
self.glb_last_refresh=time.time()
threading.Timer(60*60*24,self.refresh).start() #periodic refresh: set time self.logger.debug(lcl_data)
return
def get_data(self,): self.glb_data = lcl_data
super().get_data() self.glb_last_refresh = time.time()
self.glb_response.update({"spots_per_month": self.glb_data})
return self.glb_response
#----------------------------------------------------------------------------------- threading.Timer(
60 * 60 * 24, self.refresh
).start() # periodic refresh: set time
return
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 for managing data for Spots trend chart
#----------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------
class SpotsTrend(BaseDataProvider): class SpotsTrend(BaseDataProvider):
def __init__(self, logger, qm):
# Calling constructor of base class
super().__init__(logger, qm, [], [])
def __init__(self,logger,qm): def __load_data(self):
# Calling constructor of base class
super().__init__(logger,qm,[],[])
def __load_data(self): self.logger.info("Start")
self.logger.info("doing query...")
self.logger.info("Start") # construct final query string
self.logger.info("doing query...") qry_string = """
#construct final query string
qry_string="""
select select
FROM_UNIXTIME(time,'%Y-%m-%d') as day, FROM_UNIXTIME(time,'%Y-%m-%d') as day,
count(0) as total count(0) as total
@ -283,82 +311,103 @@ class SpotsTrend(BaseDataProvider):
GROUP by 1 GROUP by 1
; ;
""" """
self.logger.debug(qry_string) self.logger.debug(qry_string)
self.qm.qry_pd(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.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") self.logger.warning("no data found")
#normalize data eliminating peaks # normalize data eliminating peaks
df['day']=pd.to_datetime(df['day']) df["day"] = pd.to_datetime(df["day"])
df=df.set_index('day') df = df.set_index("day")
df=df.resample('D').interpolate(method='pad', limit_direction='forward', axis=0) df = df.resample("D").interpolate(
df=df.rolling('30D').mean() method="pad", limit_direction="forward", axis=0
df['total']=df['total'].round(0) )
df = df.rolling("30D").mean()
df["total"] = df["total"].round(0)
return df return df
def refresh(self): def refresh(self):
super().refresh() 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(): 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.logger.debug(lcl_data)
self.glb_data=lcl_data
self.glb_last_refresh=time.time()
threading.Timer(60*60*24,self.refresh).start() #periodic refresh: set time self.glb_data = lcl_data
return self.glb_last_refresh = time.time()
def get_data(self): threading.Timer(
super().get_data() 60 * 60 * 24, self.refresh
self.glb_response.update({"spots_trend": self.glb_data}) ).start() # periodic refresh: set time
return self.glb_response return
#----------------------------------------------------------------------------------- def get_data(self):
super().get_data()
self.glb_response.update({"spots_trend": self.glb_data})
return self.glb_response
# -----------------------------------------------------------------------------------
# Class for managing data for Hour/Band chart # Class for managing data for Hour/Band chart
#----------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------
class HourBand(BaseDataProvider): class HourBand(BaseDataProvider):
def __init__(self, logger, qm, bands):
# Calling constructor of base class
super().__init__(logger, qm, [], bands)
def __init__(self,logger,qm,bands): def __load_data(self):
# Calling constructor of base class
super().__init__(logger,qm,[],bands)
def __load_data(self): self.logger.info("Start")
self.logger.info("doing query...")
self.logger.info("Start") self.logger.debug(self.bands)
self.logger.info("doing query...") # 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"] + '"'
self.logger.debug(self.bands) # construct bands query weight
#construct bands query bands_weight_qry_string = "CASE "
bands_qry_string = 'CASE ' for i in range(len(self.bands["bands"])):
for i in range(len(self.bands["bands"])): bands_weight_qry_string += (
bands_qry_string+=' WHEN freq between '+str(self.bands["bands"][i]["min"])+' AND '+ str(self.bands["bands"][i]["max"]) " WHEN freq between "
bands_qry_string+=' THEN "'+self.bands["bands"][i]["id"]+'"' + 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 bands query weight # construct final query string
bands_weight_qry_string = 'CASE ' qry_string = (
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"])+'"'
#construct final query string
qry_string ="""
select s1.band, s1.hour, s1.total from ( select s1.band, s1.hour, s1.total from (
SELECT SELECT
cast(concat(HOUR (FROM_UNIXTIME(time))) as unsigned) as hour, 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 count(0) AS total
from spot from spot
WHERE FROM_UNIXTIME(time) > DATE_SUB(now(), INTERVAL 1 MONTH) WHERE FROM_UNIXTIME(time) > DATE_SUB(now(), INTERVAL 1 MONTH)
@ -368,60 +417,63 @@ class HourBand(BaseDataProvider):
order by s1.band, s1.hour order by s1.band, s1.hour
; ;
""" """
)
self.logger.debug(qry_string) self.logger.debug(qry_string)
self.qm.qry(qry_string) self.qm.qry(qry_string)
data=self.qm.get_data() data = self.qm.get_data()
if len(data)==0: if len(data) == 0:
self.logger.warning("no data found") self.logger.warning("no data found")
self.logger.info("query done") self.logger.info("query done")
self.logger.debug (data) self.logger.debug(data)
return data return data
def refresh(self): def refresh(self):
super().refresh() super().refresh()
lcl_data={} lcl_data = {}
qry_data=self.__load_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: if i not in lcl_data:
lcl_data[i]={} lcl_data[i] = {}
lcl_data[i].update({j:k}) lcl_data[i].update({j: k})
self.logger.debug(lcl_data) self.logger.debug(lcl_data)
self.glb_data=lcl_data
self.glb_last_refresh=time.time()
threading.Timer(60*60*24,self.refresh).start() #periodic refresh: set time self.glb_data = lcl_data
return self.glb_last_refresh = time.time()
def get_data(self): threading.Timer(
super().get_data() 60 * 60 * 24, self.refresh
self.glb_response.update({"hour_band": self.glb_data}) ).start() # periodic refresh: set time
return self.glb_response return
def get_data(self):
super().get_data()
self.glb_response.update({"hour_band": self.glb_data})
return self.glb_response
#----------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------
# Class for managing data for World DX SPOTS current activity # Class for managing data for World DX SPOTS current activity
#----------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------
class WorldDxSpotsLive(BaseDataProvider): class WorldDxSpotsLive(BaseDataProvider):
global glb_pfxt global glb_pfxt
def __init__(self,logger,qm,pfxt): def __init__(self, logger, qm, pfxt):
# Calling constructor of base class # Calling constructor of base class
self.glb_pfxt=pfxt self.glb_pfxt = pfxt
super().__init__(logger,qm,[],[]) super().__init__(logger, qm, [], [])
def __load_data(self): def __load_data(self):
self.logger.info("Start") self.logger.info("Start")
self.logger.info("doing query...") self.logger.info("doing query...")
#construct final query string # construct final query string
qry_string =""" qry_string = """
select spotcall as dx select spotcall as dx
from spot from spot
WHERE FROM_UNIXTIME(time) > DATE_SUB(now(), INTERVAL 1 HOUR) WHERE FROM_UNIXTIME(time) > DATE_SUB(now(), INTERVAL 1 HOUR)
@ -429,70 +481,70 @@ class WorldDxSpotsLive(BaseDataProvider):
group by 1; group by 1;
""" """
self.logger.debug(qry_string) self.logger.debug(qry_string)
self.qm.qry(qry_string) self.qm.qry(qry_string)
data=self.qm.get_data() data = self.qm.get_data()
row_headers=self.qm.get_headers() row_headers = self.qm.get_headers()
if len(data)==0: if len(data) == 0:
self.logger.warning("no data found") self.logger.warning("no data found")
self.logger.info("query done") self.logger.info("query done")
self.logger.debug (data) self.logger.debug(data)
#define country table for search info on callsigns # define country table for search info on callsigns
df = pd.DataFrame(columns=['row_id','dx','lat','lon']) df = pd.DataFrame(columns=["row_id", "dx", "lat", "lon"])
dx=[] dx = []
lat=[] lat = []
lon=[] lon = []
row_id=[] row_id = []
idx=0 idx = 0
for result in data: for result in data:
main_result=dict(zip(row_headers,result)) main_result = dict(zip(row_headers, result))
# find the country in prefix table # find the country in prefix table
search_prefix=self.glb_pfxt.find(main_result["dx"]) search_prefix = self.glb_pfxt.find(main_result["dx"])
if search_prefix["country"] != "unknown country" : if search_prefix["country"] != "unknown country":
# merge recordset and contry prefix # merge recordset and contry prefix
dx.append(main_result["dx"]) dx.append(main_result["dx"])
lon.append(float(search_prefix["lat"])) lon.append(float(search_prefix["lat"]))
lat.append(-float(search_prefix["lon"])) lat.append(-float(search_prefix["lon"]))
idx+=1 idx += 1
row_id.append(idx) row_id.append(idx)
df['dx']=dx df["dx"] = dx
df['lat']=lat df["lat"] = lat
df['lon']=lon df["lon"] = lon
df['row_id']=row_id df["row_id"] = row_id
df_grp=df.groupby(["lat", "lon"])["row_id"].count().reset_index(name="count") df_grp = df.groupby(["lat", "lon"])["row_id"].count().reset_index(name="count")
if df is None ==0:
logger.warning("no data found")
return df_grp if df is None == 0:
logger.warning("no data found")
def refresh(self): return df_grp
super().refresh()
lcl_data={}
qry_data=self.__load_data()
self.logger.debug(qry_data) def refresh(self):
super().refresh()
lcl_data = {}
qry_data = self.__load_data()
lcl_data=[] self.logger.debug(qry_data)
for index, row in qry_data.iterrows():
record=dict(lat=row['lat'], lon=row['lon'], count=row['count'])
lcl_data.append(record)
self.logger.debug(lcl_data) lcl_data = []
for index, row in qry_data.iterrows():
self.glb_data=lcl_data record = dict(lat=row["lat"], lon=row["lon"], count=row["count"])
self.glb_last_refresh=time.time() lcl_data.append(record)
threading.Timer(5*60,self.refresh).start() #periodic refresh: set time self.logger.debug(lcl_data)
return
def get_data(self): self.glb_data = lcl_data
super().get_data() self.glb_last_refresh = time.time()
self.glb_response.update({"world_dx_spots_live": self.glb_data}) threading.Timer(5 * 60, self.refresh).start() # periodic refresh: set time
return
return self.glb_response
def get_data(self):
super().get_data()
self.glb_response.update({"world_dx_spots_live": self.glb_data})
return self.glb_response

View File

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

View File

@ -1,15 +1,20 @@
# #
# little script used to build static pages # little script used to build static pages
# #
__author__ = 'IU1BOW - Corrado' __author__ = "IU1BOW - Corrado"
from staticjinja import Site from staticjinja import Site
def cookies_check(): def cookies_check():
return False return False
if __name__ == "__main__": if __name__ == "__main__":
site = Site.make_site(searchpath="../static/html/templates/",outpath="../static/html/",env_globals={ site = Site.make_site(
'cookies_check':cookies_check, searchpath="../static/html/dev/",
}) outpath="../static/html/rel/",
site.render(use_reloader=False) 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 charset-normalizer==2.1.1
click==8.1.3 click==8.1.3
dill==0.3.6
docopt-ng==0.8.1 docopt-ng==0.8.1
easywatch==0.0.5 easywatch==0.0.5
Flask==2.2.2 Flask==2.2.2
@ -7,14 +9,18 @@ Flask-Minify==0.41
Flask-WTF==1.0.1 Flask-WTF==1.0.1
htmlmin==0.1.12 htmlmin==0.1.12
idna==3.4 idna==3.4
isort==5.11.4
itsdangerous==2.1.2 itsdangerous==2.1.2
Jinja2==3.1.2 Jinja2==3.1.2
jsmin==3.0.1 jsmin==3.0.1
lazy-object-proxy==1.9.0
lesscpy==0.15.1 lesscpy==0.15.1
MarkupSafe==2.1.1 MarkupSafe==2.1.1
mccabe==0.7.0
mysql-connector-python==8.0.31 mysql-connector-python==8.0.31
numpy==1.24.1 numpy==1.24.1
pandas==1.5.2 pandas==1.5.2
platformdirs==2.6.2
ply==3.11 ply==3.11
protobuf==4.21.12 protobuf==4.21.12
python-dateutil==2.8.2 python-dateutil==2.8.2
@ -22,8 +28,10 @@ pytz==2022.7
rcssmin==1.1.1 rcssmin==1.1.1
requests==2.28.1 requests==2.28.1
six==1.16.0 six==1.16.0
tomlkit==0.11.6
urllib3==1.26.13 urllib3==1.26.13
watchdog==2.2.0 watchdog==2.2.0
Werkzeug==2.2.2 Werkzeug==2.2.2
wrapt==1.14.1
WTForms==3.0.1 WTForms==3.0.1
xxhash==3.1.0 xxhash==3.1.0

View File

@ -8,13 +8,141 @@ path_static='../static'
path_static_html=${path_static}'/html' path_static_html=${path_static}'/html'
path_static_js=${path_static}'/js' path_static_js=${path_static}'/js'
path_static_css=${path_static}'/css' path_static_css=${path_static}'/css'
path_cfg='../cfg'
app_ini=${path_cfg}'/webapp_log_config.ini'
path_docs='../docs' path_docs='../docs'
readme='../README.md' readme='../README.md'
manifest=${path_static}'/manifest.webmanifest' manifest=${path_static}'/manifest.webmanifest'
changelog=${path_docs}'/'CHANGELOG.md 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' #echo 'create modes.json from dxspider bands.pl'
#python ../lib/get_dxcluster_modes.py /home/sysop/spider/data/bands.pl #python ../lib/get_dxcluster_modes.py /home/sysop/spider/data/bands.pl
#if [ "$?" != "0" ]; then #if [ "$?" != "0" ]; then
# echo 'ERROR on creating modes.json from dxspider bands.pl' # echo 'ERROR on creating modes.json from dxspider bands.pl'
@ -48,14 +176,14 @@ then
fi fi
echo 'writing version in '${changelog} '...' echo 'writing version in '${changelog} '...'
if ! sed -i '1,4s/Release: v.*/Release: '$ver'/g' ${changelog} if ! sed -i '1,4s/Release: v.*/Release: '$ver'/g' ${changelog}
then then
echo 'ERROR writing version in '${changelog} echo 'ERROR writing version in '${changelog}
exit 35 exit 35
fi fi
echo 'writing date in '${changelog} '...' 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} 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 then
echo 'ERROR writing date in '${changelog} echo 'ERROR writing date in '${changelog}
@ -76,58 +204,5 @@ then
exit 50 exit 50
fi 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
echo Build ok 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

@ -2,11 +2,11 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>DX Cluster from IU1BOW: OFFLINE</title> <title>DX Cluster from IU1BOW: OFFLINE</title>
<!-- page generated by staticjinja --> <!-- page generated by staticjinja -->
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="description" content="Web Ham Radio DX Cluster and spot search"> <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="keywords" content="ham radio, dx cluster, dx spots, cluster sposts,web dx cluster,dx cluster search, DX spots">
@ -16,7 +16,7 @@
<link rel="icon" href="/static/images/icons/spider_ico_master.svg" type="image/svg+xml"> <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="apple-touch-icon" href="/static/images/icons/icon-apple.png">
<link rel="manifest" href="/static/manifest.webmanifest"> <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' "> <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> <noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css"></noscript>
@ -24,14 +24,14 @@
<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'" > <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> <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> </head>
<body> <body>
<header> <header>
<!-- nav bar --> <!-- nav bar -->
<nav class="navbar px-2 navbar-expand-lg navbar-dark bg-dark"> <nav class="navbar px-2 navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid"> <div class="container-fluid">
@ -47,43 +47,44 @@
</ul> </ul>
<div id="MyClockDisplay" onload="showTime()" class="text-white-50 d-none d-lg-block"></div> <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> </div>
</div> </div>
</nav> </nav>
</header> </header>
<div class="container-fluid mt-1 ml-0 mr-0 px-0"> <div class="container-fluid mt-1 ml-0 mr-0 px-0">
<div class="ipcs bg-light p-4 rounded-lg m-2"> <div class="ipcs bg-light p-4 rounded-lg m-2">
<h1 class="display-4 text-white">WEB DX Cluster</h1> <h1 class="display-4 text-white">WEB DX Cluster</h1>
<p class="lead text-light">Spots list</p> <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">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> <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>
<!-- <div class="container"> <!-- <div class="container">
<div class="row"> <div class="row">
<div class="col align-self-center"> --> <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> <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
<p class="lead"> Internet</p>
<a class="btn btn-primary btn-lg" href="/" role="button">Try again</a> <p class="lead">
</p> <a class="btn btn-primary btn-lg" href="/" role="button">Try again</a>
<!-- </div> </p>
<!-- </div>
</div> </div>
</div> --> </div> -->
</div> </div>
</div> </div>
<footer class="page-footer font-small blue"> <footer class="page-footer font-small blue">
<div class="footer-copyright text-center py-3"> <div class="footer-copyright text-center py-3">
@ -93,40 +94,43 @@
<span id="version">v2.4.1</span> <span id="version">v2.4.1</span>
</div> </div>
</footer> </footer>
<script async src="static/js/clock.min.js"></script> <script async src="static/js/dev/clock.js"></script>
<script async src="static/js/copy_date.min.js"></script> <script async src="static/js/dev/copy_date.js"></script>
<script async src="static/js/load-sw.min.js"></script> <script async src="static/js/dev/load-sw.js"></script>
<script nonce="sedfGFG32xs"> <script nonce="sedfGFG32xs">
</script> </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 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 --> <!-- cookie consent management -->
<div class="modal" tabindex="-1" id="cookie-consent-container">
<div class="modal-dialog"> <!-- Modal for cookie consent-->
<div class="modal-content"> <div class="modal fade" id="cookie_consent_modal" tabindex="-1" aria-labelledby="cookie-consent-container" aria-hidden="true">
<div class="modal-header"> <div class="modal-dialog">
<h1 class="modal-title">We use cookies</h1> <div class="modal-content">
</div> <div class="modal-header">
<div class="modal-body"> <h5 class="modal-title" id="exampleModalLabel">We use cookies</h5>
<p>We use only technical cookies.</p> </div>
<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 class="modal-body">
</div> <p>We use only technical cookies.</p>
<div class="modal-footer"> <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>
<button type="button" class="btn btn-primary" id="cookie-consent">I agree</button> </div>
</div> <div class="modal-footer">
</div> <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> </div>
</div>
<script defer src="static/js/dev/cookie_consent.js"></script>
</body> </body>
</html> </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 - single
semi: semi:
- error - 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,14 +1,14 @@
/* /*
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'), { let cookie_modal = new bootstrap.Modal(document.getElementById('cookie_consent_modal'), {
keyboard: false keyboard: false
}) });
cookie_modal.show(); cookie_modal.show();
//if button is pressed, setting cookie //if button is pressed, setting cookie
document.getElementById('cookie_consent_btn').onclick = function(){ document.getElementById('cookie_consent_btn').onclick = function(){
setCookie('cookie_consent',true,30); setCookie('cookie_consent',true,30);
cookie_modal.hide(); cookie_modal.hide();
}; };

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 * 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=jQuery.parseJSON(my_adxo_events_json.replaceAll("\t",""));
var my_adxo_events=JSON.parse(my_adxo_events_json.replaceAll("\t","")); var my_adxo_events=JSON.parse(my_adxo_events_json.replaceAll('\t',''));
var rows_list = new Array();
refresh_timer(); //run first data fetch refresh_timer(); //run first data fetch
var myRefresh = setInterval(refresh_timer, timer_interval_json); 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 */ /*! loadCSS. [c]2020 Filament Group, Inc. MIT License */
(function(w){ (function(w){
"use strict"; 'use strict';
/* exported loadCSS */ /* exported loadCSS */
var loadCSS = function( href, before, media, attributes ){ var loadCSS = function( href, before, media, attributes ){
// Arguments explained: // Arguments explained:
@ -10,13 +10,13 @@
// `media` [OPTIONAL] is the media type or query of the stylesheet. By default it will be 'all' // `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. // `attributes` [OPTIONAL] is the Object of attribute name/attribute value pairs to set on the stylesheet's DOM Element.
var doc = w.document; var doc = w.document;
var ss = doc.createElement( "link" ); var ss = doc.createElement( 'link' );
var ref; var ref;
if( before ){ if( before ){
ref = before; ref = before;
} }
else { else {
var refs = ( doc.body || doc.getElementsByTagName( "head" )[ 0 ] ).childNodes; var refs = ( doc.body || doc.getElementsByTagName( 'head' )[ 0 ] ).childNodes;
ref = refs[ refs.length - 1]; ref = refs[ refs.length - 1];
} }
@ -29,10 +29,10 @@
} }
} }
} }
ss.rel = "stylesheet"; ss.rel = 'stylesheet';
ss.href = href; ss.href = href;
// temporarily set media to something inapplicable to ensure it'll fetch without blocking render // 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. // wait until body is defined before injecting link. This ensures a non-blocking load in IE11.
function ready( cb ){ function ready( cb ){
@ -44,8 +44,8 @@
}); });
} }
// Inject link // Inject link
// Note: the ternary preserves the existing behavior of "before" argument, but we could choose to change the argument to "after" in a later release and standardize on ref.nextSibling for all refs // Note: the ternary preserves the existing behavior of "before" argument, but we could choose to change the argument to "after" in a later release and standardize on ref.nextSibling for all refs
// Note: `insertBefore` is used instead of `appendChild`, for safety re: http://www.paulirish.com/2011/surefire-dom-element-insertion/ // Note: `insertBefore` is used instead of `appendChild`, for safety re: http://www.paulirish.com/2011/surefire-dom-element-insertion/
ready( function(){ ready( function(){
ref.parentNode.insertBefore( ss, ( before ? ref : ref.nextSibling ) ); ref.parentNode.insertBefore( ss, ( before ? ref : ref.nextSibling ) );
}); });
@ -65,24 +65,24 @@
function loadCB(){ function loadCB(){
if( ss.addEventListener ){ 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 // once loaded, set link's media back to `all` so that the stylesheet applies once it loads
if( ss.addEventListener ){ if( ss.addEventListener ){
ss.addEventListener( "load", loadCB); ss.addEventListener( 'load', loadCB);
} }
ss.onloadcssdefined = onloadcssdefined; ss.onloadcssdefined = onloadcssdefined;
onloadcssdefined( loadCB ); onloadcssdefined( loadCB );
return ss; return ss;
}; };
// commonjs // commonjs
if( typeof exports !== "undefined" ){ if( typeof exports !== 'undefined' ){
exports.loadCSS = loadCSS; exports.loadCSS = loadCSS;
} }
else { else {
w.loadCSS = loadCSS; 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()};

View File

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

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="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="apple-touch-icon" href="/static/images/icons/icon-apple.png">
<link rel="manifest" href="/static/manifest.webmanifest"> <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' "> <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> <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'" > <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> <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 %} {% endblock head %}
</head> </head>
<body> <body>
@ -89,21 +89,18 @@
<span id="version">v2.4.1</span> <span id="version">v2.4.1</span>
</div> </div>
</footer> </footer>
<script async class="spiderscript" src="static/js/clock.min.js"></script> <script async src="static/js/rel/clock.min.js"></script>
<script async class="spiderscript" src="static/js/copy_date.min.js"></script> <script async src="static/js/rel/copy_date.min.js"></script>
<script async class="spiderscript" src="static/js/load-sw.min.js"></script> <script async src="static/js/rel/load-sw.min.js"></script>
<script nonce="sedfGFG32xs"> <script nonce="sedfGFG32xs">
{% block app_data %} {% block app_data %}
var my_callsign='{{callsign}}'; var my_callsign='{{callsign}}';
{% endblock app_data %} {% endblock app_data %}
</script> </script>
<script defer class="spiderscript" src="static/js/common.js"></script> <script defer src="static/js/rel/common.min.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="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></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 %} {% 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 %} {% endblock app_scripts %}
{% block inline_scripts %} {% block inline_scripts %}
{% endblock inline_scripts %} {% endblock inline_scripts %}
@ -131,7 +128,7 @@
</div> </div>
</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 %} {% endif %}
</body> </body>
</html> </html>

View File

@ -1,16 +1,17 @@
{% extends "index.html" %} {% extends "index.html" %}
<head> <head>
{% block title %} {% block title %}
<title>Spot search for a specific Callsign</title> <title>Spot search for a specific Callsign</title>
{% endblock %} {% endblock %}
</head> </head>
{% block titles %} {% block titles %}
<h1 class="display-4 text-white">{{callsign}}</h1> <h1 class="display-4 text-white">{{callsign}}</h1>
<p class="lead text-light">Some statistics about this callsign</p> <p class="lead text-light">Some statistics about this callsign</p>
{% endblock %} {% endblock %}
{% block filters %} {% block filters %}
<div class="row mx-auto"> <div class="row mx-auto">
{% endblock filters %} {% endblock filters %}
{% block inline_scripts %} {% 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 %} {% 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,33 +1,47 @@
{% extends "_base.html" %} {% extends "_base.html" %}
<head> <head>
{% block title %} {% block title %}
<title>DX Cluster from IU1BOW: Cookies</title> <title>DX Cluster from IU1BOW: Cookies</title>
{% endblock %} {% endblock %}
{% block head %} {% block head %}
{{ super() }} {{ super() }}
{% endblock %} {% endblock %}
</head> </head>
{% block titles %} {% block titles %}
<h1 class="display-4 text-white">COOKIES</h1> <h1 class="display-4 text-white">COOKIES</h1>
<p class="lead text-light">WEB DX Cluster For HAM Radio</p> <p class="lead text-light">WEB DX Cluster For HAM Radio</p>
{% endblock %} {% endblock %}
{% block filters %} {% block filters %}
{% endblock %} {% endblock %}
{% block contents %} {% block contents %}
<div class="col mr-3 px-2"> <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 <p class="text-justify"><span class="font-weight-bold">Cookies</span> are small text files that can be used by
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 websites to make a user's experience more efficient. This site uses different types of cookies. You can at any time
please check the websites of these third parties for more information about their cookies and how they manage them.</p> change or withdraw
<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 your consent from the Cookies page on my website. Some cookies are placed by third party services that appear on our
these cookies.</p> pages, for example if you view or listen to any embedded audio or video content. I don't control the setting of
<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> these cookies, so
<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> please check the websites of these third parties for more information about their cookies and how they manage them.
<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>
valuable for publishers and third party advertisers.</p> <p class="text-justify"><span class="font-weight-bold">Necessary</span> cookies help make a website usable by enabling
<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> basic functions like page navigation and access to secure areas of the website. The website cannot function properly
</div> without
{% endblock %} these cookies.</p>
{% block app_data %} <p class="text-justify"><span class="font-weight-bold">Preference</span> cookies enable a website to remember
{% endblock %} information that changes the way the website behaves or looks, like your preferred language or the region that you
{% block app_scritps %} are in.</p>
{% endblock %} <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 %}

View File

@ -1,210 +1,211 @@
{% extends "_base.html" %} {% extends "_base.html" %}
<head> <head>
{% block title %} {% block title %}
<title>DX Cluster / DX Spot for Hamradio</title> <title>DX Cluster / DX Spot for Hamradio</title>
{% endblock %} {% endblock %}
{% block head %} {% block head %}
{{ super() }} {{ super() }}
{% endblock %} {% endblock %}
</head> </head>
{% block titles %} {% block titles %}
<h1 class="display-4 text-white">WEB DX Cluster</h1> <h1 class="display-4 text-white">WEB DX Cluster</h1>
<p class="lead text-light">Spots list</p> <p class="lead text-light">Spots list</p>
{% endblock %} {% endblock %}
{% block filters %} {% block filters %}
<div class="row mx-auto justify-content-between align-middle"> <div class="row mx-auto justify-content-between align-middle">
<div class="mx-auto"> <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"
<span class="bi-funnel-fill" role="button" aria-label="funnel-fill"></span> aria-label="filter" data-bs-toggle="collapse" data-bs-target="#collapseFilters">
</button> <span class="bi-funnel-fill" role="button" aria-label="funnel-fill"></span>
</button>
</div>
</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() }}" />
<strong>Band</strong>
<select class="form-select overflow-hidden" id="band" size="14" multiple>
{% for dict_item in bands['bands']|reverse %}
<option selected value="{{dict_item['id']}}">{{dict_item["id"]}}</option>
{% endfor %}
</select>
<p></p>
<div class="row">
<strong>Mode</strong>
</div> </div>
</div> <select class="form-select overflow-hidden" id="mode" size="3" multiple>
<div class="row mx-auto"> <option selected value="cw">CW</option>
<!--Sidebar content--> <option selected value="phone">PHONE</option>
<div class="col-md-auto ml-2 collapse rounded-sm shadow mb-5 bg-body" id="collapseFilters"> <option selected value="digi">DIGI</option>
<form method="POST" id="form-filters" enctype="multipart/form-data" > </select>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <p></p>
<strong>Band</strong> <div class="row">
<select class="form-select overflow-hidden" id="band" size="14" multiple> <div class="col">
{% for dict_item in bands['bands']|reverse %} <strong>De</strong>
<select class="form-select overflow-hidden" id="de_re" size="7" multiple>
{% for dict_item in continents['continents'] %}
<option selected value="{{dict_item['id']}}">{{dict_item["id"]}}</option> <option selected value="{{dict_item['id']}}">{{dict_item["id"]}}</option>
{% endfor %} {% endfor %}
</select> </select>
<p></p> </div>
<div class="row"> <div class="col">
<strong>Mode</strong> <strong>Dx</strong>
</div> <select class="form-select overflow-hidden" id="dx_re" size="7" multiple>
<select class="form-select overflow-hidden" id="mode" size="3" multiple> {% for dict_item in continents['continents'] %}
<option selected value="cw">CW</option> <option selected value="{{dict_item['id']}}">{{dict_item["id"]}}</option>
<option selected value="phone">PHONE</option> {% endfor %}
<option selected value="digi">DIGI</option>
</select> </select>
<p></p> </div>
<div class="row">
<div class="col">
<strong>De</strong>
<select class="form-select overflow-hidden" id="de_re" size="7" multiple>
{% for dict_item in continents['continents'] %}
<option selected value="{{dict_item['id']}}">{{dict_item["id"]}}</option>
{% endfor %}
</select>
</div>
<div class="col">
<strong>Dx</strong>
<select class="form-select overflow-hidden" id="dx_re" size="7" multiple>
{% for dict_item in continents['continents'] %}
<option selected value="{{dict_item['id']}}">{{dict_item["id"]}}</option>
{% endfor %}
</select>
</div>
</div>
<p></p>
<!-- cq filter -->
{% if enable_cq_filter == "Y" %}
<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>
</div>
</div>
<p></p>
<div class="row">
<strong>CQ Dx zone</strong>
<div class="col">
<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>
</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>
</div>
</form>
</div> </div>
<p></p>
<!-- cq filter -->
{% if enable_cq_filter == "Y" %}
<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>
</div>
</div>
<p></p>
<div class="row">
<strong>CQ Dx zone</strong>
<div class="col">
<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>
</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>
</div>
</form>
</div>
{% endblock filters %} {% endblock filters %}
<!-- table --> <!-- table -->
{% block contents %} {% block contents %}
<div class="col"> <div class="col">
<table id="spotsTable" class="table table-striped table-sm text-responsive table-borderless table-hover"> <table id="spotsTable" class="table table-striped table-sm text-responsive table-borderless table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col">DE</th> <th scope="col">DE</th>
<th scope="col">Freq</th> <th scope="col">Freq</th>
<th scope="col">DX</th> <th scope="col">DX</th>
<th scope="col"></th> <th scope="col"></th>
<th scope="col" class="d-none d-lg-table-cell d-xl-table-cell">Country</th> <th scope="col" class="d-none d-lg-table-cell d-xl-table-cell">Country</th>
<th scope="col" class="d-none d-lg-table-cell d-xl-table-cell">Comments</th> <th scope="col" class="d-none d-lg-table-cell d-xl-table-cell">Comments</th>
<th scope="col">UTC</th> <th scope="col">UTC</th>
</tr> </tr>
</thead> </thead>
<tbody id="bodyspot"> <tbody id="bodyspot">
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
{% endblock contents %} {% endblock contents %}
{% block app_data %} {% block app_data %}
{{ super() }} {{ 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 %}
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,40 +1,42 @@
{% extends "_base.html" %} {% extends "_base.html" %}
<head> <head>
{% block title %} {% block title %}
<title>DX Cluster from IU1BOW: OFFLINE</title> <title>DX Cluster from IU1BOW: OFFLINE</title>
<!-- page generated by staticjinja --> <!-- page generated by staticjinja -->
{% endblock %} {% endblock %}
{% block head %} {% block head %}
{{ super() }} {{ super() }}
{% endblock %} {% endblock %}
</head> </head>
{% block menu %} {% block menu %}
{{ super() }} {{ super() }}
{% block callsign %} {% block callsign %}
{% endblock callsign %} {% endblock callsign %}
{% endblock menu %} {% endblock menu %}
{% block titles %} {% block titles %}
<h1 class="display-4 text-white">WEB DX Cluster</h1> <h1 class="display-4 text-white">WEB DX Cluster</h1>
<p class="lead text-light">Spots list</p> <p class="lead text-light">Spots list</p>
{% endblock %} {% endblock %}
{% block filters %} {% block filters %}
{% endblock %} {% endblock %}
{% block contents %} {% block contents %}
<!-- <div class="container"> <!-- <div class="container">
<div class="row"> <div class="row">
<div class="col align-self-center"> --> <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> <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
<p class="lead"> Internet</p>
<a class="btn btn-primary btn-lg" href="/" role="button">Try again</a> <p class="lead">
</p> <a class="btn btn-primary btn-lg" href="/" role="button">Try again</a>
<!-- </div> </p>
<!-- </div>
</div> </div>
</div> --> </div> -->
</div> </div>
{% endblock %} {% endblock %}
{% block app_data %} {% block app_data %}
{% endblock %} {% endblock %}
{% block app_scritps %} {% block app_scritps %}
{% endblock %} {% endblock %}

View File

@ -1,100 +1,101 @@
{% extends "_base.html" %} {% extends "_base.html" %}
<head> <head>
{% block title %} {% block title %}
<title>Some charts end stats from the dx clustes node</title> <title>Some charts end stats from the dx clustes node</title>
{% endblock %} {% endblock %}
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<meta http-equiv="refresh" content="300"> <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 %} {% endblock %}
</head> </head>
{% block titles %} {% block titles %}
<h1 class="display-4 text-white">PLOTS & STATS</h1> <h1 class="display-4 text-white">PLOTS & STATS</h1>
<p class="lead text-light">Some statistics about this node</p> <p class="lead text-light">Some statistics about this node</p>
{% endblock %} {% endblock %}
{% block filters %} {% block filters %}
{% endblock %} {% endblock %}
{% block contents %} {% 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="shadow-lg bg-body mb-5 rounded" id="form-band_activity">
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<form method="POST" id="form-continents" enctype="multipart/form-data" > <form method="POST" id="form-continents" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<div class="container"> <div class="container">
Your continent is: Your continent is:
<select class="form-select flex-shrink" aria-label="continent" id="continentInput"> <select class="form-select flex-shrink" aria-label="continent" id="continentInput">
{% for dict_item in continents['continents']%} {% 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 %} {% endfor %}
</select> </select>
</div> </div>
</form> </form>
<div id="chart-band_activity"></div> <div id="chart-band_activity"></div>
</div>
<small><sup id="txt_continent"></sup></small>
</div>
<div class="shadow-lg mb-5 bg-body rounded" id="chart-world_dx_spots_live"></div>
<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">
</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>
<hr>
<table class="table table-striped table-borderless table-sm text-responsive table-hover">
<thead id="telnet-thead">
<tr>
<th scope="col">Callsign</th>
<th scope="col">Type</th>
<th scope="col">Started</th>
<th scope="col" class="d-none d-lg-table-cell d-xl-table-cell">Name</th>
<th scope="col">Avg RTT</th>
</tr>
</thead>
<tbody>
{% for dict_item in who %}
<tr>
<td>{{dict_item["callsign"]}}</td>
<td>{{dict_item["type"]}}</td>
<td>{{dict_item["started"]}}</td>
<td class="d-none d-lg-table-cell d-xl-table-cell">{{dict_item["name"]}}</td>
<td>{{dict_item["average_rtt"]}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
<small><sup id="txt_continent"></sup></small>
</div>
<div class="shadow-lg mb-5 bg-body rounded" id="chart-world_dx_spots_live"></div>
<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">
</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>
<hr>
<table class="table table-striped table-borderless table-sm text-responsive table-hover">
<thead id="telnet-thead">
<tr>
<th scope="col">Callsign</th>
<th scope="col">Type</th>
<th scope="col">Started</th>
<th scope="col" class="d-none d-lg-table-cell d-xl-table-cell">Name</th>
<th scope="col">Avg RTT</th>
</tr>
</thead>
<tbody>
{% for dict_item in who %}
<tr>
<td>{{dict_item["callsign"]}}</td>
<td>{{dict_item["type"]}}</td>
<td>{{dict_item["started"]}}</td>
<td class="d-none d-lg-table-cell d-xl-table-cell">{{dict_item["name"]}}</td>
<td>{{dict_item["average_rtt"]}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
</div> </div>
{% endblock contents %} </div>
{% endblock contents %}
{% block app_data %}
{{ super() }} {% block app_data %}
var continents_cq={{continents["continents"]|tojson|safe}}; {{ super() }}
var band_frequencies={{bands["bands"]|tojson|safe}}; var continents_cq={{continents["continents"]|tojson|safe}};
{% endblock app_data %} var band_frequencies={{bands["bands"]|tojson|safe}};
{% endblock app_data %}
{% block app_scripts %}
{{ super() }} {% block app_scripts %}
<script defer class="spiderscript" src="static/js/plot_band_activity.js"></script> {{ super() }}
<script defer class="spiderscript" src="static/js/plot_world_dx_spots_live.js"></script> <script defer src="static/js/rel/plot_band_activity.min.js"></script>
<script defer class="spiderscript" src="static/js/plot_hour_band.js"></script> <script defer src="static/js/rel/plot_world_dx_spots_live.min.js"></script>
<script defer class="spiderscript" src="static/js/plot_dx_spots_trend.js"></script> <script defer src="static/js/rel/plot_hour_band.min.js"></script>
<script defer class="spiderscript" src="static/js/plot_dx_spots_per_month.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,75 +1,120 @@
{% extends "_base.html" %} {% extends "_base.html" %}
<head> <head>
{% block title %} {% block title %}
<title>DX Cluster from IU1BOW: Privacy</title> <title>DX Cluster from IU1BOW: Privacy</title>
{% endblock %} {% endblock %}
{% block head %} {% block head %}
{{ super() }} {{ super() }}
{% endblock %} {% endblock %}
</head> </head>
{% block titles %} {% block titles %}
<h1 class="display-4 text-white">PRIVACY</h1> <h1 class="display-4 text-white">PRIVACY</h1>
<p class="lead text-light">WEB DX Cluster For HAM Radio</p> <p class="lead text-light">WEB DX Cluster For HAM Radio</p>
{% endblock %} {% endblock %}
{% block filters %} {% block filters %}
{% endblock %} {% endblock %}
{% block contents %} {% block contents %}
<div class="col mr-3 px-2"> <div class="col mr-3 px-2">
<h1>Privacy Policy for this web site</h1> <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>At this web site, one of our main priorities is the privacy of our visitors. This Privacy Policy document
<p>If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact us.</p> contains types of information that is collected and recorded by this web site and how we use it.</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> <p>If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact
<h2>Consent</h2> us.</p>
<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> <p>This Privacy Policy applies only to our online activities and is valid for visitors to our website with regards
<h2>Information we collect</h2> to the information that they shared and/or collect in this web site. This policy is not applicable to any
<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> information collected offline or via channels other than this website. Our Privacy Policy was created with the
<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> help of the <a href="https://www.privacypolicygenerator.info">Privacy Policy Generator</a> and the <a
<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> href="https://www.generateprivacypolicy.com/">Free Privacy Policy Generator</a>.</p>
<h2>How we use your information</h2> <h2>Consent</h2>
<p>We use the information we collect in various ways, including to:</p> <p>By using our website, you hereby consent to our Privacy Policy and agree to its terms. For our Terms and
<ul> Conditions, please visit the <a href="https://www.privacypolicyonline.com/terms-conditions-generator/">Terms &
<li>Provide, operate, and maintain our webste</li> Conditions Generator</a>.</p>
<li>Improve, personalize, and expand our webste</li> <h2>Information we collect</h2>
<li>Understand and analyze how you use our webste</li> <p>The personal information that you are asked to provide, and the reasons why you are asked to provide it, will be
<li>Develop new products, services, features, and functionality</li> made clear to you at the point we ask you to provide your personal information.</p>
<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> <p>If you contact us directly, we may receive additional information about you such as your name, email address,
<li>Send you emails</li> phone number, the contents of the message and/or attachments you may send us, and any other information you may
<li>Find and prevent fraud</li> choose to provide.</p>
</ul> <p>When you register for an Account, we may ask for your contact information, including items such as name, company
<h2>Log Files</h2> name, address, email address, and telephone number.</p>
<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>How we use your information</h2>
<h2>Cookies and Web Beacons</h2> <p>We use the information we collect in various ways, including to:</p>
<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> <ul>
<p>For more general information on cookies, please read <a href="https://www.privacypolicies.com/blog/cookies/">"What Are Cookies"</a>.</p> <li>Provide, operate, and maintain our webste</li>
<h2>Advertising Partners Privacy Policies</h2> <li>Improve, personalize, and expand our webste</li>
<P>You may consult this list to find the Privacy Policy for each of the advertising partners of this web site.</p> <li>Understand and analyze how you use our webste</li>
<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> <li>Develop new products, services, features, and functionality</li>
<p>Note that this web site has no access to or control over these cookies that are used by third-party advertisers.</p> <li>Communicate with you, either directly or through one of our partners, including for customer service, to
<h2>Third Party Privacy Policies</h2> provide you with updates and other information relating to the webste, and for marketing and promotional
<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> purposes</li>
<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> <li>Send you emails</li>
<h2>CCPA Privacy Rights (Do Not Sell My Personal Information)</h2> <li>Find and prevent fraud</li>
<p>Under the CCPA, among other rights, California consumers have the right to:</p> </ul>
<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> <h2>Log Files</h2>
<p>Request that a business delete any personal data about the consumer that a business has collected.</p> <p>this web site follows a standard procedure of using log files. These files log visitors when they visit websites.
<p>Request that a business that sells a consumer's personal data, not sell the consumer's personal data.</p> All hosting companies do this and a part of hosting services' analytics. The information collected by log files
<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> include internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp,
<h2>GDPR Data Protection Rights</h2> referring/exit pages, and possibly the number of clicks. These are not linked to any information that is
<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> personally identifiable. The purpose of the information is for analyzing trends, administering the site,
<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> tracking users' movement on the website, and gathering demographic information.</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> <h2>Cookies and Web Beacons</h2>
<p>The right to erasure You have the right to request that we erase your personal data, under certain conditions.</p> <p>Like any other website, this web site uses 'cookies'. These cookies are used to store information including
<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> visitors' preferences, and the pages on the website that the visitor accessed or visited. The information is
<p>The right to object to processing You have the right to object to our processing of your personal data, under certain conditions.</p> used to optimize the users' experience by customizing our web page content based on visitors' browser type
<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> and/or other information.</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> <p>For more general information on cookies, please read <a
<h2>Children's Information</h2> href="https://www.privacypolicies.com/blog/cookies/">"What Are Cookies"</a>.</p>
<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> <h2>Advertising Partners Privacy Policies</h2>
<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> <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> </div>
{% endblock %} {% endblock %}
{% block app_data %} {% block app_data %}
{% endblock %} {% endblock %}
{% block app_scritps %} {% block app_scritps %}
{% endblock %} {% endblock %}

21
test.sh
View File

@ -1,8 +1,21 @@
if [ "$1" == "-b" ]; then if [ $# -gt 0 ]
then
cd scripts || exit cd scripts || exit
./build.sh if ! ./build.sh ${1}
then
cd ..
echo "terminated"
exit 1
fi
cd .. cd ..
fi 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

496
webapp.py
View File

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

View File

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