oplog rc2-final
|
@ -0,0 +1,4 @@
|
|||
import os
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///%s/../op25-data.db' % (os.path.dirname(__file__))
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
@ -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('/')
|
11038
op25/gr-op25_repeater/apps/oplog/op25/static/css/bootstrap/bootstrap-darkly.css
vendored
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 160 B |
After Width: | Height: | Size: 148 B |
After Width: | Height: | Size: 201 B |
After Width: | Height: | Size: 158 B |
After Width: | Height: | Size: 146 B |
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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 */
|
||||
}
|
After Width: | Height: | Size: 1.1 KiB |
7
op25/gr-op25_repeater/apps/oplog/op25/static/js/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
15212
op25/gr-op25_repeater/apps/oplog/op25/static/js/datatables/jquery.dataTables.js
vendored
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 2.0 KiB |
|
@ -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">
|
||||
|
||||
</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 © 2020, 2021 Max H. Parke KA1RBI<br>
|
||||
Copyright © 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">
|
||||
|
||||
</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> \
|
||||
<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 %}
|
|
@ -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>
|
||||
|
||||
<span>Start <input class="sel-date" type="text" id="startDate"></span>
|
||||
<span>End <input class="sel-date" type="text" id="endDate"> </span>
|
||||
<button style="width: 75px;" class="btn btn-info btn-sm" id="btnReset" onclick="location.reload();">Refresh</button>
|
||||
<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>
|
|
@ -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>
|
||||
|
||||
Talkgroups: <b><span id="talkgroups">{{ dbstats[2] }}</span></b>
|
||||
|
||||
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;"> — </td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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">
|
||||
|
||||
</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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
New records added:<b> {{ session['imp_results'][1] }} </b>
|
||||
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">
|
||||
|
||||
</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> \
|
||||
<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 %}
|
||||
|
|
@ -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">
|
||||
|
||||
</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">
|
||||
|
||||
</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> \
|
||||
<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 %}
|
|
@ -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> </p>
|
||||
</div>
|
||||
<div id="primary">
|
||||
|
||||
</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">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end secondary -->
|
||||
|
||||
</div>
|
||||
<!-- end content -->
|
||||
|
||||
</div>
|
||||
<br>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_javascripts %}
|
||||
|
||||
{% endblock %}
|
|
@ -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>
|
||||
<a href="{{ url_for('editsys') }}">System Tags</a>
|
||||
<a href="{{ url_for('edit_tags') }}?cmd=tgid">Talkgroup Tags</a>
|
||||
<a href="{{ url_for('edit_tags') }}?cmd=unit">Unit Tags</a>
|
||||
<a href="{{ url_for('purge') }}">Purge Database</a>
|
||||
<a href="{{ url_for('about') }}">About</a>
|
||||
<br>
|
||||
Server time: {{ t_loc() }} <br>
|
||||
07.23.2021
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -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">
|
||||
|
||||
</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>
|
||||
|
||||
<button class="btnMain btn btn-outline-info" onclick="window.location.href='/logs?r=call_detail'">Call<br>Detail</button>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<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> {{ params['cc_desc'][i] }} </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'footer-links.html' %}
|
||||
</div> <!-- end main -->
|
||||
|
||||
<div class="side">
|
||||
|
||||
</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 %}
|
|
@ -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 %}
|
|
@ -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'] }}
|
||||
{% 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' %}
|
|
@ -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">
|
||||
|
||||
</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>
|
||||
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>
|
||||
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">
|
||||
|
||||
</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 %}
|
|
@ -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">
|
||||
|
||||
</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">
|
||||
|
||||
</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 %}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
#! /bin/sh
|
||||
|
||||
export FLASK_APP=op25
|
||||
FLASK_DEBUG=1 flask run --host=0.0.0.0
|
|
@ -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=[],
|
||||
)
|