oplog rc2-final

This commit is contained in:
Max 2021-07-25 20:47:32 -04:00
parent 51042858e8
commit 806d7c44ca
40 changed files with 43127 additions and 0 deletions

View File

@ -0,0 +1,4 @@
import os
SQLALCHEMY_DATABASE_URI = 'sqlite:///%s/../op25-data.db' % (os.path.dirname(__file__))
SQLALCHEMY_TRACK_MODIFICATIONS = False

View File

@ -0,0 +1,841 @@
#! /usr/bin/env python
# Copyright 2021 Max H. Parke KA1RBI, Michael Rose
#
# This file is part of OP25
#
# OP25 is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# OP25 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with OP25; see the file COPYING. If not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
# 02110-1301, USA.
import time
from time import sleep
from time import gmtime, strftime
import os
from os import listdir
from os.path import isfile, join
import sys
import traceback
import math
import json
import click
import datetime
from datatables import ColumnDT, DataTables
from flask import Flask, jsonify, render_template, request, redirect, session
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import func, desc, and_, or_, case, delete, insert, update, exc
from sqlalchemy.orm import Query
from sqlalchemy.exc import OperationalError
import sqlalchemy.types as types
from shutil import copyfile
sys.path.append('..') # for emap
from emap import oplog_map, cc_events, cc_desc
app = Flask(__name__)
app.config.from_pyfile("../app.cfg")
app.config['SQLALCHEMY_ECHO'] = False # set to True to send sql statements to the console
# enables session variables to be used
app.secret_key = b'kH8HT0ucrh' # random bytes - this key not used anywhere else
db = SQLAlchemy(app)
try:
db.reflect(app=app)
db.init_app(app)
except OperationalError as e:
raise(e) # database is locked by another process
class MyDateType(types.TypeDecorator):
impl = types.REAL
def process_result_value(self, value, dialect):
return datetime.datetime.fromtimestamp(value).strftime('%Y-%m-%d %H:%M:%S')
class column_helper(object):
"""
convenience class - enables columns to be referenced as
for example, Foo.bar instead of Foo['bar']
"""
def __init__(self, table):
self.table_ = db.metadata.tables[table]
cols = self.table_.columns
for k in cols.keys():
setattr(self, k, cols[k])
def dbstate():
database = app.config['SQLALCHEMY_DATABASE_URI'][10:]
if not os.path.isfile(database):
return 1 # db file does not exist
fs = os.path.getsize(database)
if fs < 1024:
return 2 # file size too small
DataStore = column_helper('data_store')
rows = db.session.query(DataStore.id).count()
if rows < 1:
return 4 # no rows present
return 0
# clears the sm (successMessage) after being used in jinja
def clear_sm():
session['sm'] = 0
return '' #must be an empty string or 'None' is displayed in the template
def t_gmt():
t = time.strftime("%a, %d %b %Y %H:%M:%S", time.gmtime())
return t
def t_loc():
t = strftime("%a, %d %b %Y %H:%M:%S %Z")
return t
# make these functions available to jinja
app.jinja_env.globals.update(t_gmt=t_gmt)
app.jinja_env.globals.update(t_loc=t_loc)
app.jinja_env.globals.update(clear_sm=clear_sm)
# for displaying the db file size, shamelessly stolen from SO
def convert_size(size_bytes):
if size_bytes == 0:
return "0 B"
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
i = int(math.floor(math.log(size_bytes, 1024)))
p = math.pow(1024, i)
s = round(size_bytes / p, 2)
return "%s %s" % (s, size_name[i])
def dbStats():
DataStore = column_helper('data_store')
DataStore = column_helper('data_store')
SysIDTags = column_helper('sysid_tags')
DataStore.time.type = MyDateType()
rows = db.session.query(func.count(DataStore.id)).scalar()
if rows == 0:
return(0, 0, 0, 0, 0, 0, 0)
sys_count = db.session.query(DataStore.sysid) \
.distinct(DataStore.sysid) \
.group_by(DataStore.sysid) \
.filter(DataStore.sysid != 0) \
.count()
# TODO: talkgroups and subs should be distinct by system
talkgroups = db.session.query(DataStore.tgid) \
.distinct(DataStore.tgid) \
.group_by(DataStore.tgid) \
.count()
subs = db.session.query(DataStore.suid) \
.distinct(DataStore.suid) \
.group_by(DataStore.suid) \
.count()
firstRec = db.session.query(DataStore.time, func.min(DataStore.time)).scalar()
lastRec = db.session.query(DataStore.time, func.max(DataStore.time)).scalar()
f = app.config['SQLALCHEMY_DATABASE_URI'][10:] # db file name
dbsize = convert_size(os.path.getsize(f))
return(rows, sys_count, talkgroups, subs, firstRec, lastRec, dbsize, f)
def sysList():
DataStore = column_helper('data_store')
rows = db.session.query(func.count(DataStore.id)).scalar()
if rows == 0:
return []
SysIDTags = column_helper('sysid_tags')
sysList = db.session.query(DataStore.sysid, SysIDTags.tag.label('tag')) \
.distinct(DataStore.sysid) \
.outerjoin(SysIDTags.table_, SysIDTags.sysid == DataStore.sysid) \
.filter(DataStore.sysid != 0)
return sysList
def read_tsv(filename): # used by import_tsv and inspect_tsv, careful w/ changes
rows = []
with open(filename, 'r') as f:
lines = f.read().rstrip().split('\n')
for i in range(len(lines)):
a = lines[i].split('\t')
if i == 0: # check hdr
if not a[0].strip().isdigit():
continue
if not a[0].strip().isdigit(): # check each a[0] for wildcards and skip (continue) if wildcards found
continue
rid = int(a[0])
tag = a[1]
priority = 0 if len(a) < 3 else int(a[2])
s = (rid, tag, priority)
rows.append(s)
return rows
def import_tsv(argv):
UnitIDTags = column_helper('unit_id_tags')
TGIDTags = column_helper('tgid_tags')
cmd = argv[1]
filename = argv[2]
sysid = int(argv[3])
if cmd == 'import_tgid':
tbl = TGIDTags
elif cmd == 'import_unit':
tbl = UnitIDTags
else:
print('%s unsupported' % (cmd))
return
rows = read_tsv(filename)
rm = 0 # records matched
nr = 0 # new records
dr = 0 # duplicate records
if len(rows):
for i in rows:
recCount = db.session.query(tbl.table_).where(and_(tbl.rid == i[0], tbl.sysid == argv[3])).count()
if recCount == 1:
# update record
q = update(tbl.table_) \
.where(and_(tbl.rid == i[0], tbl.sysid == argv[3])) \
.values(rid = int(i[0]), sysid = int(argv[3]), tag = i[1], priority = int(i[2]))
db.session.execute(q)
db.session.commit()
rm +=1
elif recCount == 0:
# insert record
q = insert(tbl.table_).values(rid = int(i[0]), sysid = int(argv[3]), tag = i[1], priority = int(i[2]))
db.session.execute(q)
db.session.commit()
nr += 1
else:
# delete all of the duplicates and insert new (duplicates break things)
print('command %s - db error - %s records for %s %s' % (cmd, recCount, i[0], i[1]))
delRec = delete(TGIDTags.table_).where(and_(tbl.rid == i[0], tbl.sysid == argv[3]))
db.session.execute(delRec)
db.session.commit()
q = insert(tbl.table_).values(rid = int(i[0]), sysid = int(argv[3]), tag = i[1], priority = int(i[2]))
db.session.execute(q)
db.session.commit()
dr += 1
return(rm, nr, dr)
@app.route("/")
def home():
ds = dbstate()
if ds is not 0:
return redirect('error?code=%s' % ds)
params = request.args.to_dict()
params['ekeys'] = sorted(oplog_map.keys())
params['cc_desc'] = cc_desc
return render_template("home.html", project="op25", params=params, dbstats=dbStats(), sysList=sysList())
@app.route("/about")
def about():
ds = dbstate()
if ds is not 0:
return redirect('error?code=%s' % ds)
params = request.args.to_dict()
params['ekeys'] = sorted(oplog_map.keys())
params['cc_desc'] = cc_desc
return render_template("about.html", project="op25", params=params, sysList=sysList())
# error page for database errors
@app.route("/error")
def error_page():
params = request.args.to_dict()
params['file'] = app.config['SQLALCHEMY_DATABASE_URI'][10:]
return render_template("error.html", params=params)
# Inspect TSV (import) - returns a table of the tsv for display in a div, accessed by ajax
@app.route("/inspect")
def inspect():
params = request.args.to_dict()
f = os.getcwd() + '/../' + params['file']
i = read_tsv(f)
return render_template("inspect.html", i=i)
# edit and import tags
@app.route("/edit_tags")
def edit_tags():
UnitIDTags = column_helper('unit_id_tags')
TGIDTags = column_helper('tgid_tags')
SysIDTags = column_helper('sysid_tags')
params = request.args.to_dict()
params['ekeys'] = sorted(oplog_map.keys())
if 'cmd' not in params.keys(): # render talkgroup by default
params['cmd'] = 'tgid'
cmd = params['cmd']
session['cmd'] = cmd
systems = db.session.query(SysIDTags.sysid, SysIDTags.tag)
p = os.getcwd() + '/..'
tsvs = []
for root, dirs, files in os.walk(p, topdown=True):
for file in files:
if file.endswith(".tsv") and not file.startswith("._"):
print(os.path.join(root, file))
tsvs.append(os.path.join(root, file))
tsvs.sort()
return render_template("edit_tags.html", params=params, systems=systems, sysList=sysList(), p=p, cmd=cmd, tsvs=tsvs)
# data for tags table editor
@app.route("/edittg")
def edittg():
params = request.args.to_dict()
params['ekeys'] = sorted(oplog_map.keys())
cmd = params['cmd']
sysid = int(params['sysid'])
UnitIDTags = column_helper('unit_id_tags')
TGIDTags = column_helper('tgid_tags')
SysIDTags = column_helper('sysid_tags')
if cmd == 'tgid':
tbl = TGIDTags
if cmd == 'unit':
tbl = UnitIDTags
column_d = {
'tgid': [
ColumnDT(TGIDTags.id),
ColumnDT(TGIDTags.sysid),
ColumnDT(TGIDTags.rid),
ColumnDT(TGIDTags.tag),
ColumnDT(TGIDTags.id)
],
'unit': [
ColumnDT(UnitIDTags.id),
ColumnDT(UnitIDTags.sysid),
ColumnDT(UnitIDTags.rid),
ColumnDT(UnitIDTags.tag),
ColumnDT(UnitIDTags.id)
]
}
q = db.session.query(tbl.id, tbl.sysid, tbl.rid, tbl.tag).order_by(tbl.rid)
if sysid != 0:
q = q.filter(tbl.sysid == sysid)
rowTable = DataTables(params, q, column_d[cmd])
js = jsonify(rowTable.output_result())
return js
#dtd = delete tag data
@app.route("/dtd")
def dtd():
params = request.args.to_dict()
params['ekeys'] = sorted(oplog_map.keys())
cmd = params['cmd']
UnitIDTags = column_helper('unit_id_tags')
TGIDTags = column_helper('tgid_tags')
SysIDTags = column_helper('sysid_tags')
recId = params['id']
if cmd == 'tgid':
tbl = TGIDTags
if cmd == 'unit':
tbl = UnitIDTags
delRec = delete(tbl.table_).where(tbl.id == recId)
db.session.execute(delRec)
db.session.commit()
session['sm'] = 2
return redirect('/edit_tags?cmd=' + cmd)
#utd = update tag data
@app.route("/utd")
def utd():
params = request.args.to_dict()
params['ekeys'] = sorted(oplog_map.keys())
cmd = params['cmd']
UnitIDTags = column_helper('unit_id_tags')
TGIDTags = column_helper('tgid_tags')
SysIDTags = column_helper('sysid_tags')
recId = params['id']
tag = params['tag']
if cmd == 'tgid':
tbl = TGIDTags
if cmd == 'unit':
tbl = UnitIDTags
upRec = update(tbl.table_).where(tbl.id == recId).values(tag=tag)
db.session.execute(upRec)
db.session.commit()
session['sm'] = 1
return redirect('/edit_tags?cmd=' + cmd)
# import tags
@app.route("/itt")
def itt():
params = request.args.to_dict()
params['ekeys'] = sorted(oplog_map.keys())
cmd = params['cmd']
argv = [ None, 'import_' + cmd, os.getcwd() + '/../' + params['file'], params['sysid'] ]
session['imp_results'] = import_tsv(argv)
session['sm'] = 3
return redirect('/edit_tags?cmd=' + cmd)
# delete all talkgroup/subscriber tags
@app.route("/delTags")
def delTags():
params = request.args.to_dict()
params['ekeys'] = sorted(oplog_map.keys())
cmd = params['cmd']
UnitIDTags = column_helper('unit_id_tags')
TGIDTags = column_helper('tgid_tags')
SysIDTags = column_helper('sysid_tags')
sysid = params['sysid']
if cmd == 'tgid':
tbl = TGIDTags
if cmd == 'unit':
tbl = UnitIDTags
delRec = delete(tbl.table_).where(tbl.sysid == sysid)
db.session.execute(delRec)
db.session.commit()
db.session.execute("VACUUM") # sqlite3 clean up -- reduces file size
session['sm'] = 4
return redirect('/edit_tags?cmd=' + cmd)
# system tag editor functions (entirely separate from the tags editor above)
@app.route("/editsys")
def editsys():
params = request.args.to_dict()
params['ekeys'] = sorted(oplog_map.keys())
params['cc_desc'] = cc_desc
SysIDTags = column_helper('sysid_tags')
systems = db.session.query(SysIDTags.sysid, SysIDTags.tag)
return render_template("editsys.html", params=params, systems=systems, sysList=sysList())
#dsd = delete system data
@app.route("/dsd")
def dsd():
params = request.args.to_dict()
SysIDTags = column_helper('sysid_tags')
recId = params['id']
delRec = delete(SysIDTags.table_).where(SysIDTags.id == recId)
db.session.execute(delRec)
db.session.commit()
return redirect('/editsys')
#usd = update system data
@app.route("/usd")
def usd():
params = request.args.to_dict()
SysIDTags = column_helper('sysid_tags')
recId = params['id']
tag = params['tag']
upRec = update(SysIDTags.table_).where(SysIDTags.id == recId).values(tag=tag)
db.session.execute(upRec)
db.session.commit()
return redirect('/editsys')
#esd = edit system data (system tags)
@app.route("/esd")
def esd():
params = request.args.to_dict()
SysIDTags = column_helper('sysid_tags')
column_d = {
's': [
ColumnDT(SysIDTags.id),
ColumnDT(SysIDTags.sysid),
ColumnDT(SysIDTags.tag),
ColumnDT(SysIDTags.id)
]
}
q = db.session.query(SysIDTags.id, SysIDTags.sysid, SysIDTags.tag)
rowTable = DataTables(params, q, column_d['s'])
js = jsonify(rowTable.output_result())
return js
#asd = add system data
@app.route("/asd")
def asd():
params = request.args.to_dict()
ns = params['id']
nt = params['tag']
#todo: validate input
SysIDTags = column_helper('sysid_tags')
insRec = insert(SysIDTags.table_).values(sysid=ns, tag=nt)
db.session.execute(insRec)
db.session.commit()
return redirect('/editsys')
# purge database functions
@app.route("/purge")
def purge():
params = request.args.to_dict()
params['ekeys'] = sorted(oplog_map.keys())
DataStore = column_helper('data_store')
destfile = ''
if 'bu' in params.keys():
if params['bu'] == 'true':
t = strftime("%Y%m%d_%H%M%S")
destfile = 'op25-backup-%s.db' % t
src = app.config['SQLALCHEMY_DATABASE_URI'][10:]
s = src.split('/')
f = s[-1]
dst = src.replace(f, destfile)
if 'simulate' in params.keys():
simulate = params['simulate']
if 'action' in params.keys():
if params['action'] == 'purge':
sd = params['sd']
ed = params['ed']
sysid = int(params['sysid'])
delRec = delete(DataStore.table_).where(DataStore.time >= int(sd), DataStore.time <= int(ed))
recCount = db.session.query(DataStore.id).filter(and_(DataStore.time >= int(sd), DataStore.time <= int(ed)))
if sysid != 0:
recCount = recCount.filter(DataStore.sysid == sysid)
delRec = delRec.where(DataStore.sysid == sysid)
if 'kv' in params.keys(): # keep voice calls
if params['kv'] == 'true':
recCount = recCount.where(and_(DataStore.opcode != 0, DataStore.opcode != 2))
delRec = delRec.where(and_(DataStore.opcode != 0, DataStore.opcode != 2))
recCount = recCount.count()
dispQuery = delRec.compile(compile_kwargs={"literal_binds": True})
if simulate == 'false':
copyfile(src, dst)
db.session.execute(delRec)
db.session.commit()
db.session.execute("VACUUM") # sqlite3 clean up -- reduces file size
successMessage = 1
else:
successMessage = 2
else:
recCount = 0
successMessage = 0
dispQuery = ''
return render_template("purge.html", \
project="op25", \
params=params, \
dbstats=dbStats(), \
sysList=sysList(), \
successMessage=successMessage, \
recCount=recCount, \
dispQuery=dispQuery, \
destfile=destfile )
# displays all logs w/ datatables
@app.route("/logs")
def logs():
UnitIDTags = column_helper('unit_id_tags')
TGIDTags = column_helper('tgid_tags')
tag = ''
params = request.args.to_dict()
params['ekeys'] = oplog_map.keys()
params['cc_desc'] = cc_desc
t = None if 'q' not in params.keys() else params['q']
sysid = 0 if 'sysid' not in params.keys() else int(params['sysid'])
if sysid != 0:
if t is not None and params['r'] == 'tgid':
q = db.session.query(TGIDTags.tag).where(and_(TGIDTags.rid == t, TGIDTags.sysid == sysid))
if t is not None and params['r'] == 'su':
q = db.session.query(UnitIDTags.tag).where(and_(UnitIDTags.rid == t, UnitIDTags.sysid == sysid))
if q.count() > 0:
tg = (db.session.execute(q).one())
tag = (' - %s' % tg.tag)
if params['r'] == 'cc_event':
mapl = oplog_map[params['p'].strip()]
params['ckeys'] = [s[1] for s in mapl if s[0] != 'opcode' and s[0] != 'cc_event']
return render_template("logs.html", \
project="logs", \
params=params, \
sysList=sysList(), \
tag=tag )
# data for /logs
@app.route("/data")
def data():
"""Return server side data."""
# GET parameters
params = request.args.to_dict()
host_rid = None if 'host_rid' not in params.keys() else params['host_rid']
host_function_type = None if 'host_function_type' not in params.keys() else params['host_function_type']
host_function_param = None if 'host_function_param' not in params.keys() else params['host_function_param'].strip()
filter_tgid = None if 'tgid' not in params.keys() else int(params['tgid'].strip())
filter_suid = None if 'suid' not in params.keys() else int(params['suid'].strip())
start_time = None if 'sdate' not in params.keys() else datetime.datetime.utcfromtimestamp(float(params['sdate']))
end_time = None if 'edate' not in params.keys() else datetime.datetime.utcfromtimestamp(float(params['edate']))
sysid = None if 'sysid' not in params.keys() else int(params['sysid'])
stime = int(params['sdate']) #used in the queries
etime = int(params['edate']) #used in the queries
DataStore = column_helper('data_store')
EventKeys = column_helper('event_keys')
SysIDTags = column_helper('sysid_tags')
UnitIDTags = column_helper('unit_id_tags')
TGIDTags = column_helper('tgid_tags')
LocRegResp = column_helper('loc_reg_resp_rv')
DataStore.time.type = MyDateType()
k = 'logs'
if host_function_type:
k = '%s_%s' % (k, host_function_type)
column_d = {
'logs_su': [
ColumnDT(TGIDTags.tag),
ColumnDT(DataStore.tgid),
ColumnDT(DataStore.tgid),
],
'logs_tgid': [
ColumnDT(DataStore.suid),
ColumnDT(UnitIDTags.tag),
ColumnDT(DataStore.suid),
ColumnDT(DataStore.time)
],
'logs_calls': [
ColumnDT(DataStore.time),
ColumnDT(SysIDTags.tag),
ColumnDT(DataStore.tgid),
ColumnDT(TGIDTags.tag),
ColumnDT(DataStore.frequency),
ColumnDT(DataStore.suid)
],
'logs_joins': [
ColumnDT(DataStore.time),
ColumnDT(DataStore.opcode),
ColumnDT(DataStore.sysid),
ColumnDT(SysIDTags.tag),
ColumnDT(LocRegResp.tag),
ColumnDT(DataStore.tgid),
ColumnDT(TGIDTags.tag),
ColumnDT(DataStore.suid),
ColumnDT(UnitIDTags.tag)
],
'logs_total_tgid': [
ColumnDT(DataStore.sysid),
ColumnDT(SysIDTags.tag),
ColumnDT(DataStore.tgid),
ColumnDT(TGIDTags.tag),
ColumnDT(DataStore.tgid)
],
'logs_call_detail': [
ColumnDT(DataStore.time),
ColumnDT(DataStore.opcode),
ColumnDT(SysIDTags.sysid),
ColumnDT(SysIDTags.tag),
ColumnDT(DataStore.tgid),
ColumnDT(TGIDTags.tag),
ColumnDT(DataStore.suid),
ColumnDT(UnitIDTags.tag),
ColumnDT(DataStore.frequency)
]
}
"""or_( EventKeys.tag == 'grp_v_ch_grant', EventKeys.tag == 'grp_v_ch_grant_exp'),"""
query_d = {
'logs_total_tgid': db.session.query(DataStore.sysid, \
SysIDTags.tag, \
DataStore.tgid, \
TGIDTags.tag, \
func.count(DataStore.tgid).label('count'))
.group_by(DataStore.tgid)
.outerjoin(SysIDTags.table_, DataStore.sysid == SysIDTags.sysid)
.outerjoin(TGIDTags.table_, DataStore.tgid == TGIDTags.rid)
.filter(and_(DataStore.tgid != 0), (DataStore.frequency != None) ),
'logs_call_detail': db.session.query(DataStore.time, \
DataStore.opcode, \
DataStore.sysid, \
SysIDTags.tag, \
DataStore.tgid, \
TGIDTags.tag, \
DataStore.suid, \
UnitIDTags.tag, \
DataStore.frequency )
.outerjoin(SysIDTags.table_, DataStore.sysid == SysIDTags.sysid)
.outerjoin(TGIDTags.table_, and_(DataStore.tgid == TGIDTags.rid, DataStore.sysid == TGIDTags.sysid))
.outerjoin(UnitIDTags.table_, and_(DataStore.suid == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid))
.filter(and_(DataStore.tgid != 0), (DataStore.frequency != None) )
.filter(or_(DataStore.opcode == 0, and_(DataStore.opcode == 2, DataStore.mfrid == 144)) ),
'logs_tgid': db.session.query(DataStore.suid, \
UnitIDTags.tag, \
func.count(DataStore.suid).label('count'), func.max(DataStore.time).label('last') )
.outerjoin(UnitIDTags.table_, and_(DataStore.suid == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid)),
'logs_su': db.session.query(TGIDTags.tag, \
DataStore.tgid, \
func.count(DataStore.tgid).label('count') )
.outerjoin(TGIDTags.table_, DataStore.tgid == TGIDTags.rid),
'logs_calls': db.session.query(DataStore.time, \
SysIDTags.tag, \
DataStore.tgid, \
TGIDTags.tag, \
DataStore.frequency, \
DataStore.suid )
.join(EventKeys.table_, and_(or_( EventKeys.tag == 'grp_v_ch_grant', EventKeys.tag == 'grp_v_ch_grant_mbt'),EventKeys.id == DataStore.cc_event))
.outerjoin(TGIDTags.table_, and_(TGIDTags.rid == DataStore.tgid, TGIDTags.sysid == DataStore.sysid))
.outerjoin(SysIDTags.table_, DataStore.sysid == SysIDTags.sysid),
'logs_joins': db.session.query(DataStore.time, \
DataStore.opcode, \
DataStore.sysid, \
SysIDTags.tag, \
LocRegResp.tag, \
DataStore.tgid, \
TGIDTags.tag, \
DataStore.suid, \
UnitIDTags.tag )
.join(LocRegResp.table_, DataStore.p == LocRegResp.rv)
.outerjoin(SysIDTags.table_, DataStore.sysid == SysIDTags.sysid)
.outerjoin(TGIDTags.table_, and_(DataStore.tgid == TGIDTags.rid, DataStore.sysid == TGIDTags.sysid))
.outerjoin(UnitIDTags.table_, and_(DataStore.suid == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid))
.filter(or_(DataStore.opcode == 40, DataStore.opcode == 43)) # joins
} # end query_d
if host_function_type != 'cc_event':
q = query_d[k]
if host_function_type in 'su tgid'.split():
filter_col = {'su': DataStore.suid, 'tgid': DataStore.tgid}
group_col = {'su': DataStore.tgid, 'tgid': DataStore.suid}
if '?' in host_rid:
id_start = int(host_rid.replace('?', '0'))
id_end = int(host_rid.replace('?', '9'))
q = q.filter(filter_col[host_function_type] >= id_start, filter_col[host_function_type] <= id_end)
elif '-' in host_rid:
id_start, id_end = host_rid.split('-')
id_start = int(id_start)
id_end = int(id_end)
q = q.filter(filter_col[host_function_type] >= id_start, filter_col[host_function_type] <= id_end)
else:
q = q.filter(filter_col[host_function_type] == int(host_rid))
q = q.group_by(group_col[host_function_type])
q = q.filter(DataStore.suid != None)
dt_cols = {
'logs_tgid' : [ DataStore.suid, UnitIDTags.tag, 'count' ],
'logs_su' : [ TGIDTags.tag, DataStore.tgid, 'count' ],
'logs_calls' : [ DataStore.time, SysIDTags.tag, DataStore.tgid, TGIDTags.tag, DataStore.frequency, DataStore.suid ],
'logs_joins' : [ DataStore.time, SysIDTags.tag, LocRegResp.tag, TGIDTags.tag, DataStore.suid ],
'logs_total_tgid' : [ DataStore.sysid, SysIDTags.tag, DataStore.tgid, TGIDTags.tag, 'count' ]
}
if host_function_type == 'cc_event':
mapl = oplog_map[host_function_param]
columns = []
for row in mapl:
col = getattr(DataStore, row[0])
if row[0] == 'sysid':
col = SysIDTags.tag
elif row[1] == 'Talkgroup':
col = TGIDTags.tag
elif row[1] == 'Source' or row[1] == 'Target':
col = UnitIDTags.tag
elif row[0] == 'cc_event':
continue
#col = EventKeys.tag
elif row[0] == 'opcode':
continue
elif host_function_param == 'loc_reg_resp' and row[0] == 'p':
col = LocRegResp.tag
columns.append(col)
column_dt = [ColumnDT(s) for s in columns]
q = db.session.query(*columns
).join(
EventKeys.table_, and_( EventKeys.tag == host_function_param, EventKeys.id == DataStore.cc_event)
).outerjoin(
SysIDTags.table_, DataStore.sysid == SysIDTags.sysid
)
if host_function_param == 'grp_aff_resp':
q = q.outerjoin(
TGIDTags.table_, and_(DataStore.tgid2 == TGIDTags.rid, DataStore.sysid == TGIDTags.sysid)
).outerjoin(
UnitIDTags.table_, and_(DataStore.suid == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid)
)
elif host_function_param == 'ack_resp_fne' or host_function_param == 'grp_aff_q' or host_function_param == 'u_reg_cmd':
q = q.outerjoin(
TGIDTags.table_, and_(DataStore.tgid2 == TGIDTags.rid, DataStore.sysid == TGIDTags.sysid)
).outerjoin(
UnitIDTags.table_, and_(DataStore.suid2 == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid)
)
else:
q = q.outerjoin(
TGIDTags.table_, and_(DataStore.tgid == TGIDTags.rid, DataStore.sysid == TGIDTags.sysid)
).outerjoin(
UnitIDTags.table_, and_(DataStore.suid == UnitIDTags.rid, DataStore.sysid == UnitIDTags.sysid)
)
if host_function_param == 'loc_reg_resp':
q = q.join(LocRegResp.table_, LocRegResp.rv == DataStore.p)
if host_function_type == 'cc_event':
cl = columns
elif k in dt_cols:
cl = dt_cols[k]
else:
cl = None
# apply tgid and suid filters if present
if host_function_type == 'cc_event':
if filter_tgid is not None and int(filter_tgid) != 0:
q = q.filter(DataStore.tgid == filter_tgid)
if filter_suid is not None and int(filter_suid) != 0:
q = q.filter(DataStore.suid == filter_suid)
if cl:
c = int(params['order[0][column]'])
d = params['order[0][dir]'] # asc or desc
if d == 'asc':
q = q.order_by(cl[c])
else:
q = q.order_by(desc(cl[c]))
q = q.filter(and_(DataStore.time >= int(stime), DataStore.time <= int(etime)))
if sysid != 0:
q = q.filter(DataStore.sysid == sysid)
if host_function_type == 'cc_event':
rowTable = DataTables(params, q, column_dt)
else:
rowTable = DataTables(params, q, column_d[k])
js = jsonify(rowTable.output_result())
# j= 'skipped' # json.dumps(rowTable.output_result(), indent=4, separators=[',', ':'], sort_keys=True)
# with open('data-log', 'a') as logf:
# s = '\n\t'.join(['%s:%s' % (k, params[k]) for k in params.keys()])
# logf.write('keys: %s\n' % (' '.join(params.keys())))
# logf.write('params:\n\t%s\nrequest: %s\n' % (s, function_req))
# logf.write('%s\n' % j)
return js
# switch and backup database file
@app.route("/switch_db")
def switch_db():
params = request.args.to_dict()
params['ekeys'] = sorted(oplog_map.keys())
p = os.getcwd() + '/..'
files = [f for f in listdir(p) if isfile(join(p, f))]
files.sort()
if 'cmd' not in params.keys():
curr_file = app.config['SQLALCHEMY_DATABASE_URI'].split('/')[-1]
return render_template("switch_db.html", params=params, files=files, curr_file=curr_file)
if params['cmd'] == 'backup':
t = strftime("%Y-%m-%d_%H%M%S")
destfile = 'op25-backup-%s.db' % t
src = app.config['SQLALCHEMY_DATABASE_URI'][10:]
s = src.split('/')
curr_file = s[-1]
dst = src.replace(curr_file, destfile)
copyfile(src, dst)
return render_template("switch_db.html", params=params, destfile=destfile, curr_file=curr_file, files=files, sm=1)
if params['cmd'] == 'switch':
new_f = params['file']
database = app.config['SQLALCHEMY_DATABASE_URI']
f = database.split('/')[-1]
new_db = database.replace(f, new_f)
print('switching database to: %s' % new_db)
app.config['SQLALCHEMY_DATABASE_URI'] = new_db
return redirect('/')

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,461 @@
/*
* Table styles
*/
table.dataTable {
width: 100%;
margin: 0 auto;
clear: both;
border-collapse: separate;
border-spacing: 0;
/*
* Header and footer styles
*/
/*
* Body styles
*/
}
table.dataTable thead th,
table.dataTable tfoot th {
font-weight: bold;
}
table.dataTable thead th,
table.dataTable thead td {
padding: 10px 18px;
border-bottom: 1px solid #dddddd;
}
table.dataTable thead th:active,
table.dataTable thead td:active {
outline: none;
}
table.dataTable tfoot th,
table.dataTable tfoot td {
padding: 10px 18px 6px 18px;
border-top: 1px solid #dddddd;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc,
table.dataTable thead .sorting_asc_disabled,
table.dataTable thead .sorting_desc_disabled {
cursor: pointer;
*cursor: hand;
background-repeat: no-repeat;
background-position: center right;
}
table.dataTable thead .sorting {
background-image: url("../images/sort_both.png");
}
table.dataTable thead .sorting_asc {
background-image: url("../images/sort_asc.png") !important;
}
table.dataTable thead .sorting_desc {
background-image: url("../images/sort_desc.png") !important;
}
table.dataTable thead .sorting_asc_disabled {
background-image: url("../images/sort_asc_disabled.png");
}
table.dataTable thead .sorting_desc_disabled {
background-image: url("../images/sort_desc_disabled.png");
}
table.dataTable tbody tr {
background-color: #333333;
}
table.dataTable tbody tr.selected {
background-color: #666666;
}
table.dataTable tbody th,
table.dataTable tbody td {
padding: 8px 10px;
}
table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td {
border-top: 1px solid #111111;
}
table.dataTable.row-border tbody tr:first-child th,
table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th,
table.dataTable.display tbody tr:first-child td {
border-top: none;
}
table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td {
border-top: 1px solid #111111;
border-right: 1px solid #111111;
}
table.dataTable.cell-border tbody tr th:first-child,
table.dataTable.cell-border tbody tr td:first-child {
border-left: 1px solid #111111;
}
table.dataTable.cell-border tbody tr:first-child th,
table.dataTable.cell-border tbody tr:first-child td {
border-top: none;
}
table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd {
background-color: #323232;
}
table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected {
background-color: #646464;
}
table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover {
background-color: #313131;
}
table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected {
background-color: #626262;
}
table.dataTable.order-column tbody tr > .sorting_1,
table.dataTable.order-column tbody tr > .sorting_2,
table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1,
table.dataTable.display tbody tr > .sorting_2,
table.dataTable.display tbody tr > .sorting_3 {
background-color: #323232;
}
table.dataTable.order-column tbody tr.selected > .sorting_1,
table.dataTable.order-column tbody tr.selected > .sorting_2,
table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1,
table.dataTable.display tbody tr.selected > .sorting_2,
table.dataTable.display tbody tr.selected > .sorting_3 {
background-color: #646464;
}
table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 {
background-color: #303030;
}
table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 {
background-color: #313131;
}
table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 {
background-color: #313131;
}
table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 {
background-color: #606060;
}
table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 {
background-color: #616161;
}
table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 {
background-color: #626262;
}
table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 {
background-color: #323232;
}
table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 {
background-color: #323232;
}
table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 {
background-color: #333333;
}
table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 {
background-color: #646464;
}
table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 {
background-color: #656565;
}
table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 {
background-color: #666666;
}
table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 {
background-color: #2f2f2f;
}
table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 {
background-color: #2f2f2f;
}
table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 {
background-color: #303030;
}
table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 {
background-color: #5e5e5e;
}
table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 {
background-color: #5e5e5e;
}
table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 {
background-color: #606060;
}
table.dataTable.no-footer {
border-bottom: 1px solid #dddddd;
}
table.dataTable.nowrap th, table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable.compact thead th,
table.dataTable.compact thead td {
padding: 4px 17px;
}
table.dataTable.compact tfoot th,
table.dataTable.compact tfoot td {
padding: 4px;
}
table.dataTable.compact tbody th,
table.dataTable.compact tbody td {
padding: 4px;
}
table.dataTable th.dt-left,
table.dataTable td.dt-left {
text-align: left;
}
table.dataTable th.dt-center,
table.dataTable td.dt-center,
table.dataTable td.dataTables_empty {
text-align: center;
}
table.dataTable th.dt-right,
table.dataTable td.dt-right {
text-align: right;
}
table.dataTable th.dt-justify,
table.dataTable td.dt-justify {
text-align: justify;
}
table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap {
white-space: nowrap;
}
table.dataTable thead th.dt-head-left,
table.dataTable thead td.dt-head-left,
table.dataTable tfoot th.dt-head-left,
table.dataTable tfoot td.dt-head-left {
text-align: left;
}
table.dataTable thead th.dt-head-center,
table.dataTable thead td.dt-head-center,
table.dataTable tfoot th.dt-head-center,
table.dataTable tfoot td.dt-head-center {
text-align: center;
}
table.dataTable thead th.dt-head-right,
table.dataTable thead td.dt-head-right,
table.dataTable tfoot th.dt-head-right,
table.dataTable tfoot td.dt-head-right {
text-align: right;
}
table.dataTable thead th.dt-head-justify,
table.dataTable thead td.dt-head-justify,
table.dataTable tfoot th.dt-head-justify,
table.dataTable tfoot td.dt-head-justify {
text-align: justify;
}
table.dataTable thead th.dt-head-nowrap,
table.dataTable thead td.dt-head-nowrap,
table.dataTable tfoot th.dt-head-nowrap,
table.dataTable tfoot td.dt-head-nowrap {
white-space: nowrap;
}
table.dataTable tbody th.dt-body-left,
table.dataTable tbody td.dt-body-left {
text-align: left;
}
table.dataTable tbody th.dt-body-center,
table.dataTable tbody td.dt-body-center {
text-align: center;
}
table.dataTable tbody th.dt-body-right,
table.dataTable tbody td.dt-body-right {
text-align: right;
}
table.dataTable tbody th.dt-body-justify,
table.dataTable tbody td.dt-body-justify {
text-align: justify;
}
table.dataTable tbody th.dt-body-nowrap,
table.dataTable tbody td.dt-body-nowrap {
white-space: nowrap;
}
table.dataTable,
table.dataTable th,
table.dataTable td {
box-sizing: content-box;
}
/*
* Control feature layout
*/
.dataTables_wrapper {
position: relative;
clear: both;
*zoom: 1;
zoom: 1;
}
.dataTables_wrapper .dataTables_length {
float: left;
}
.dataTables_wrapper .dataTables_length select {
border: 1px solid #aaa;
border-radius: 3px;
padding: 5px;
background-color: transparent;
padding: 4px;
}
.dataTables_wrapper .dataTables_filter {
float: right;
text-align: right;
}
.dataTables_wrapper .dataTables_filter input {
border: 1px solid #aaa;
border-radius: 3px;
padding: 5px;
background-color: transparent;
margin-left: 3px;
}
.dataTables_wrapper .dataTables_info {
clear: both;
float: left;
padding-top: 0.755em;
}
.dataTables_wrapper .dataTables_paginate {
float: right;
text-align: right;
padding-top: 0.25em;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
box-sizing: border-box;
display: inline-block;
min-width: 1.5em;
padding: 0.5em 1em;
margin-left: 2px;
text-align: center;
text-decoration: none !important;
cursor: pointer;
*cursor: hand;
color: #aaaaaa !important;
border: 1px solid transparent;
border-radius: 2px;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
color: #aaaaaa !important;
border: 1px solid #979797;
background-color: white;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%);
/* IE10+ */
background: -o-linear-gradient(top, white 0%, #dcdcdc 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, white 0%, #dcdcdc 100%);
/* W3C */
}
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
cursor: default;
color: #666 !important;
border: 1px solid transparent;
background: transparent;
box-shadow: none;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
color: white !important;
border: 1px solid #375a7f;
background-color: #7ea1c7;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #7ea1c7), color-stop(100%, #375a7f));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #7ea1c7 0%, #375a7f 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, #7ea1c7 0%, #375a7f 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, #7ea1c7 0%, #375a7f 100%);
/* IE10+ */
background: -o-linear-gradient(top, #7ea1c7 0%, #375a7f 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, #7ea1c7 0%, #375a7f 100%);
/* W3C */
}
.dataTables_wrapper .dataTables_paginate .paginate_button:active {
outline: none;
background-color: #4673a3;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #4673a3), color-stop(100%, #345578));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #4673a3 0%, #345578 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, #4673a3 0%, #345578 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, #4673a3 0%, #345578 100%);
/* IE10+ */
background: -o-linear-gradient(top, #4673a3 0%, #345578 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, #4673a3 0%, #345578 100%);
/* W3C */
box-shadow: inset 0 0 3px #111;
}
.dataTables_wrapper .dataTables_paginate .ellipsis {
padding: 0 1em;
}
.dataTables_wrapper .dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 90px;
margin-left: -50%;
margin-top: -25px;
padding-top: 20px;
text-align: center;
font-size: 2.0em;
background-color: white;
background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(51, 51, 51, 0)), color-stop(25%, rgba(51, 51, 51, 0.9)), color-stop(75%, rgba(51, 51, 51, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));
background: -webkit-linear-gradient(left, rgba(51, 51, 51, 0) 0%, rgba(51, 51, 51, 0.9) 25%, rgba(51, 51, 51, 0.9) 75%, rgba(51, 51, 51, 0) 100%);
background: -moz-linear-gradient(left, rgba(51, 51, 51, 0) 0%, rgba(51, 51, 51, 0.9) 25%, rgba(51, 51, 51, 0.9) 75%, rgba(51, 51, 51, 0) 100%);
background: -ms-linear-gradient(left, rgba(51, 51, 51, 0) 0%, rgba(51, 51, 51, 0.9) 25%, rgba(51, 51, 51, 0.9) 75%, rgba(51, 51, 51, 0) 100%);
background: -o-linear-gradient(left, rgba(51, 51, 51, 0) 0%, rgba(51, 51, 51, 0.9) 25%, rgba(51, 51, 51, 0.9) 75%, rgba(51, 51, 51, 0) 100%);
background: linear-gradient(to right, rgba(51, 51, 51, 0) 0%, rgba(51, 51, 51, 0.9) 25%, rgba(51, 51, 51, 0.9) 75%, rgba(51, 51, 51, 0) 100%);
}
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter,
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_processing,
.dataTables_wrapper .dataTables_paginate {
color: #aaaaaa;
}
.dataTables_wrapper .dataTables_scroll {
clear: both;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody {
*margin-top: -1px;
-webkit-overflow-scrolling: touch;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td {
vertical-align: middle;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing,
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing,
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing {
height: 0;
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
}
.dataTables_wrapper.no-footer .dataTables_scrollBody {
border-bottom: 1px solid #dddddd;
}
.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,
.dataTables_wrapper.no-footer div.dataTables_scrollBody > table {
border-bottom: none;
}
.dataTables_wrapper:after {
visibility: hidden;
display: block;
content: "";
clear: both;
height: 0;
}
@media screen and (max-width: 767px) {
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_paginate {
float: none;
text-align: center;
}
.dataTables_wrapper .dataTables_paginate {
margin-top: 0.5em;
}
}
@media screen and (max-width: 640px) {
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter {
float: none;
text-align: center;
}
.dataTables_wrapper .dataTables_filter {
margin-top: 0.5em;
}
}

View File

@ -0,0 +1,453 @@
/*
* Table styles
*/
table.dataTable {
width: 100%;
margin: 0 auto;
clear: both;
border-collapse: separate;
border-spacing: 0;
/*
* Header and footer styles
*/
/*
* Body styles
*/
}
table.dataTable thead th,
table.dataTable tfoot th {
font-weight: bold;
}
table.dataTable thead th,
table.dataTable thead td {
padding: 10px 18px;
border-bottom: 1px solid #111;
}
table.dataTable thead th:active,
table.dataTable thead td:active {
outline: none;
}
table.dataTable tfoot th,
table.dataTable tfoot td {
padding: 10px 18px 6px 18px;
border-top: 1px solid #111;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc {
cursor: pointer;
*cursor: hand;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc,
table.dataTable thead .sorting_asc_disabled,
table.dataTable thead .sorting_desc_disabled {
background-repeat: no-repeat;
background-position: center right;
}
table.dataTable thead .sorting {
background-image: url("../images/sort_both.png");
}
table.dataTable thead .sorting_asc {
background-image: url("../images/sort_asc.png");
}
table.dataTable thead .sorting_desc {
background-image: url("../images/sort_desc.png");
}
table.dataTable thead .sorting_asc_disabled {
background-image: url("../images/sort_asc_disabled.png");
}
table.dataTable thead .sorting_desc_disabled {
background-image: url("../images/sort_desc_disabled.png");
}
table.dataTable tbody tr {
background-color: #ffffff;
}
table.dataTable tbody tr.selected {
background-color: #B0BED9;
}
table.dataTable tbody th,
table.dataTable tbody td {
padding: 8px 10px;
}
table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td {
border-top: 1px solid #ddd;
}
table.dataTable.row-border tbody tr:first-child th,
table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th,
table.dataTable.display tbody tr:first-child td {
border-top: none;
}
table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td {
border-top: 1px solid #ddd;
border-right: 1px solid #ddd;
}
table.dataTable.cell-border tbody tr th:first-child,
table.dataTable.cell-border tbody tr td:first-child {
border-left: 1px solid #ddd;
}
table.dataTable.cell-border tbody tr:first-child th,
table.dataTable.cell-border tbody tr:first-child td {
border-top: none;
}
table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd {
background-color: #f9f9f9;
}
table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected {
background-color: #acbad4;
}
table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover {
background-color: #f6f6f6;
}
table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected {
background-color: #aab7d1;
}
table.dataTable.order-column tbody tr > .sorting_1,
table.dataTable.order-column tbody tr > .sorting_2,
table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1,
table.dataTable.display tbody tr > .sorting_2,
table.dataTable.display tbody tr > .sorting_3 {
background-color: #fafafa;
}
table.dataTable.order-column tbody tr.selected > .sorting_1,
table.dataTable.order-column tbody tr.selected > .sorting_2,
table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1,
table.dataTable.display tbody tr.selected > .sorting_2,
table.dataTable.display tbody tr.selected > .sorting_3 {
background-color: #acbad5;
}
table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 {
background-color: #f1f1f1;
}
table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 {
background-color: #f3f3f3;
}
table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 {
background-color: whitesmoke;
}
table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 {
background-color: #a6b4cd;
}
table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 {
background-color: #a8b5cf;
}
table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 {
background-color: #a9b7d1;
}
table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 {
background-color: #fafafa;
}
table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 {
background-color: #fcfcfc;
}
table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 {
background-color: #fefefe;
}
table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 {
background-color: #acbad5;
}
table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 {
background-color: #aebcd6;
}
table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 {
background-color: #afbdd8;
}
table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 {
background-color: #eaeaea;
}
table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 {
background-color: #ececec;
}
table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 {
background-color: #efefef;
}
table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 {
background-color: #a2aec7;
}
table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 {
background-color: #a3b0c9;
}
table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 {
background-color: #a5b2cb;
}
table.dataTable.no-footer {
border-bottom: 1px solid #111;
}
table.dataTable.nowrap th, table.dataTable.nowrap td {
white-space: nowrap;
}
table.dataTable.compact thead th,
table.dataTable.compact thead td {
padding: 4px 17px 4px 4px;
}
table.dataTable.compact tfoot th,
table.dataTable.compact tfoot td {
padding: 4px;
}
table.dataTable.compact tbody th,
table.dataTable.compact tbody td {
padding: 4px;
}
table.dataTable th.dt-left,
table.dataTable td.dt-left {
text-align: left;
}
table.dataTable th.dt-center,
table.dataTable td.dt-center,
table.dataTable td.dataTables_empty {
text-align: center;
}
table.dataTable th.dt-right,
table.dataTable td.dt-right {
text-align: right;
}
table.dataTable th.dt-justify,
table.dataTable td.dt-justify {
text-align: justify;
}
table.dataTable th.dt-nowrap,
table.dataTable td.dt-nowrap {
white-space: nowrap;
}
table.dataTable thead th.dt-head-left,
table.dataTable thead td.dt-head-left,
table.dataTable tfoot th.dt-head-left,
table.dataTable tfoot td.dt-head-left {
text-align: left;
}
table.dataTable thead th.dt-head-center,
table.dataTable thead td.dt-head-center,
table.dataTable tfoot th.dt-head-center,
table.dataTable tfoot td.dt-head-center {
text-align: center;
}
table.dataTable thead th.dt-head-right,
table.dataTable thead td.dt-head-right,
table.dataTable tfoot th.dt-head-right,
table.dataTable tfoot td.dt-head-right {
text-align: right;
}
table.dataTable thead th.dt-head-justify,
table.dataTable thead td.dt-head-justify,
table.dataTable tfoot th.dt-head-justify,
table.dataTable tfoot td.dt-head-justify {
text-align: justify;
}
table.dataTable thead th.dt-head-nowrap,
table.dataTable thead td.dt-head-nowrap,
table.dataTable tfoot th.dt-head-nowrap,
table.dataTable tfoot td.dt-head-nowrap {
white-space: nowrap;
}
table.dataTable tbody th.dt-body-left,
table.dataTable tbody td.dt-body-left {
text-align: left;
}
table.dataTable tbody th.dt-body-center,
table.dataTable tbody td.dt-body-center {
text-align: center;
}
table.dataTable tbody th.dt-body-right,
table.dataTable tbody td.dt-body-right {
text-align: right;
}
table.dataTable tbody th.dt-body-justify,
table.dataTable tbody td.dt-body-justify {
text-align: justify;
}
table.dataTable tbody th.dt-body-nowrap,
table.dataTable tbody td.dt-body-nowrap {
white-space: nowrap;
}
table.dataTable,
table.dataTable th,
table.dataTable td {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/*
* Control feature layout
*/
.dataTables_wrapper {
position: relative;
clear: both;
*zoom: 1;
zoom: 1;
}
.dataTables_wrapper .dataTables_length {
float: left;
}
.dataTables_wrapper .dataTables_filter {
float: right;
text-align: right;
}
.dataTables_wrapper .dataTables_filter input {
margin-left: 0.5em;
}
.dataTables_wrapper .dataTables_info {
clear: both;
float: left;
padding-top: 0.755em;
}
.dataTables_wrapper .dataTables_paginate {
float: right;
text-align: right;
padding-top: 0.25em;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
box-sizing: border-box;
display: inline-block;
min-width: 1.5em;
padding: 0.5em 1em;
margin-left: 2px;
text-align: center;
text-decoration: none !important;
cursor: pointer;
*cursor: hand;
color: #333 !important;
border: 1px solid transparent;
border-radius: 2px;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
color: #333 !important;
border: 1px solid #979797;
background-color: white;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%);
/* IE10+ */
background: -o-linear-gradient(top, white 0%, #dcdcdc 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, white 0%, #dcdcdc 100%);
/* W3C */
}
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
cursor: default;
color: #666 !important;
border: 1px solid transparent;
background: transparent;
box-shadow: none;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
color: white !important;
border: 1px solid #111;
background-color: #585858;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #585858 0%, #111 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, #585858 0%, #111 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, #585858 0%, #111 100%);
/* IE10+ */
background: -o-linear-gradient(top, #585858 0%, #111 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, #585858 0%, #111 100%);
/* W3C */
}
.dataTables_wrapper .dataTables_paginate .paginate_button:active {
outline: none;
background-color: #2b2b2b;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* Chrome10+,Safari5.1+ */
background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* FF3.6+ */
background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* IE10+ */
background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);
/* Opera 11.10+ */
background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);
/* W3C */
box-shadow: inset 0 0 3px #111;
}
.dataTables_wrapper .dataTables_paginate .ellipsis {
padding: 0 1em;
}
.dataTables_wrapper .dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 40px;
margin-left: -50%;
margin-top: -25px;
padding-top: 20px;
text-align: center;
font-size: 1.2em;
background-color: white;
background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));
background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);
}
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter,
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_processing,
.dataTables_wrapper .dataTables_paginate {
color: #333;
}
.dataTables_wrapper .dataTables_scroll {
clear: both;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody {
*margin-top: -1px;
-webkit-overflow-scrolling: touch;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td {
vertical-align: middle;
}
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th > div.dataTables_sizing,
.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td > div.dataTables_sizing {
height: 0;
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
}
.dataTables_wrapper.no-footer .dataTables_scrollBody {
border-bottom: 1px solid #111;
}
.dataTables_wrapper.no-footer div.dataTables_scrollHead table,
.dataTables_wrapper.no-footer div.dataTables_scrollBody table {
border-bottom: none;
}
.dataTables_wrapper:after {
visibility: hidden;
display: block;
content: "";
clear: both;
height: 0;
}
@media screen and (max-width: 767px) {
.dataTables_wrapper .dataTables_info,
.dataTables_wrapper .dataTables_paginate {
float: none;
text-align: center;
}
.dataTables_wrapper .dataTables_paginate {
margin-top: 0.5em;
}
}
@media screen and (max-width: 640px) {
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter {
float: none;
text-align: center;
}
.dataTables_wrapper .dataTables_filter {
margin-top: 0.5em;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

View File

@ -0,0 +1,237 @@
body {
/* min-height: 2000px; */
/* padding: 0px 25px 0px 25px; */
margin: 5px 10px 5px 10px !important;
/* background-color: #0f0; */
}
.li-text {
padding: 10px 15px 5px 15px;
color: #ddd;
}
.sel-date {
color: black;
width: 150px;
height: 33px;
}
/* Clear floats after the columns */
.row:after {
content: "";
display: table;
clear: both;
}
*/
/*
#container {
width: 1000px;
min-height: 2000px;
margin: 0 auto;
border: 1px solid;
background-color: #f00; }
#primary {
min-height: 2000px; float: left;
width: 15%;
padding: 5px;
background-color: #222;}
#content {
min-height: 2000px; float: left;
width: 70%;
padding: 5px;
background-color: #fff; }
#secondary {
min-height: 2000px; float: left;
width: 15%;
padding: 5px;
background-color: #eee;}
*/
#footer {
clear: both;
background-color: #375a7f;
min-height: 50px;
color: white;
}
.btnMain {
margin: 2px;
padding: 2px;
width: 210px;
}
.dataTables_length select {
color: #fff;
}
.dataTables_length select option {
background-color: #111;
}
.dataTables_filter input[type=search] {
color: #fff;
}
#systemSelect {
color: #000;
height: 33px;
}
#navSelect {
color: #000;
height: 30px;
}
.op-input {
display: inline-block;
font-weight: 400;
line-height: 1.5;
color: #fff;
text-align: center;
text-decoration: none;
vertical-align: middle;
cursor: pointer;
background-color: transparent;
border: 1px solid transparent;
padding: 0.375rem 0.75rem;
font-size: 1rem;
border-radius: 0.25rem;
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
/*
The Modal (background).modal {
display: none; Hidden by default position: fixed; Stay in place z-index: 1; Sit on top left: 0;
top: 0;
width: 100%; Full width height: 100%; Full height overflow: auto; Enable scroll if needed background-color: rgb(0,0,0); Fallback color background-color: rgba(0,0,0,0.4); Black w/ opacity}
Modal Content/Box.modal-content {
background-color: #fefefe;
margin: 15% auto; 15% from the top and centered padding: 20px;
border: 1px solid #888;
width: 80%; Could be more or less, depending on screen size}
The Close Button.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
*/
#loading {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
top: 0;
left: 0;
opacity: 0.5;
background-color: #fff;
z-index: 99;
}
#loading-image {
z-index: 100;
}
/* New layout stuff */
/* Style the body */
/*
body {
font-family: Arial;
margin: 0;
}
*/
/* Header/logo Title */
/*
.header {
padding: 60px;
text-align: center;
background: #1abc9c;
color: white;
}
Style the top navigation bar.navbar {
display: flex;
background-color: #333;
}
Style the navigation bar links.navbar a {
color: white;
padding: 14px 20px;
text-decoration: none;
text-align: center;
}
Change color on hover.navbar a:hover {
background-color: #ddd;
color: black;
}
*/
/* Column container */
.row-main {
display: flex;
flex-wrap: wrap;
}
/* Sidebar/left column */
.side {
flex: 15%;
/* flex: 0 0 275px; */
/* width: 200px; */
/* background-color: #111; */
padding: 0px;
}
/* Main column */
.main {
flex: 70%;
/* background-color: white; */
padding: 5px;
}
/* Footer */
.footer {
/* padding: 20px; */
text-align: center;
/* background: #ddd; */
}
/* Responsive layout - when the screen is less than 700px wide, make the two columns stack on top of each other instead of next to each other */
@media screen and (max-width: 790px) {
.row, .navbar {
flex-direction: column;
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,568 @@
.xdsoft_datetimepicker {
box-shadow: 0 5px 15px -5px rgba(0, 0, 0, 0.506);
background: #fff;
border-bottom: 1px solid #bbb;
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
border-top: 1px solid #ccc;
color: #333;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
padding: 8px;
padding-left: 0;
padding-top: 2px;
position: absolute;
z-index: 9999;
-moz-box-sizing: border-box;
box-sizing: border-box;
display: none;
}
.xdsoft_datetimepicker.xdsoft_rtl {
padding: 8px 0 8px 8px;
}
.xdsoft_datetimepicker iframe {
position: absolute;
left: 0;
top: 0;
width: 75px;
height: 210px;
background: transparent;
border: none;
}
/*For IE8 or lower*/
.xdsoft_datetimepicker button {
border: none !important;
}
.xdsoft_noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
}
.xdsoft_noselect::selection { background: transparent }
.xdsoft_noselect::-moz-selection { background: transparent }
.xdsoft_datetimepicker.xdsoft_inline {
display: inline-block;
position: static;
box-shadow: none;
}
.xdsoft_datetimepicker * {
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 0;
margin: 0;
}
.xdsoft_datetimepicker .xdsoft_datepicker, .xdsoft_datetimepicker .xdsoft_timepicker {
display: none;
}
.xdsoft_datetimepicker .xdsoft_datepicker.active, .xdsoft_datetimepicker .xdsoft_timepicker.active {
display: block;
}
.xdsoft_datetimepicker .xdsoft_datepicker {
width: 224px;
float: left;
margin-left: 8px;
}
.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_datepicker {
float: right;
margin-right: 8px;
margin-left: 0;
}
.xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_datepicker {
width: 256px;
}
.xdsoft_datetimepicker .xdsoft_timepicker {
width: 58px;
float: left;
text-align: center;
margin-left: 8px;
margin-top: 0;
}
.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker {
float: right;
margin-right: 8px;
margin-left: 0;
}
.xdsoft_datetimepicker .xdsoft_datepicker.active+.xdsoft_timepicker {
margin-top: 8px;
margin-bottom: 3px
}
.xdsoft_datetimepicker .xdsoft_monthpicker {
position: relative;
text-align: center;
}
.xdsoft_datetimepicker .xdsoft_label i,
.xdsoft_datetimepicker .xdsoft_prev,
.xdsoft_datetimepicker .xdsoft_next,
.xdsoft_datetimepicker .xdsoft_today_button {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAeCAYAAADaW7vzAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Q0NBRjI1NjM0M0UwMTFFNDk4NkFGMzJFQkQzQjEwRUIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Q0NBRjI1NjQ0M0UwMTFFNDk4NkFGMzJFQkQzQjEwRUIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDQ0FGMjU2MTQzRTAxMUU0OTg2QUYzMkVCRDNCMTBFQiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDQ0FGMjU2MjQzRTAxMUU0OTg2QUYzMkVCRDNCMTBFQiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PoNEP54AAAIOSURBVHja7Jq9TsMwEMcxrZD4WpBYeKUCe+kTMCACHZh4BFfHO/AAIHZGFhYkBBsSEqxsLCAgXKhbXYOTxh9pfJVP+qutnZ5s/5Lz2Y5I03QhWji2GIcgAokWgfCxNvcOCCGKqiSqhUp0laHOne05vdEyGMfkdxJDVjgwDlEQgYQBgx+ULJaWSXXS6r/ER5FBVR8VfGftTKcITNs+a1XpcFoExREIDF14AVIFxgQUS+h520cdud6wNkC0UBw6BCO/HoCYwBhD8QCkQ/x1mwDyD4plh4D6DDV0TAGyo4HcawLIBBSLDkHeH0Mg2yVP3l4TQMZQDDsEOl/MgHQqhMNuE0D+oBh0CIr8MAKyazBH9WyBuKxDWgbXfjNf32TZ1KWm/Ap1oSk/R53UtQ5xTh3LUlMmT8gt6g51Q9p+SobxgJQ/qmsfZhWywGFSl0yBjCLJCMgXail3b7+rumdVJ2YRss4cN+r6qAHDkPWjPjdJCF4n9RmAD/V9A/Wp4NQassDjwlB6XBiCxcJQWmZZb8THFilfy/lfrTvLghq2TqTHrRMTKNJ0sIhdo15RT+RpyWwFdY96UZ/LdQKBGjcXpcc1AlSFEfLmouD+1knuxBDUVrvOBmoOC/rEcN7OQxKVeJTCiAdUzUJhA2Oez9QTkp72OTVcxDcXY8iKNkxGAJXmJCOQwOa6dhyXsOa6XwEGAKdeb5ET3rQdAAAAAElFTkSuQmCC);
}
.xdsoft_datetimepicker .xdsoft_label i {
opacity: 0.5;
background-position: -92px -19px;
display: inline-block;
width: 9px;
height: 20px;
vertical-align: middle;
}
.xdsoft_datetimepicker .xdsoft_prev {
float: left;
background-position: -20px 0;
}
.xdsoft_datetimepicker .xdsoft_today_button {
float: left;
background-position: -70px 0;
margin-left: 5px;
}
.xdsoft_datetimepicker .xdsoft_next {
float: right;
background-position: 0 0;
}
.xdsoft_datetimepicker .xdsoft_next,
.xdsoft_datetimepicker .xdsoft_prev ,
.xdsoft_datetimepicker .xdsoft_today_button {
background-color: transparent;
background-repeat: no-repeat;
border: 0 none;
cursor: pointer;
display: block;
height: 30px;
opacity: 0.5;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
outline: medium none;
overflow: hidden;
padding: 0;
position: relative;
text-indent: 100%;
white-space: nowrap;
width: 20px;
min-width: 0;
}
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_prev,
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_next {
float: none;
background-position: -40px -15px;
height: 15px;
width: 30px;
display: block;
margin-left: 14px;
margin-top: 7px;
}
.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker .xdsoft_prev,
.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker .xdsoft_next {
float: none;
margin-left: 0;
margin-right: 14px;
}
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_prev {
background-position: -40px 0;
margin-bottom: 7px;
margin-top: 0;
}
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box {
height: 151px;
overflow: hidden;
border-bottom: 1px solid #ddd;
}
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div {
background: #f5f5f5;
border-top: 1px solid #ddd;
color: #666;
font-size: 12px;
text-align: center;
border-collapse: collapse;
cursor: pointer;
border-bottom-width: 0;
height: 25px;
line-height: 25px;
}
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div > div:first-child {
border-top-width: 0;
}
.xdsoft_datetimepicker .xdsoft_today_button:hover,
.xdsoft_datetimepicker .xdsoft_next:hover,
.xdsoft_datetimepicker .xdsoft_prev:hover {
opacity: 1;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
}
.xdsoft_datetimepicker .xdsoft_label {
display: inline;
position: relative;
z-index: 9999;
margin: 0;
padding: 5px 3px;
font-size: 14px;
line-height: 20px;
font-weight: bold;
background-color: #fff;
float: left;
width: 182px;
text-align: center;
cursor: pointer;
}
.xdsoft_datetimepicker .xdsoft_label:hover>span {
text-decoration: underline;
}
.xdsoft_datetimepicker .xdsoft_label:hover i {
opacity: 1.0;
}
.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select {
border: 1px solid #ccc;
position: absolute;
right: 0;
top: 30px;
z-index: 101;
display: none;
background: #fff;
max-height: 160px;
overflow-y: hidden;
}
.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select.xdsoft_monthselect{ right: -7px }
.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select.xdsoft_yearselect{ right: 2px }
.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select > div > .xdsoft_option:hover {
color: #fff;
background: #ff8000;
}
.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select > div > .xdsoft_option {
padding: 2px 10px 2px 5px;
text-decoration: none !important;
}
.xdsoft_datetimepicker .xdsoft_label > .xdsoft_select > div > .xdsoft_option.xdsoft_current {
background: #33aaff;
box-shadow: #178fe5 0 1px 3px 0 inset;
color: #fff;
font-weight: 700;
}
.xdsoft_datetimepicker .xdsoft_month {
width: 100px;
text-align: right;
}
.xdsoft_datetimepicker .xdsoft_calendar {
clear: both;
}
.xdsoft_datetimepicker .xdsoft_year{
width: 48px;
margin-left: 5px;
}
.xdsoft_datetimepicker .xdsoft_calendar table {
border-collapse: collapse;
width: 100%;
}
.xdsoft_datetimepicker .xdsoft_calendar td > div {
padding-right: 5px;
}
.xdsoft_datetimepicker .xdsoft_calendar th {
height: 25px;
}
.xdsoft_datetimepicker .xdsoft_calendar td,.xdsoft_datetimepicker .xdsoft_calendar th {
width: 14.2857142%;
background: #f5f5f5;
border: 1px solid #ddd;
color: #666;
font-size: 12px;
text-align: right;
vertical-align: middle;
padding: 0;
border-collapse: collapse;
cursor: pointer;
height: 25px;
}
.xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_calendar td,.xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_calendar th {
width: 12.5%;
}
.xdsoft_datetimepicker .xdsoft_calendar th {
background: #f1f1f1;
}
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_today {
color: #33aaff;
}
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_highlighted_default {
background: #ffe9d2;
box-shadow: #ffb871 0 1px 4px 0 inset;
color: #000;
}
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_highlighted_mint {
background: #c1ffc9;
box-shadow: #00dd1c 0 1px 4px 0 inset;
color: #000;
}
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_default,
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_current,
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div.xdsoft_current {
background: #33aaff;
box-shadow: #178fe5 0 1px 3px 0 inset;
color: #fff;
font-weight: 700;
}
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_other_month,
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_disabled,
.xdsoft_datetimepicker .xdsoft_time_box >div >div.xdsoft_disabled {
opacity: 0.5;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
cursor: default;
}
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_other_month.xdsoft_disabled {
opacity: 0.2;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
}
.xdsoft_datetimepicker .xdsoft_calendar td:hover,
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div:hover {
color: #fff !important;
background: #ff8000 !important;
box-shadow: none !important;
}
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_current.xdsoft_disabled:hover,
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box>div>div.xdsoft_current.xdsoft_disabled:hover {
background: #33aaff !important;
box-shadow: #178fe5 0 1px 3px 0 inset !important;
color: #fff !important;
}
.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_disabled:hover,
.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box >div >div.xdsoft_disabled:hover {
color: inherit !important;
background: inherit !important;
box-shadow: inherit !important;
}
.xdsoft_datetimepicker .xdsoft_calendar th {
font-weight: 700;
text-align: center;
color: #999;
cursor: default;
}
.xdsoft_datetimepicker .xdsoft_copyright {
color: #ccc !important;
font-size: 10px;
clear: both;
float: none;
margin-left: 8px;
}
.xdsoft_datetimepicker .xdsoft_copyright a { color: #eee !important }
.xdsoft_datetimepicker .xdsoft_copyright a:hover { color: #aaa !important }
.xdsoft_time_box {
position: relative;
border: 1px solid #ccc;
}
.xdsoft_scrollbar >.xdsoft_scroller {
background: #ccc !important;
height: 20px;
border-radius: 3px;
}
.xdsoft_scrollbar {
position: absolute;
width: 7px;
right: 0;
top: 0;
bottom: 0;
cursor: pointer;
}
.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_scrollbar {
left: 0;
right: auto;
}
.xdsoft_scroller_box {
position: relative;
}
.xdsoft_datetimepicker.xdsoft_dark {
box-shadow: 0 5px 15px -5px rgba(255, 255, 255, 0.506);
background: #000;
border-bottom: 1px solid #444;
border-left: 1px solid #333;
border-right: 1px solid #333;
border-top: 1px solid #333;
color: #ccc;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box {
border-bottom: 1px solid #222;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box >div >div {
background: #0a0a0a;
border-top: 1px solid #222;
color: #999;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_label {
background-color: #000;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_label > .xdsoft_select {
border: 1px solid #333;
background: #000;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_label > .xdsoft_select > div > .xdsoft_option:hover {
color: #000;
background: #007fff;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_label > .xdsoft_select > div > .xdsoft_option.xdsoft_current {
background: #cc5500;
box-shadow: #b03e00 0 1px 3px 0 inset;
color: #000;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_label i,
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_prev,
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_next,
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_today_button {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAeCAYAAADaW7vzAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QUExQUUzOTA0M0UyMTFFNDlBM0FFQTJENTExRDVBODYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QUExQUUzOTE0M0UyMTFFNDlBM0FFQTJENTExRDVBODYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBQTFBRTM4RTQzRTIxMUU0OUEzQUVBMkQ1MTFENUE4NiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBQTFBRTM4RjQzRTIxMUU0OUEzQUVBMkQ1MTFENUE4NiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pp0VxGEAAAIASURBVHja7JrNSgMxEMebtgh+3MSLr1T1Xn2CHoSKB08+QmR8Bx9A8e7RixdB9CKCoNdexIugxFlJa7rNZneTbLIpM/CnNLsdMvNjM8l0mRCiQ9Ye61IKCAgZAUnH+mU3MMZaHYChBnJUDzWOFZdVfc5+ZFLbrWDeXPwbxIqrLLfaeS0hEBVGIRQCEiZoHQwtlGSByCCdYBl8g8egTTAWoKQMRBRBcZxYlhzhKegqMOageErsCHVkk3hXIFooDgHB1KkHIHVgzKB4ADJQ/A1jAFmAYhkQqA5TOBtocrKrgXwQA8gcFIuAIO8sQSA7hidvPwaQGZSaAYHOUWJABhWWw2EMIH9QagQERU4SArJXo0ZZL18uvaxejXt/Em8xjVBXmvFr1KVm/AJ10tRe2XnraNqaJvKE3KHuUbfK1E+VHB0q40/y3sdQSxY4FHWeKJCunP8UyDdqJZenT3ntVV5jIYCAh20vT7ioP8tpf6E2lfEMwERe+whV1MHjwZB7PBiCxcGQWwKZKD62lfGNnP/1poFAA60T7rF1UgcKd2id3KDeUS+oLWV8DfWAepOfq00CgQabi9zjcgJVYVD7PVzQUAUGAQkbNJTBICDhgwYTjDYD6XeW08ZKh+A4pYkzenOxXUbvZcWz7E8ykRMnIHGX1XPl+1m2vPYpL+2qdb8CDAARlKFEz/ZVkAAAAABJRU5ErkJggg==);
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td,
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar th {
background: #0a0a0a;
border: 1px solid #222;
color: #999;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar th {
background: #0e0e0e;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_today {
color: #cc5500;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_highlighted_default {
background: #ffe9d2;
box-shadow: #ffb871 0 1px 4px 0 inset;
color:#000;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_highlighted_mint {
background: #c1ffc9;
box-shadow: #00dd1c 0 1px 4px 0 inset;
color:#000;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_default,
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_current,
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box >div >div.xdsoft_current {
background: #cc5500;
box-shadow: #b03e00 0 1px 3px 0 inset;
color: #000;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td:hover,
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box >div >div:hover {
color: #000 !important;
background: #007fff !important;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar th {
color: #666;
}
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_copyright { color: #333 !important }
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_copyright a { color: #111 !important }
.xdsoft_datetimepicker.xdsoft_dark .xdsoft_copyright a:hover { color: #555 !important }
.xdsoft_dark .xdsoft_time_box {
border: 1px solid #333;
}
.xdsoft_dark .xdsoft_scrollbar >.xdsoft_scroller {
background: #333 !important;
}
.xdsoft_datetimepicker .xdsoft_save_selected {
display: block;
border: 1px solid #dddddd !important;
margin-top: 5px;
width: 100%;
color: #454551;
font-size: 13px;
}
.xdsoft_datetimepicker .blue-gradient-button {
font-family: "museo-sans", "Book Antiqua", sans-serif;
font-size: 12px;
font-weight: 300;
color: #82878c;
height: 28px;
position: relative;
padding: 4px 17px 4px 33px;
border: 1px solid #d7d8da;
background: -moz-linear-gradient(top, #fff 0%, #f4f8fa 73%);
/* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(73%, #f4f8fa));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #fff 0%, #f4f8fa 73%);
/* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #fff 0%, #f4f8fa 73%);
/* Opera 11.10+ */
background: -ms-linear-gradient(top, #fff 0%, #f4f8fa 73%);
/* IE10+ */
background: linear-gradient(to bottom, #fff 0%, #f4f8fa 73%);
/* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fff', endColorstr='#f4f8fa',GradientType=0 );
/* IE6-9 */
}
.xdsoft_datetimepicker .blue-gradient-button:hover, .xdsoft_datetimepicker .blue-gradient-button:focus, .xdsoft_datetimepicker .blue-gradient-button:hover span, .xdsoft_datetimepicker .blue-gradient-button:focus span {
color: #454551;
background: -moz-linear-gradient(top, #f4f8fa 0%, #FFF 73%);
/* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f4f8fa), color-stop(73%, #FFF));
/* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #f4f8fa 0%, #FFF 73%);
/* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #f4f8fa 0%, #FFF 73%);
/* Opera 11.10+ */
background: -ms-linear-gradient(top, #f4f8fa 0%, #FFF 73%);
/* IE10+ */
background: linear-gradient(to bottom, #f4f8fa 0%, #FFF 73%);
/* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f4f8fa', endColorstr='#FFF',GradientType=0 );
/* IE6-9 */
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,227 @@
// Copyright 2017, 2018, 2019, 2020, 2021 Max H. Parke KA1RBI
//
// This file is part of OP25
//
// OP25 is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3, or (at your option)
// any later version.
//
// OP25 is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with OP25; see the file COPYING. If not, write to the Free
// Software Foundation, Inc., 51 Franklin Street, Boston, MA
// 02110-1301, USA.
//
// OP25 Logs
$(window).load(function() {
$('#loading').hide();
});
$(document).ready(function() {
$('#startDate').val(localStorage.logStart);
$('#endDate').val(localStorage.logEnd);
randCss(); // force css reload each time for dev
$('#records').text(comma(parseInt(($('#records').text()))));
$('#systems').text(comma(parseInt(($('#systems').text()))));
$('#talkgroups').text(comma(parseInt(($('#talkgroups').text()))));
$('#subs').text(comma(parseInt(($('#subs').text()))));
if (localStorage.systemSelect) {
$('#systemSelect').val(localStorage.systemSelect);
}
if (localStorage.systemSelect4) {
$('#systemSelect4').val(localStorage.systemSelect4);
}
});
$(window).load(function() {
$('#loading').hide();
});
function resetDates() {
$('#startDate').val('');
$('#endDate').val('');
$('#systemSelect').val('0');
window.localStorage.removeItem('logStart');
window.localStorage.removeItem('logEnd');
window.localStorage.removeItem('systemSelect');
}
$('#navSelect').change(function(){
console.log("shit");
var ns = $('#navSelect').val();
if (ns == '0')
return;
console.log(ns);
load_new_page1(ns);
});
// forces css to reload - helpful during dev
function randCss() {
var h, a, f;
a = document.getElementsByTagName('link');
for (h = 0; h < a.length; h++) {
f = a[h];
if (f.rel.toLowerCase().match(/stylesheet/) && f.href) {
var g = f.href.replace(/(&|\?)rnd=\d+/, '');
f.href = g + (g.match(/\?/) ? '&' : '?');
f.href += 'rnd=' + (new Date().valueOf());
}
}
}
function load_new_page1(request,param) {
var v1 = $('#resource_id').val();
tgid = $('#cc_filter_tgid').val();
suid = $('#cc_filter_suid').val();
tgid = (Number.isInteger(parseInt(tgid)) == true) ? parseInt(tgid) : 0;
suid = (Number.isInteger(parseInt(suid)) == true) ? parseInt(suid) : 0;
load_new_page('/logs', 'q=' + v1 + '&r=' + request + '&p=' + param + '&tgid=' + tgid + '&suid=' + suid);
}
//SUID and TGID 'specified' buttons!
function load_new_page0(request) {
var v1 = $('#resource_id').val();
var sysid = $('#systemSelect').val();
if (v1 == '') {
alert("Subscriber unit ID or talkgroup ID is required!");
return;
}
if (v1.split('-').length > 2) {
alert("Too many values for a range.");
return;
}
load_new_page('/logs', 'q=' + v1 + '&r=' + request + '&sysid=' + sysid);
}
function load_new_page(url, arg) {
var u = url;
if (arg)
u = u + "?" + arg;
window.open(u, "_self", "resizable,location,menubar,toolbar,scrollbars,status")
}
function sdate() {
var s = $('#startDate').val() ? new Date($('#startDate').val()) : new Date("2001/01/01 01:00");
var stime = s.getTime() / 1000;
return stime | 0;
}
function edate() {
var e = $('#endDate').val() ? new Date($('#endDate').val()) : new Date();
var etime = e.getTime() / 1000;
return etime | 0;
}
function comma(x) {
// add comma formatting to whatever you give it (xx,xxxx,xxxx)
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
function doPurge(sim) {
var kv = $('#keepVoice').prop('checked');
var bu = $('#createBackup').prop('checked');
if ($('#startDate').val() == '' || $('#endDate').val() == '') {
alert('Start date and end date are required.');
return;
}
var sd = sdate();
var ed = edate();
var sysid = $('#systemSelect').val();
window.location.href='/purge?action=purge&sd=' + sd + '&ed=' + ed + '&sysid=' + sysid + '&simulate=' + sim + '&kv=' + kv + '&bu=' + bu;
}
function addNewSystemTag() {
if ($('#newSysId').val() == '' || $('#newSysTag').val() == '') {
alert('System ID (dec) and System Tag are required.');
return;
}
var hexId = $('#newSysId').val();
var newId = parseInt(dec(hexId));
var newTag = $('#newSysTag').val()
if (! Number.isInteger(newId)) {
alert('Invalid system ID.');
return;
}
window.location.href='/asd?id=' + newId + '&tag=' + newTag;
}
function importTalkgroupTsv(cmd) {
$('#impProc').show()
if ($('#selTsv').val() == '0' || $('#systemSelect2').val() == '0') {
alert('TSV file selection and System selection are required.');
$('#impProc').hide()
return;
}
if ($('#invtsv').length){
$('#impProc').hide()
alert('The TSV is invalid!');
return;
}
var sysid = $('#systemSelect2').val();
var tsvfile = $('#selTsv').val();
window.location.href='/itt?sysid=' + sysid + '&file=' + tsvfile + '&cmd=' + cmd;
}
function deleteTags(cmd) {
if ($('#systemSelect3').val() == '0') {
alert('System selection is required.');
return;
}
sysid = $('#systemSelect3').val();
window.location.href='/delTags?sysid=' + sysid + '&cmd=' + cmd;
}
function hex(dec) {
if (!dec) return;
return dec.toString(16);
}
function dec(hex) {
if (!hex) return;
return parseInt(hex, 16);
}
function csvTable(table_id, separator = ',') { // Quick and simple export target #table_id into a csv
var rows = document.querySelectorAll('table#' + table_id + ' tr');
// Construct csv
var csv = [];
for (var i = 0; i < rows.length; i++) {
var row = [],
cols = rows[i].querySelectorAll('td, th');
for (var j = 0; j < cols.length; j++) {
// Clean innertext to remove multiple spaces and jumpline (break csv)
var data = cols[j].innerText.replace(/(\r\n|\n|\r)/gm, '').replace(/(\s\s)/gm, ' ');
// Escape double-quote with double-double-quote
data = data.replace(/"/g, '""');
// Push escaped string
row.push('"' + data + '"');
}
csv.push(row.join(separator));
}
var csv_string = csv.join('\n');
// Download it
var filename = 'export_' + table_id + '_' + new Date().toLocaleDateString() + '.csv';
var link = document.createElement('a');
link.style.display = 'none';
link.setAttribute('target', '_blank');
link.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv_string));
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,143 @@
<!--
Copyright 2017, 2018 Max H. Parke KA1RBI
Copyright 2020, 2021 Michael Rose
This file is part of OP25
OP25 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
OP25 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with OP25; see the file COPYING. If not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Boston, MA
02110-1301, USA.
-->
{% include 'base.html' %}
{% block extra_stylesheets %}
<link href="static/css/datatables/jquery.dataTables-dark.css" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="row-main">
<div class="side">
&nbsp;
</div> <!-- end side -->
<div class="main">
<div class="card mb-3 border-primary">
<h4 class="card-header">About OP25 Logs</h4>
<div class="card-body">
<p class="card-text">
<div align="center">
<div class="card border-secondary mb-3" style="max-width: 40rem; text-align: left;">
<div class="card-body">
<p class="card-text">
OP25 Logs (aka Oplog) is the OP25 sqlite3 logs database viewer.
</p>
<p class="card-text">
Copyright &copy; 2020, 2021 Max H. Parke KA1RBI<br>
Copyright &copy; 2020, 2021 Michael Rose
</p>
<p class="card-text">
OP25 Logs is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
</p>
<p class="card-text">
OP25 Logs is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
</p>
</div>
</div>
</div>
</div>
</div>
{% include 'footer-links.html' %}
</div> <!-- end main -->
<div class="side">
&nbsp;
</div>
</div> <!-- end row -->
<!-- end secondary -->
</div>
<!-- end content -->
</div>
<br>
{% endblock %}
<!-- js moved to op25.js -->
{% block extra_javascripts %}
<script src="static/js/datatables/jquery.dataTables.js"></script>
<script>
$(document).ready(function () {
$('#startDate').prop('disabled', true );
$('#endDate').prop('disabled', true );
$('#op25_esd').DataTable({
"processing": true,
"serverSide": true,
'bFilter': false,
'paging': false,
"ajax": '/esd',
"columns": [
null,
{
"data": [1],
"render": function(data, type, row, meta){
if(type === 'display'){
data = data + ' - ' + hex(data).toUpperCase();
}
return data;
}
},
null,
{
"data": [3],
"render": function(data, type, row, meta){
if(type === 'display'){
data = '<button type="button" class="btn btn-primary btn-sm" onclick="this.blur(); editTagName(' + data + ', \'' + row[2] + '\')">Edit Tag</button>&nbsp;\
<button type="button" class="btn btn-primary btn-sm" onclick="window.location.href=\'\/dsd?id=' + data + '\'">Delete</button>';
}
return data;
},
"width": "150px"
}
]
});
});
function editTagName(id, t) {
var tag = prompt("Enter new system tag:", t);
if (tag == null || tag == '') {
return;
}
window.location.href='/usd?id=' + id + '&tag=' + tag;
}
</script>
{% endblock %}

View File

@ -0,0 +1,167 @@
<!--
Copyright 2017, 2018 Max H. Parke KA1RBI
Copyright 2020, 2021 Michael Rose
This file is part of OP25
OP25 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
OP25 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with OP25; see the file COPYING. If not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Boston, MA
02110-1301, USA.
-->
<!DOCTYPE html>
<html lang="{{ request.locale_name }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
<title>OP25 - Logs</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" type="text/css" href="static/css/op25.css">
<link href="static/css/bootstrap/bootstrap-darkly.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="static/dtpick/jquery.datetimepicker.css">
<style>
</style>
{% block extra_stylesheets %} {% endblock %}
<script src="static/jquery/jquery-2.2.4.min.js"></script>
<script src="static/js/bootstrap/bootstrap.bundle.min.js"></script>
<script src="static/dtpick/dtpick2.js"></script>
<script src="static/js/op25.js"></script>
</head>
<body>
<div class="card text-white bg-primary mb-3">
<div class="card-body">
<table style="width: 100%;">
<tr>
<td>
<a style="width: 200px;" class="nav-link dropdown-toggle navbar-brand text-white" data-bs-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false"><b>OP25 - Logs</b></a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{{ url_for('home') }}">Home</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{{ url_for('logs') }}?r=total_tgid">Total Talkgroup Voice Activity</a>
<a class="dropdown-item" href="{{ url_for('logs') }}?r=call_detail">Call Detail</a>
<a class="dropdown-item" href="{{ url_for('logs') }}?r=joins">Join Activity</a>
<div class="dropdown-divider"></div>
{% for s in params['ekeys'] %}
<a class="dropdown-item" href="#" onclick="javascript:load_new_page1('cc_event', '{{ s }}');"> {{ s|replace("_", " ") }}</a> {% endfor %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{{ url_for('editsys') }}">Edit System Tags</a>
<a class="dropdown-item" href="{{ url_for('edit_tags') }}?cmd=tgid">Update Talkgroup Tags</a>
<a class="dropdown-item" href="{{ url_for('edit_tags') }}?cmd=unit">Update Subscriber Tags</a>
<a class="dropdown-item" href="{{ url_for('switch_db') }}">Backup & Switch Database</a>
<a class="dropdown-item text-danger" href="{{ url_for('purge') }}">Purge Database</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{{ url_for('about') }}">About</a>
</div>
</td>
<td>
<div style="float: right;">
<b>System</b>
<select id="systemSelect" style="">
<option value="0" selected>All</option>
</select>
&nbsp;&nbsp;&nbsp;&nbsp;
<span>Start <input class="sel-date" type="text" id="startDate"></span>
<span>End <input class="sel-date" type="text" id="endDate">&nbsp;&nbsp;&nbsp;</span>
<button style="width: 75px;" class="btn btn-info btn-sm" id="btnReset" onclick="location.reload();">Refresh</button>&nbsp;&nbsp;
<button style="width: 75px;" class="btn btn-info btn-sm" id="btnClear" onclick="resetDates();">Clear</button>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="container" style="margin-top: 0px; margin-bottom: 15px;">
<div align="center">
<a href="/"><img src="static/op25-dark-h.png" title="OP25"></a>
</div>
{% block content %} {% endblock %}
</div>
{% block extra_javascripts %}
<script>
$('#startDate').datetimepicker({
inline:false,
});
$('#endDate').datetimepicker({
inline:false,
});
$('#startDate').change( function(){
localStorage.logStart = $('#startDate').val();
});
$('#endDate').change( function(){
localStorage.logEnd = $('#endDate').val();
});
{% if sysList is not none %}
{% for i in sysList %}
{% if i.tag is not none %}
$('#systemSelect').append(new Option('{{ i.tag }} - {{ i.sysid }} - 0x{{ ( '%0x' % i.sysid ).upper() }}', '{{ i.sysid }}'));
{% else %}
$('#systemSelect').append(new Option('{{ i.sysid }} - 0x{{ ( '%0x' % i.sysid ).upper() }}', '{{ i.sysid }}'));
{% endif %}
{% endfor %}
{% endif %}
$('#systemSelect').change( function(){
localStorage.systemSelect = $('#systemSelect').val();
});
$('#navSelect').append(new Option('Home', '4'));
$('#navSelect').append(new Option('Total Talkgroup Voice Activity', '1'));
$('#navSelect').append(new Option('Call Detail', '2'));
$('#navSelect').append(new Option('Join Activity', '3'));
{% for s in params['ekeys'] %}
$('#navSelect').append(new Option( '{{ s }}', '{{ s }}' ));
{% endfor %}
// pretty sure this is not used anymore 7/2
$('#navSelect').change(function(){
var ns = $('#navSelect').val();
if (ns == '0')
return;
if (ns == '1') {
window.location.href='/logs?r=total_tgid';
return;
}
if (ns == '2'){
window.location.href='/logs?r=call_detail';
return;
}
if (ns == '3'){
window.location.href='/logs?r=joins';
return;
}
if (ns == '4'){
window.location.href='/';
return;
}
load_new_page1('cc_event', ns);
});
</script>
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,53 @@
<div class="card mb-3 border-primary">
<h4 class="card-header">Database Statistics</h4>
<div class="card-body">
<div class="card mb-3 bg-dark border-primary">
<table border="0" style="width: 100%;" class="border-primary">
<tr>
<td style="vertical-align: top; padding: 0px;">
<div class="card mb-3 bg-dark border-dark">
<span class="card-header">Records</span>
<div class="card-body">
<table style="padding: 5px; width: 100%; border="0">
<tr><td>Total Records: <b><span id="records">{{ dbstats[0] }}</span></b>
&nbsp;&nbsp;&nbsp;&nbsp;
Talkgroups: <b><span id="talkgroups">{{ dbstats[2] }}</span></b>
&nbsp;&nbsp;&nbsp;&nbsp;
Subscribers: <b><span id="subs">{{ dbstats[3] }}</span></b>
</td></tr>
<tr>
<td>First: <b><span id="firstDate"> {{ dbstats[4] }} </b></span></td>
</tr><tr>
<td>Last: <b><span id="lastDate"> {{ dbstats[5] }} </b></span></td>
</tr><tr>
<td>Database Size: <b><span id="dbSize"> {{ dbstats[6] }} </b></span></td>
</tr><tr>
{% set fn = dbstats[7].split('/') %}
<td>Database File: <b><span id="dbFile"> {{ fn|last }} </b> <a href="#" title=" {{ dbstats[7] }}">?</a></span></td>
</tr>
</table>
</div>
</div>
</td>
<td style="vertical-align: top; padding: 0px;">
<div class="card mb-3 bg-dark border-dark">
<span class="card-header">Logged Systems: <b><span id="systems">{{ dbstats[1] }}</span></b><br></span>
<div class="card-body">
<table style="padding: 5px; width: 100%;" class="table table-hover">
{% for i in sysList %}
<tr>
{% if i.tag is not none %}
<td style="padding: 2px;"> {{ i.sysid }} </td><td style="padding: 2px;"> 0x{{ ( '%0x' % i.sysid ).upper() }} </td><td style="padding: 2px;"> {{ i.tag }} </td>
{% else %}
<td style="padding: 2px;"> {{ i.sysid }}</td><td style="padding: 2px;"> 0x{{ ( '%0x' % i.sysid ).upper() }} </td><td style="padding: 2px;"> &mdash; </td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
</div>
</td></tr></table>
</div>
</div>
</div>

View File

@ -0,0 +1,293 @@
<!--
Copyright 2017, 2018 Max H. Parke KA1RBI
Copyright 2020, 2021 Michael Rose
This file is part of OP25
OP25 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
OP25 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with OP25; see the file COPYING. If not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Boston, MA
02110-1301, USA.
-->
{% include 'base.html' %}
{% block extra_stylesheets %}
<link href="static/css/datatables/jquery.dataTables-dark.css" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="row-main">
<div class="side">
&nbsp;
</div> <!-- end side -->
<div class="main">
<div class="card mb-3 border-primary">
{% if cmd == 'tgid' %}
<h4 class="card-header">Talkgroup Tags</h4>
{% elif cmd == 'unit' %}
<h4 class="card-header">Subscriber Tags</h4>
{% endif %}
<div class="card-body">
<div class="card mb-3 border-secondary">
<h5 class="card-header">Import Tool</h4>
<div class="card-body">
<p class="card-text">
This tool imports tags from the selected TSV and associates them with the
selected system. Existing System ID and Talkgroup or Subscriber ID combinations will be overwritten by the tag values in the TSV file.
To add new tags: Add in OP25 Web UI, then import them here. Wildcard entries are not imported.
</p>
<label for="selTsv">Choose TSV file:</label>
<select name="selTsv" id="selTsv">
<option value='0' >Select...</option>
{% for i in tsvs %}
{% if '.tsv' in i and '._' not in i %}
<option value="{{ i.split('/../')[-1] }}">{{ i.split('/../')[-1] }}</option>
{% endif %}
{% endfor %}
</select>
&nbsp;&nbsp;&nbsp;
<label for="systemSelect2">Choose System: </label>
<select id="systemSelect2">
<option value="0" selected>Select...</option>
</select>
<br><br>
<button class="btn btn-primary" onclick="this.blur(); inspectTsv();">Inspect TSV</button>
<br>
<div id="inspect" style="display: none;">
<br>
<div id="inspectText" style="width: 100%; height: 225px; overflow: auto;"></div>
<br>
<button class="btn btn-primary" onclick="this.blur(); importTalkgroupTsv('{{ cmd }}');">Import TSV</button>
&nbsp;&nbsp;<img id="impProc" src="static/loading.gif" style="height: 20px; display: none;" alt="loading">
</div>
{% if session['sm'] == 3 %}
<br>
<div class="alert alert-dismissible alert-success">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>Import completed.</strong><br><br>
Existing records updated:<b> {{ session['imp_results'][0] }}</b> &nbsp;&nbsp;&nbsp;&nbsp;
New records added:<b> {{ session['imp_results'][1] }} </b> &nbsp;&nbsp;&nbsp;&nbsp;
Duplicate records corrected:<b> {{ session['imp_results'][2] }} </b>
</div>
{{ clear_sm() }}
{% endif %}
{% if session['sm'] == 4 %}
<br>
<div class="alert alert-dismissible alert-primary">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>Tags deleted.</strong><br><br>
</div>
{{ clear_sm() }}
{% endif %}
</div>
</div>
<hr>
<div class="card mb-3 border-secondary">
<h5 class="card-header">Editor</h4>
<div class="card-body">
<div align="right">
<label for="systemSelect4">Filter by system: </label>
<select id="systemSelect4">
<option value="0" selected>All</option>
</select><br><br>
<button style="width: 75px;" class="btn btn-primary btn-sm" id="applyFilter" onclick="location.reload();">Apply</button>
<br><br></div>
{% if session['sm'] == 1 %}
<div class="alert alert-dismissible alert-success">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>Edit completed.</strong>
</div>
{{ clear_sm() }}
{% endif %}
{% if session['sm'] == 2 %}
<div class="alert alert-dismissible alert-warning">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>Record deleted.</strong>
</div>
{{ clear_sm() }}
{% endif %}
<table id="op25_esd" class="display" cellspacing="0" width="100%">
<thead>
<tr>
{% if cmd == 'tgid' %}
<th>Record ID</th>
<th>System ID</th>
<th>Talkgroup ID</th>
<th>Talkgroup Tag</th>
<th>Actions</th>
{% elif cmd == 'unit' %}
<th>Record ID</th>
<th>System ID</th>
<th>Subscriber ID</th>
<th>Subscriber Tag</th>
<th>Actions</th>
{% endif %}
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card mb-3 border-danger">
<h5 class="card-header">Delete Tags</h4>
<div class="card-body">
Delete all
{% if cmd == 'tgid' %}
talkgroup
{% elif cmd == 'unit' %}
subscriber
{% endif %}
tags associated with a system. You cannot undo this action!
<br><br>
<label for="systemSelect3">Choose System: </label>
<select id="systemSelect3">
<option value="0" selected>Select...</option>
</select>
<br><br>
<div align="center">
<button id="btnPurge" class="btn btn-danger" onclick="this.blur(); deleteTags('{{ cmd }}');">Delete
{% if cmd == 'tgid' %}
Talkgroup
{% elif cmd == 'unit' %}
Subscriber
{% endif %}
Tags
</button>
</div>
</div>
</div>
{% include 'footer-links.html' %}
</div> <!-- end main -->
<div class="side">
&nbsp;
</div>
</div> <!-- end row -->
<br>
{% endblock %}
{% block extra_javascripts %}
<script src="static/js/datatables/jquery.dataTables.js"></script>
<script>
$(document).ready(function () {
$('#startDate').prop('disabled', true );
$('#endDate').prop('disabled', true );
var sysid = $('#systemSelect4').val();
$('#op25_esd').DataTable({
"processing": true,
"serverSide": true,
'bFilter': true,
'paging': true,
"ajax": {
"url": '/edittg',
"data": { "sysid": sysid,
"cmd": '{{ cmd }}',
}
},
"columns": [
{ "visible": false },
{
"data": [1],
"render": function(data, type, row, meta){
if(type === 'display'){
data = data + ' - ' + hex(data).toUpperCase();
}
return data;
}
},
null,
null,
{
"data": [3],
"render": function(data, type, row, meta){
if(type === 'display'){
data = '<button type="button" class="btn btn-primary btn-sm" onclick="this.blur(); editTagName(' + row[0] + ', \'' + row[3] + '\')">Edit Tag</button>&nbsp;\
<button type="button" class="btn btn-primary btn-sm" onclick="window.location.href=\'\/dtd?cmd={{ cmd }}&id=' + row[0] + '\'">Delete</button>';
}
return data;
},
"width": "150px"
}
]
});
});
function editTagName(id, t) {
var tag = prompt("Enter new tag:", t);
if (tag == null || tag == '')
return;
window.location.href='/utd?id=' + id + '&tag=' + tag + '&cmd={{ cmd }}';
}
function inspectTsv() {
var file = $('#selTsv').val();
if (file == '0')
return;
f = '/inspect?file=' + file;
$.ajax({
url : f,
type : 'GET',
success : popInsp,
error : function(XMLHttpRequest, textStatus, errorThrown) {alert('Error: \n\nFile:' + f + '\n\n' + errorThrown + '\n\n');}
});
}
function popInsp(h) {
$('#inspect').show();
$('#inspectText').html(h).scrollTop(0);
}
{% if systems is not none %}
{% for i in systems %}
{% if i.tag is not none %}
$('#systemSelect2').append(new Option('{{ i.sysid }} - 0x{{ ( '%0x' % i.sysid ).upper() }} - {{ i.tag }}', '{{ i.sysid }}'));
$('#systemSelect3').append(new Option('{{ i.sysid }} - 0x{{ ( '%0x' % i.sysid ).upper() }} - {{ i.tag }}', '{{ i.sysid }}'));
$('#systemSelect4').append(new Option('{{ i.sysid }} - 0x{{ ( '%0x' % i.sysid ).upper() }} - {{ i.tag }}', '{{ i.sysid }}'));
{% else %}
$('#systemSelect2').append(new Option('{{ i.sysid }} - 0x{{ ( '%0x' % i.sysid ).upper() }}', '{{ i.sysid }}'));
$('#systemSelect3').append(new Option('{{ i.sysid }} - 0x{{ ( '%0x' % i.sysid ).upper() }}', '{{ i.sysid }}'));
$('#systemSelect4').append(new Option('{{ i.sysid }} - 0x{{ ( '%0x' % i.sysid ).upper() }}', '{{ i.sysid }}'));
{% endif %}
{% endfor %}
{% endif %}
$('#systemSelect4').change( function(){
localStorage.systemSelect4 = $('#systemSelect4').val();
});
</script>
{% endblock %}

View File

@ -0,0 +1,151 @@
<!--
Copyright 2017, 2018 Max H. Parke KA1RBI
Copyright 2020, 2021 Michael Rose
This file is part of OP25
OP25 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
OP25 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with OP25; see the file COPYING. If not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Boston, MA
02110-1301, USA.
-->
{% include 'base.html' %}
{% block extra_stylesheets %}
<link href="static/css/datatables/jquery.dataTables-dark.css" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="row-main">
<div class="side">
&nbsp;
</div> <!-- end side -->
<div class="main">
<div class="card mb-3 border-primary">
<h4 class="card-header">System Tags</h4>
<div class="card-body">
<p class="card-text">
</p>
<table id="op25_esd" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th>Record ID</th>
<th>System ID</th>
<th>System Name</th>
<th>Actions</th>
</tr>
</thead>
<tbody></tbody>
</table>
<br><br>
<table style="width: 65%; padding: 0px;" class="border-primary">
<tr>
<td style="vertical-align: top;">
<div class="form-floating mb-3 primary">
<input type="text" width="10" class="form-control" id="newSysId" placeholder="">
<label for="floatingInput">System ID (hex)</label>
</div>
</td>
<td>
<div class="form-floating mb-3">
<input type="text" class="form-control" size="25" id="newSysTag" placeholder="">
<label for="floatingInput">System Tag</label>
</div>
</td>
</tr>
<tr>
<td style="text-align: left; vertical-align: top;">
<button class="btn btn-primary" onclick="this.blur; addNewSystemTag();">Add New</button>
</td>
</table>
</div>
</div>
{% include 'footer-links.html' %}
</div> <!-- end main -->
<div class="side">
&nbsp;
</div>
</div> <!-- end row -->
<!-- end secondary -->
</div>
<!-- end content -->
</div>
<br>
{% endblock %}
<!-- js moved to op25.js -->
{% block extra_javascripts %}
<script src="static/js/datatables/jquery.dataTables.js"></script>
<script>
$(document).ready(function () {
$('#startDate').prop('disabled', true );
$('#endDate').prop('disabled', true );
$('#op25_esd').DataTable({
"processing": true,
"serverSide": true,
'bFilter': false,
'paging': false,
"ajax": '/esd',
"columns": [
null,
{
"data": [1],
"render": function(data, type, row, meta){
if(type === 'display'){
data = data + ' - ' + hex(data).toUpperCase();
}
return data;
}
},
null,
{
"data": [3],
"render": function(data, type, row, meta){
if(type === 'display'){
data = '<button type="button" class="btn btn-primary btn-sm" onclick="this.blur(); editTagName(' + data + ', \'' + row[2] + '\')">Edit Tag</button>&nbsp;\
<button type="button" class="btn btn-primary btn-sm" onclick="window.location.href=\'\/dsd?id=' + data + '\'">Delete</button>';
}
return data;
},
"width": "150px"
}
]
});
});
function editTagName(id, t) {
var tag = prompt("Enter new system tag:", t);
if (tag == null || tag == '') {
return;
}
window.location.href='/usd?id=' + id + '&tag=' + tag;
}
</script>
{% endblock %}

View File

@ -0,0 +1,184 @@
<!--
Copyright 2017, 2018 Max H. Parke KA1RBI
Copyright 2020, 2021 Michael Rose
This file is part of OP25
OP25 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
OP25 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with OP25; see the file COPYING. If not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Boston, MA
02110-1301, USA.
-->
{% block content %}
<html lang="{{ request.locale_name }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
<title>OP25 - Logs</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" type="text/css" href="static/css/op25.css">
<link href="static/css/bootstrap/bootstrap-darkly.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="static/dtpick/jquery.datetimepicker.css">
<style>
</style>
{% block extra_stylesheets %} {% endblock %}
<script src="static/jquery/jquery-2.2.4.min.js"></script>
<script src="static/js/bootstrap/bootstrap.bundle.min.js"></script>
<script src="static/dtpick/dtpick2.js"></script>
<script src="static/js/op25.js"></script>
</head>
<div id="container">
<div id="header">
<p>&nbsp;</p>
</div>
<div id="primary">
&nbsp;
</div>
<div id="content" align="center">
<div class="card mb-3 border-primary" style="max-width: 60rem; text-align: left;">
<h4 class="card-header bg-danger">OP25 Logs - Database Error (Code {{ code }})</h4>
<div class="card-body">
{% if code == 1 %}
<div class="alert alert-dismissible">
<strong>Database file does not exist.</strong> <br><br> {{ file }}</span> <Br><Br>File not found.<br><br>
</div>
{% endif %}
{% if code == 2 %}
<div class="alert alert-dismissible">
<strong>Database file is too small. </strong> <br><Br> {{ file }} <br><br>Attributes do not conform.
</div>
{% endif %}
{% if code == 4 %}
<div class="alert alert-dismissible">
<strong>Database contains no data. </strong> <br><Br> {{ file }} <br><br> Database structure is good, but no data was found. <br><br>0 rows in table 'data_store'.
</div>
{% endif %}
</div><br>
{% if code == 5 %}
<div class="alert alert-dismissible">
<strong>Database access error. </strong> <br><Br> {{ file }} <br><br> Database might be locked or in use by another process (OP25). <br><br>
Source: {{ source }}
</div>
{% endif %}
</div><br>
<div align="center">
<button class="btnMain btn btn-outline-info" onclick="window.location.href='/'">Try Again</button>
<br><br>
</div>
{% if code == 5 %}
<div class="card mb-3 border-primary" style="max-width: 60rem; text-align: left;">
<h4 class="card-header bg-secondary">Traceback</h4>
<div class="card-body">
{{ e }} <br><br>
{{ err }}
</div></div>
{% endif %}
{% if code != 5 %}
<div class="card mb-3 border-primary">
<h4 class="card-header bg-primary">../op25/gr-op25_repeater/apps/README</h4>
<div class="card-body">
<h4>Setup SQL Log Database (Optional)</h4>
<p>
This addition provides a permanent server-side log of control channel
activity via logging to an SQL database. See the next section for details
on installing and using the log viewer.
</p>
<p>
1. Make sure that sqlite3 is installed in python
</p>
<p>
2. Initialize DB (any existing DB data will be destroyed)
</p>
<p>
WARNING: OP25 MUST NOT BE RUNNING DURING THIS STEP
</p>
<p>
op25/.../apps$ python sql_dbi.py reset_db
</p>
<p>
3. Import talkgroups tags file
</p>
<p>
op25/.../apps$ python sql_dbi.py import_tgid tags.tsv
</p>
<p>
also, import the radio ID tags file (optional)
</p>
<p>
op25/.../apps$ python sql_dbi.py import_unit radio-tags.tsv
</p>
<p>
import the System ID tags file (see below)
</p>
<p>
op25/.../apps$ python sql_dbi.py import_sysid sysid-tags.tsv
</p>
<p>
The sysid tags must be a TSV file containing two columns
column 1 is the P25 trunked sysid (int, decimal)
colunn 2 is the System Name (text)
(Note: there is no header row line in this TSV file).
</p>
<p>
4. Run op25 as usual. Logfile data should be inserted into DB in real time
and you should be able to view activity via the OP25 http console (once
the flask/datatables app has been set up; see next section).
</p>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div id="secondary">
&nbsp;
</div>
</div>
</div>
<!-- end secondary -->
</div>
<!-- end content -->
</div>
<br>
{% endblock %}
{% block extra_javascripts %}
{% endblock %}

View File

@ -0,0 +1,16 @@
<!-- footer links -->
<div class="card border-primary mb-3">
<div class="card-body">
<div align="center">
<a href="{{ url_for('home') }}">Home</a>&nbsp;&nbsp;&nbsp;
<a href="{{ url_for('editsys') }}">System Tags</a>&nbsp;&nbsp;&nbsp;
<a href="{{ url_for('edit_tags') }}?cmd=tgid">Talkgroup Tags</a>&nbsp;&nbsp;&nbsp;
<a href="{{ url_for('edit_tags') }}?cmd=unit">Unit Tags</a>&nbsp;&nbsp;&nbsp;
<a href="{{ url_for('purge') }}">Purge Database</a>&nbsp;&nbsp;&nbsp;
<a href="{{ url_for('about') }}">About</a>
<br>
Server time: {{ t_loc() }} <br>
07.23.2021
</div>
</div>
</div>

View File

@ -0,0 +1,129 @@
<!--
Copyright 2017, 2018 Max H. Parke KA1RBI
Copyright 2020, 2021 Michael Rose
This file is part of OP25
OP25 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
OP25 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with OP25; see the file COPYING. If not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Boston, MA
02110-1301, USA.
-->
{% include 'base.html' %}
{% block content %}
<div class="row-main">
<div class="side">
&nbsp;
</div> <!-- end side -->
<div class="main">
{% include "dbstats.html" %}
<div class="card mb-3 border-primary">
<h4 class="card-header">Activity and Counts by Subscriber or Talkgroup</h4>
<div class="card-body">
<p class="card-text">
<button class="btnMain btn btn-outline-info" onclick="window.location.href='/logs?r=total_tgid'">Total Talkgroup<br>Voice Activity</button>
&nbsp;&nbsp;
<button class="btnMain btn btn-outline-info" onclick="window.location.href='/logs?r=call_detail'">Call<br>Detail</button>
&nbsp;&nbsp;
<button class="btnMain btn btn-outline-info" onclick="window.location.href='/logs?r=joins'">Join<br>Activity</button>
<hr style="height: 2px;">
<!-- <input class="op-input" style="height: 62px; text-align: center; width: 210px; border: 1px solid orange; background-color: #333; color:#ccc;" placeholder="Enter SU or Talkgroup ID" type="text" id="resource_id"</input> -->
<div class="form-floating mb-3 primary" style="width: 215px;">
<input type="text" width="10" class="form-control" style="height: 62px;" id="resource_id" placeholder="">
<label for="floatingInput">TGID or SUID</label>
</div>
<button class="btnMain btn btn-outline-warning" onclick="this.blur(); load_new_page0('tgid');">SU ID Activity for Specified TGID</button>
&nbsp;&nbsp;
<button class="btnMain btn btn-outline-warning" onclick="this.blur(); load_new_page0('su');">Count of Calls by TGID for Specified SU ID</button>
<p><br>Note: The ID you enter can define a range of IDs to search, for example:
<br>
<ul>
<li>1234000-1234599 to search specified range</li>
</li>
<li>1234??? Search for matches between 1234000 and 1234999</li>
</ul>
</p>
</div>
</div>
<div class="card mb-3 border-primary">
<h4 class="card-header">Control Channel Events</h4>
<div class="card-body">
Filter by talkgroup ID, subscriber ID, or both (optional):
<table style="width: 400px; padding: 0px;">
<tr>
<td style="vertical-align: top;">
<div class="form-floating mb-3 primary">
<input type="text" width="10" class="form-control" id="cc_filter_tgid" style="width: 150px;" placeholder="">
<label for="floatingInput">Talkgroup ID</label>
</div>
</td>
<td>
<div class="form-floating mb-3">
<input type="text" class="form-control" size="25" id="cc_filter_suid" style="width: 150px;" placeholder="">
<label for="floatingInput">Subscriber ID</label>
</div>
</td>
<td>&nbsp;&nbsp;
<button class="btn btn-primary" onclick="this.blur(); clrcc();">Clear</button>
</td>
</tr>
</table>
<br>
<table>
{% for i in params['ekeys'] %}
<tr>
<td><button class="btnMain btn btn-outline-info" style="height: 38px;" onclick="this.blur(); load_new_page1('cc_event', '{{ i }}');">{{ i|replace("_", " ") }}</button></td>
<td>&nbsp;{{ params['cc_desc'][i] }} </td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% include 'footer-links.html' %}
</div> <!-- end main -->
<div class="side">
&nbsp;
</div>
</div> <!-- end row -->
<br>
{% endblock %}
<!-- js moved to op25.js -->
{% block extra_javascripts %}
<script>
function clrcc() {
$('#cc_filter_tgid').val('');
$('#cc_filter_suid').val('');
}
</script>
{% endblock %}

View File

@ -0,0 +1,18 @@
<!-- Import TSV inspection -->
{% if i|length == 0 %}
<div class="alert alert-dismissible alert-danger" id="invtsv">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>Invalid TSV</strong><br><br> The TSV is not valid for import.
</div>
{% else %}
<div class="text-warning">Records: <b> {{ i|length }} </b></div><br>
<table class="table table-hover">
<th>ID</th>
<th>Tag</th>
<th>Priority/Color</th>
{% for s in i %}
<tr><td style="padding 2px; width: 150px;"> {{ s[0] }} </td><td style="padding 2px; width: 350px;"> {{ s[1] }} </td><td style="padding 2px;"> {{ s[2] }}</td></tr>
{% endfor %}
</table>
<br>
{% endif %}

View File

@ -0,0 +1,212 @@
<!--
Copyright 2017, 2018 Max H. Parke KA1RBI
Copyright 2020, 2021 Michael Rose
This file is part of OP25
OP25 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
OP25 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with OP25; see the file COPYING. If not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Boston, MA
02110-1301, USA.
-->
{% include 'base.html' %}
{% block extra_stylesheets %}
<link href="static/css/datatables/jquery.dataTables-dark.css" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="row-main">
<div class="main">
<br>
<br>
{% if params['r'] == 'su' %}
<h3>Count by Unit for SU ID {{ params['q'] }} {{ tag }}</h3>
{% elif params['r'] == 'joins' %}
<h3>Group Join Detail</h3>
{% elif params['r'] == 'cc_event' %}
<h3>CC Event Type: {{ params['cc_desc'][ params['p']] }}</h3>
{% if params['tgid'] != '0' or params['suid'] != '0' %}
Filtered by:
{% endif %}
{% if params['tgid'] != '0' %}
Talkgroup ID = {{ params['tgid'] }} &nbsp;&nbsp;
{% endif %}
{% if params['suid'] != '0' %}
Source ID = {{ params['suid'] }}
{% endif %}
{% elif params['r'] == 'total_tgid' %}
<h3>Total Talkgroup Voice Activity</h3>
{% elif params['r'] == 'call_detail' %}
<h3>Call Detail</h3>
Includes opcodes 0x00 and 0x02
{% else %}
<h3>Count by Unit for Talkgroup ID {{ params['q'] }} {{ tag }}</h3>
{% endif %}
<hr>
<table id="op25_logs" class="display" cellspacing="0" width="100%">
<thead>
<tr>
{% if params['r'] == 'tgid' %}
<th>Subscriber ID</th>
<th>Subscriber Tag</th>
<th>Count</th>
<th>Last Seen</th>
{% elif params['r'] == 'total_tgid' %}
<th>System ID</th>
<th>System</th>
<th>Talkgroup ID</th>
<th>Talkgroup</th>
<th>Count</th>
{% elif params['r'] == 'call_detail' %}
<th>Time</th>
<th>Opcode</th>
<th>System ID</th>
<th>System</th>
<th>Talkgrou ID</th>
<th>Talkgroup</th>
<th>Source ID</th>
<th>Source</th>
<th>Frequency</th>
{% elif params['r'] == 'joins' %}
<th>Time</th>
<th>Opcode</th>
<th>System ID</th>
<th>System</th>
<th>RV</th>
<th>Talkgrou ID</th>
<th>Talkgroup</th>
<th>Source ID</th>
<th>Source</th>
{% elif params['r'] == 'calls' %}
<th>Time</th>
<th>Sysid</th>
<th>Tgid</th>
<th>Talkgroup</th>
<th>Frequency</th>
<th>SU ID</th>
{% elif params['r'] == 'cc_event' %}
{% for i in params['ckeys'] %}
<th> {{ i }} </th>
{% endfor %}
{% else %}
<th>Talkgroup</th>
<th>TGID</th>
<th>Count</th>
{% endif %}
</tr>
</thead>
<tbody></tbody>
</table>
<button onclick="this.blur(); csvTable('op25_logs')" class="btn btn-light btn-sm" title="Export current view to CSV.">Export CSV</button>
</div>
</div>
{% endblock %}
{% block extra_javascripts %}
<!-- <script src="https://cdn.datatables.net/1.10.10/js/jquery.dataTables.min.js"></script> -->
<script src="static/js/datatables/jquery.dataTables.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function () {
var sd = sdate();
var ed = edate();
var sysid = $('#systemSelect').val();
var filter_tgid = {%+ if params['tgid'] is defined %}
{{ params['tgid'] }};
{%+ else %}
0;
{%+ endif %}
var filter_suid = {%+ if params['suid'] is defined %}
{{ params['suid'] }};
{%+ else %}
0;
{%+ endif %}
console.log('filter_tgid=' + filter_tgid);
console.log(typeof filter_tgid);
console.log('filter_suid=' + filter_suid);
console.log(typeof filter_suid);
var table = $('#op25_logs').DataTable({
"lengthMenu": [[10, 25, 50, 100, 500, 1000, 2500], [10, 25, 50, 100, 500, '1,000', '2,500']],
"processing": true,
"serverSide": true,
{% if params['p'] == 'grp_v_ch_grant' %}
"columns": [
null,
null,
null,
null,
{ render: function(data){ return data / 1000000; }},
null,
null,
null,
null
],
{% endif %}
{% if params['p'] == 'mot_grg_cn_grant' %}
"columns": [
null,
null,
null,
{ render: function(data){ return data / 1000000; }},
null,
null,
null,
null
],
{% endif %}
{% if params['r'] == 'call_detail' %}
"columns": [
null,
null,
null,
null,
null,
null,
null,
null,
{ render: function(data){ return data / 1000000; }},
],
{% endif %}
"ajax": {
"url": "{{ url_for('data') }}",
"data": {
"host_rid": "{{ params['q'] }}",
"host_function_type": "{{ params['r'] }}",
"host_function_param": "{{ params['p'] }}",
"sdate": sd,
"edate": ed,
"sysid": sysid,
"tgid": filter_tgid,
"suid": filter_suid
}
}
});
});
</script>
{% endblock %}
<br><br><br>
{% include 'footer-links.html' %}

View File

@ -0,0 +1,123 @@
<!--
Copyright 2017, 2018 Max H. Parke KA1RBI
Copyright 2020, 2021 Michael Rose
This file is part of OP25
OP25 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
OP25 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with OP25; see the file COPYING. If not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Boston, MA
02110-1301, USA.
-->
{% include 'base.html' %}
{% block content %}
<div id="loading">
<img id="loading-image" src="static/loading.gif" height="35px" alt="Loading..." />
<br>Processing...
</div>
<div class="row-main">
<div class="side">
&nbsp;
</div> <!-- end side -->
<div class="main">
{% include "dbstats.html" %}
<div class="card mb-3 border-primary">
<h4 class="card-header bg-danger">Purge Database</h4>
<div class="card-body">
{% if successMessage == 1 %}
<div class="alert alert-dismissible alert-primary">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>Operation completed.</strong> <span id="recCount">{{ recCount }}</span> records have been deleted.<br><br>Executed query:<br><br> {{ dispQuery }}</a>
{% if params['bu'] == 'true' %}
<br><br>Backup file created: {{ destfile }}
{% endif %}
</div>
{% endif %}
{% if successMessage == 2 %}
<div class="alert alert-dismissible alert-info">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>Simulated operation completed.</strong> <span id="recCount">{{ recCount }}</span> records will be deleted.<br><br>Simulated query:<br><br> {{ dispQuery }}</a>
{% if params['bu'] == 'true' %}
<br><br>Simulated backup file created: {{ destfile }}
{% endif %}
</div>
{% endif %}
To purge records from the database, select the system, start date and end date above, then click Purge Database. Talkgroup and subscriber tags are not affected.
<Br><Br>
To prevent accidental data loss, a start date and end date are required.
<Br><Br>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="keepVoice" checked="checked">
<label class="form-check-label" for="keepVoice"><b>Keep Voice Channel Grant Data</b></label>&nbsp;&nbsp;
When selected, all voice channel grant data will be saved.
<br>
<input class="form-check-input" type="checkbox" id="createBackup">
<label class="form-check-label" for="createBackup"><b>Create Database Backup File</b></label>&nbsp;&nbsp;
Create a backup of the database before purging.
</div>
<br>
<span class="text-danger"><b>FINAL WARNING:</b></span> Once you click "Purge Database", the action cannot be undone.</span></b><br><br>
<div align="center">
<button id="btnPurge" class="btnMain btn btn-danger" onclick="$('#processing').show(); purgeBtn(); this.blur; doPurge(false);">Purge<br>Database</button>
<button class="btnMain btn btn-warning" onclick="this.blur; doPurge(true);">Simulate Purge and<br>Display Query</button>
<button class="btnMain btn btn-success" onclick="this.blur; window.location.href='/'">Cancel<br>Purge</button>
</div><br>
<div align="center" id="processing" style="display: none;">
<img src="static/loading.gif" style="height: 20px;" alt="loading">
<br>Processing...<br>
</div>
</div>
</div>
{% include 'footer-links.html' %}
</div> <!-- end main -->
<div class="side">
&nbsp;
</div>
</div> <!-- end row -->
{% endblock %}
<!-- js moved to op25.js -->
<script>
function purgeBtn() {
// full page modal while loading, or the little Processing icon below the buttons??
// $('#loading').show();
}
</script>
{% block extra_javascripts %}
<script>
x = $('#recCount').html();
$('#recCount').html(comma(x));
</script>
{% endblock %}

View File

@ -0,0 +1,129 @@
<!--
Copyright 2017, 2018 Max H. Parke KA1RBI
Copyright 2020, 2021 Michael Rose
This file is part of OP25
OP25 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
OP25 is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with OP25; see the file COPYING. If not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Boston, MA
02110-1301, USA.
-->
{% include 'base.html' %}
{% block extra_stylesheets %}
<link href="static/css/datatables/jquery.dataTables-dark.css" rel="stylesheet">
{% endblock %}
{% block content %}
<div class="row-main">
<div class="side">
&nbsp;
</div> <!-- end side -->
<div class="main">
<div class="card mb-3 border-primary">
<div class="card-body">
<div class="card mb-3 border-secondary">
<h5 class="card-header">Switch Database</h4>
<div class="card-body">
<p class="card-text">
Switch OP25 Logs database. This does not affect live sql logging in OP25. Database selection will persist with Flask session.<br>
</p>
<br><div align="center">
Current database: {{ curr_file }}<br><br>
<label for="selTsv">Choose db file:</label>
<select name="seldb" id="seldb">
<option value='0' >Select...</option>
{% for i in files %}
{% if '.db' in i and '._' not in i %}
<option value="{{ i }}">{{ i }}</option>
{% endif %}
{% endfor %}
</select>
<br><br>
<Br>
<button class="btn btn-primary" onclick="this.blur(); switch_database();">Swtich Database</button>
<button class="btn btn-primary" onclick="this.blur(); window.location.href='/'">Cancel</button>
</div>
</div>
</div>
<div class="card mb-3 border-secondary">
<h5 class="card-header">Create Backup of Current Database</h4>
<div class="card-body">
<p class="card-text">
<div align="center">
Current database: {{ curr_file }}<br><br>
<button class="btn btn-primary" onclick="this.blur(); create_backup();">Create Backup Now</button>
</div>
<br>
{% if sm == 1 %}
<br>
<div class="alert alert-dismissible alert-success">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
<strong>Backup Created.</strong><br><br>
New backup: <b> {{ destfile }}</b>
</div>
{{ clear_sm() }}
{% endif %}
</div>
</div>
</div>
</div>
{% include 'footer-links.html' %}
</div> <!-- end main -->
<div class="side">
&nbsp;
</div>
</div> <!-- end row -->
<script>
$(document).ready(function () {
$('#startDate').prop('disabled', true );
$('#endDate').prop('disabled', true );
$('#systemSelect').prop('disabled', true );
var sysid = $('#systemSelect4').val();
});
function switch_database() {
var file = $('#seldb').val();
if (file == '0')
return;
window.location.href='/switch_db?cmd=switch&file=' + file;
}
function create_backup() {
window.location.href='/switch_db?cmd=backup';
}
</script>
{% endblock %}
{% block extra_javascripts %}
{% endblock %}

View File

@ -0,0 +1,4 @@
#! /bin/sh
export FLASK_APP=op25
FLASK_DEBUG=1 flask run --host=0.0.0.0

View File

@ -0,0 +1,30 @@
import os
from setuptools import find_packages, setup
here = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(here, "README.rst")) as f:
README = f.read()
setup(
name="flask_tut",
version="0.0",
description="flask_tut",
long_description=README,
classifiers=[
"Programming Language :: Python",
"Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
author="",
author_email="",
url="",
keywords="web wsgi bfg flask",
packages=find_packages(),
include_package_data=True,
zip_safe=False,
test_suite="flask_tut",
install_requires=[],
)