History Tab Overhaul & Domain Record Modifications Changelog (#1042)
Co-authored-by: Konstantinos Kouris <85997752+konkourgr@users.noreply.github.com> Co-authored-by: vmarkop <billy.mark.b.m.10@gmail.com> Co-authored-by: KostasMparmparousis <mparmparousis.kostas@gmail.com> Co-authored-by: dimpapac <demispapa@gmail.com>
@ -8,6 +8,7 @@ from .base import db
|
||||
|
||||
class History(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
# format of msg field must not change. History traversing is done using part of the msg field
|
||||
msg = db.Column(db.String(256))
|
||||
# detail = db.Column(db.Text().with_variant(db.Text(length=2**24-2), 'mysql'))
|
||||
detail = db.Column(db.Text())
|
||||
|
@ -189,6 +189,7 @@ class Setting(db.Model):
|
||||
'ttl_options': '1 minute,5 minutes,30 minutes,60 minutes,24 hours',
|
||||
'otp_field_enabled': True,
|
||||
'custom_css': '',
|
||||
'max_history_records': 1000
|
||||
}
|
||||
|
||||
def __init__(self, id=None, name=None, value=None):
|
||||
|
@ -32,11 +32,169 @@ admin_bp = Blueprint('admin',
|
||||
template_folder='templates',
|
||||
url_prefix='/admin')
|
||||
|
||||
"""
|
||||
changeSet is a list of tuples, in the following format
|
||||
(old_state, new_state, change_type)
|
||||
|
||||
old_state: dictionary with "disabled" and "content" keys. {"disabled" : False, "content" : "1.1.1.1" }
|
||||
new_state: similarly
|
||||
change_type: "addition" or "deletion" or "status" for status change or "unchanged" for no change
|
||||
|
||||
Note: A change in "content", is considered a deletion and recreation of the same record,
|
||||
holding the new content value.
|
||||
"""
|
||||
def get_record_changes(del_rrest, add_rrest):
|
||||
changeSet = []
|
||||
delSet = del_rrest['records'] if 'records' in del_rrest else []
|
||||
addSet = add_rrest['records'] if 'records' in add_rrest else []
|
||||
for d in delSet: # get the deletions and status changes
|
||||
exists = False
|
||||
for a in addSet:
|
||||
if d['content'] == a['content']:
|
||||
exists = True
|
||||
if d['disabled'] != a['disabled']:
|
||||
changeSet.append( ({"disabled":d['disabled'],"content":d['content']},
|
||||
{"disabled":a['disabled'],"content":a['content']},
|
||||
"status") )
|
||||
break
|
||||
|
||||
if not exists: # deletion
|
||||
changeSet.append( ({"disabled":d['disabled'],"content":d['content']},
|
||||
None,
|
||||
"deletion") )
|
||||
|
||||
for a in addSet: # get the additions
|
||||
exists = False
|
||||
for d in delSet:
|
||||
if d['content'] == a['content']:
|
||||
exists = True
|
||||
# already checked for status change
|
||||
break
|
||||
if not exists:
|
||||
changeSet.append( (None, {"disabled":a['disabled'], "content":a['content']}, "addition") )
|
||||
continue
|
||||
|
||||
for a in addSet: # get the unchanged
|
||||
exists = False
|
||||
for c in changeSet:
|
||||
if c[1] != None and c[1]["content"] == a['content']:
|
||||
exists = True
|
||||
break
|
||||
if not exists:
|
||||
changeSet.append( ( {"disabled":a['disabled'], "content":a['content']}, {"disabled":a['disabled'], "content":a['content']}, "unchanged") )
|
||||
|
||||
return changeSet
|
||||
|
||||
# out_changes is a list of HistoryRecordEntry objects in which we will append the new changes
|
||||
# a HistoryRecordEntry represents a pair of add_rrest and del_rrest
|
||||
def extract_changelogs_from_a_history_entry(out_changes, history_entry, change_num, record_name=None, record_type=None):
|
||||
|
||||
if history_entry.detail is None:
|
||||
return
|
||||
|
||||
detail_dict = json.loads(history_entry.detail.replace("'", '"'))
|
||||
if "add_rrests" not in detail_dict:
|
||||
return
|
||||
|
||||
add_rrests = detail_dict['add_rrests']
|
||||
del_rrests = detail_dict['del_rrests']
|
||||
|
||||
|
||||
for add_rrest in add_rrests:
|
||||
exists = False
|
||||
for del_rrest in del_rrests:
|
||||
if del_rrest['name'] == add_rrest['name'] and del_rrest['type'] == add_rrest['type']:
|
||||
exists = True
|
||||
if change_num not in out_changes:
|
||||
out_changes[change_num] = []
|
||||
out_changes[change_num].append(HistoryRecordEntry(history_entry, del_rrest, add_rrest, "*"))
|
||||
break
|
||||
if not exists: # this is a new record
|
||||
if change_num not in out_changes:
|
||||
out_changes[change_num] = []
|
||||
out_changes[change_num].append(HistoryRecordEntry(history_entry, [], add_rrest, "+")) # (add_rrest, del_rrest, change_type)
|
||||
for del_rrest in del_rrests:
|
||||
exists = False
|
||||
for add_rrest in add_rrests:
|
||||
if del_rrest['name'] == add_rrest['name'] and del_rrest['type'] == add_rrest['type']:
|
||||
exists = True # no need to add in the out_changes set
|
||||
break
|
||||
if not exists: # this is a deletion
|
||||
if change_num not in out_changes:
|
||||
out_changes[change_num] = []
|
||||
out_changes[change_num].append(HistoryRecordEntry(history_entry, del_rrest, [], "-"))
|
||||
|
||||
|
||||
# only used for changelog per record
|
||||
if record_name != None and record_type != None: # then get only the records with the specific (record_name, record_type) tuple
|
||||
if change_num in out_changes:
|
||||
changes_i = out_changes[change_num]
|
||||
else:
|
||||
return
|
||||
for hre in changes_i: # for each history record entry in changes_i
|
||||
if 'type' in hre.add_rrest and hre.add_rrest['name'] == record_name and hre.add_rrest['type'] == record_type:
|
||||
continue
|
||||
elif 'type' in hre.del_rrest and hre.del_rrest['name'] == record_name and hre.del_rrest['type'] == record_type:
|
||||
continue
|
||||
else:
|
||||
out_changes[change_num].remove(hre)
|
||||
|
||||
|
||||
|
||||
# records with same (name,type) are considered as a single HistoryRecordEntry
|
||||
# history_entry is of type History - used to extract created_by and created_on
|
||||
# add_rrest is a dictionary of replace
|
||||
# del_rrest is a dictionary of remove
|
||||
class HistoryRecordEntry:
|
||||
def __init__(self, history_entry, del_rrest, add_rrest, change_type):
|
||||
# search the add_rrest index into the add_rrest set for the key (name, type)
|
||||
|
||||
self.history_entry = history_entry
|
||||
self.add_rrest = add_rrest
|
||||
self.del_rrest = del_rrest
|
||||
self.change_type = change_type # "*": edit or unchanged, "+" new tuple(name,type), "-" deleted (name,type) tuple
|
||||
self.changed_fields = [] # contains a subset of : [ttl, name, type]
|
||||
self.changeSet = [] # all changes for the records of this add_rrest-del_rrest pair
|
||||
|
||||
|
||||
if change_type == "+": # addition
|
||||
self.changed_fields.append("name")
|
||||
self.changed_fields.append("type")
|
||||
self.changed_fields.append("ttl")
|
||||
self.changeSet = get_record_changes(del_rrest, add_rrest)
|
||||
elif change_type == "-": # removal
|
||||
self.changed_fields.append("name")
|
||||
self.changed_fields.append("type")
|
||||
self.changed_fields.append("ttl")
|
||||
self.changeSet = get_record_changes(del_rrest, add_rrest)
|
||||
|
||||
elif change_type == "*": # edit of unchanged
|
||||
if add_rrest['ttl'] != del_rrest['ttl']:
|
||||
self.changed_fields.append("ttl")
|
||||
self.changeSet = get_record_changes(del_rrest, add_rrest)
|
||||
|
||||
|
||||
|
||||
def toDict(self):
|
||||
return {
|
||||
"add_rrest" : self.add_rrest,
|
||||
"del_rrest" : self.del_rrest,
|
||||
"changed_fields" : self.changed_fields,
|
||||
"created_on" : self.history_entry.created_on,
|
||||
"created_by" : self.history_entry.created_by,
|
||||
"change_type" : self.change_type,
|
||||
"changeSet" : self.changeSet
|
||||
}
|
||||
|
||||
def __eq__(self, obj2): # used for removal of objects from a list
|
||||
return True if obj2.toDict() == self.toDict() else False
|
||||
|
||||
@admin_bp.before_request
|
||||
def before_request():
|
||||
# Manage session timeout
|
||||
session.permanent = True
|
||||
# current_app.permanent_session_lifetime = datetime.timedelta(
|
||||
# minutes=int(Setting().get('session_timeout')))
|
||||
current_app.permanent_session_lifetime = datetime.timedelta(
|
||||
minutes=int(Setting().get('session_timeout')))
|
||||
session.modified = True
|
||||
@ -581,6 +739,131 @@ def manage_account():
|
||||
}), 400)
|
||||
|
||||
|
||||
class DetailedHistory():
|
||||
def __init__(self, history, change_set):
|
||||
self.history = history
|
||||
self.detailed_msg = ""
|
||||
self.change_set = change_set
|
||||
|
||||
if history.detail is None:
|
||||
self.detailed_msg = ""
|
||||
# if 'Create account' in history.msg:
|
||||
# account = Account.query.filter(
|
||||
# Account.name == history.msg.split(' ')[2]).first()
|
||||
# self.detailed_msg = str(account.get_user())
|
||||
# # WRONG, cannot do query afterwards, db may have changed
|
||||
return
|
||||
|
||||
detail_dict = json.loads(history.detail.replace("'", '"'))
|
||||
if 'domain_type' in detail_dict.keys() and 'account_id' in detail_dict.keys(): # this is a domain creation
|
||||
self.detailed_msg = """
|
||||
<table class="table table-bordered table-striped"><tr><td>Domain type:</td><td>{0}</td></tr> <tr><td>Account:</td><td>{1}</td></tr></table>
|
||||
""".format(detail_dict['domain_type'],
|
||||
Account.get_name_by_id(self=None, account_id=detail_dict['account_id']) if detail_dict['account_id'] != "0" else "None")
|
||||
elif 'authenticator' in detail_dict.keys(): # this is a user authentication
|
||||
self.detailed_msg = """
|
||||
<table class="table table-bordered table-striped" style="width:565px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="3" style="background:
|
||||
"""
|
||||
|
||||
# Change table header background colour depending on auth success or failure
|
||||
if detail_dict['success'] == 1:
|
||||
self.detailed_msg+= """
|
||||
rgba(68,157,68);"> <p style="color:white;">
|
||||
User {0} authentication success
|
||||
</p></th>
|
||||
""".format(detail_dict['username'])
|
||||
|
||||
else:
|
||||
self.detailed_msg+= """
|
||||
rgba(201,48,44);"> <p style="color:white;">
|
||||
User {0} authentication failure
|
||||
</th>
|
||||
""".format(detail_dict['username'])
|
||||
|
||||
self.detailed_msg+= """
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Authenticator Type:</td>
|
||||
<td colspan="2">{0}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP Address</td>
|
||||
<td colspan="2">{1}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
""".format(detail_dict['authenticator'], detail_dict['ip_address'])
|
||||
|
||||
elif 'add_rrests' in detail_dict.keys(): # this is a domain record change
|
||||
# changes_set = []
|
||||
self.detailed_msg = ""
|
||||
# extract_changelogs_from_a_history_entry(changes_set, history, 0)
|
||||
elif 'name' in detail_dict.keys() and 'template' in history.msg: # template creation
|
||||
self.detailed_msg = """
|
||||
<table class="table table-bordered table-striped"><tr><td>Template name:</td><td>{0}</td></tr> <tr><td>Description:</td><td>{1}</td></tr></table>
|
||||
""".format(detail_dict['name'], detail_dict['description'])
|
||||
elif 'Change domain' in history.msg and 'access control' in history.msg: # added or removed a user from a domain
|
||||
self.detailed_msg = """
|
||||
<table class="table table-bordered table-striped"><tr><td>Users with access to this domain</td><td>{0}</td></tr><tr><td>Number of users:</td><td>{1}</td><tr></table>
|
||||
""".format(str(detail_dict['user_has_access']).replace("]","").replace("[", ""), len((detail_dict['user_has_access'])))
|
||||
elif 'Created API key' in history.msg or 'Updated API key' in history.msg:
|
||||
self.detailed_msg = """
|
||||
<table class="table table-bordered table-striped">
|
||||
<tr><td>Key: </td><td>{0}</td></tr>
|
||||
<tr><td>Role:</td><td>{1}</td></tr>
|
||||
<tr><td>Description:</td><td>{2}</td></tr>
|
||||
<tr><td>Accessible domains with this API key:</td><td>{3}</td></tr>
|
||||
</table>
|
||||
""".format(detail_dict['key'], detail_dict['role'], detail_dict['description'], str(detail_dict['domain_acl']).replace("]","").replace("[", ""))
|
||||
elif 'Update type for domain' in history.msg:
|
||||
self.detailed_msg = """
|
||||
<table class="table table-bordered table-striped">
|
||||
<tr><td>Domain: </td><td>{0}</td></tr>
|
||||
<tr><td>Domain type:</td><td>{1}</td></tr>
|
||||
<tr><td>Masters:</td><td>{2}</td></tr>
|
||||
</table>
|
||||
""".format(detail_dict['domain'], detail_dict['type'], str(detail_dict['masters']).replace("]","").replace("[", ""))
|
||||
elif 'Delete API key' in history.msg:
|
||||
self.detailed_msg = """
|
||||
<table class="table table-bordered table-striped">
|
||||
<tr><td>Key: </td><td>{0}</td></tr>
|
||||
<tr><td>Role:</td><td>{1}</td></tr>
|
||||
<tr><td>Description:</td><td>{2}</td></tr>
|
||||
<tr><td>Accessible domains with this API key:</td><td>{3}</td></tr>
|
||||
</table>
|
||||
""".format(detail_dict['key'], detail_dict['role'], detail_dict['description'], str(detail_dict['domains']).replace("]","").replace("[", ""))
|
||||
elif 'reverse' in history.msg:
|
||||
self.detailed_msg = """
|
||||
<table class="table table-bordered table-striped">
|
||||
<tr><td>Domain Type: </td><td>{0}</td></tr>
|
||||
<tr><td>Domain Master IPs:</td><td>{1}</td></tr>
|
||||
</table>
|
||||
""".format(detail_dict['domain_type'], detail_dict['domain_master_ips'])
|
||||
|
||||
# convert a list of History objects into DetailedHistory objects
|
||||
def convert_histories(histories):
|
||||
changes_set = dict()
|
||||
detailedHistories = []
|
||||
j = 0
|
||||
for i in range(len(histories)):
|
||||
# if histories[i].detail != None and 'add_rrests' in json.loads(histories[i].detail.replace("'", '"')):
|
||||
if histories[i].detail != None and ('add_rrests' in json.loads(histories[i].detail.replace("'", '"')) or 'del_rrests' in json.loads(histories[i].detail.replace("'", '"'))):
|
||||
extract_changelogs_from_a_history_entry(changes_set, histories[i], j)
|
||||
if j in changes_set:
|
||||
detailedHistories.append(DetailedHistory(histories[i], changes_set[j]))
|
||||
else: # no changes were found
|
||||
detailedHistories.append(DetailedHistory(histories[i], None))
|
||||
j += 1
|
||||
|
||||
else:
|
||||
detailedHistories.append(DetailedHistory(histories[i], None))
|
||||
return detailedHistories
|
||||
|
||||
@admin_bp.route('/history', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@history_access_required
|
||||
@ -611,14 +894,121 @@ def history():
|
||||
'msg': 'Can not remove histories.'
|
||||
}), 500)
|
||||
|
||||
|
||||
if request.method == 'GET':
|
||||
doms = accounts = users = ""
|
||||
if current_user.role.name in [ 'Administrator', 'Operator']:
|
||||
all_domain_names = Domain.query.all()
|
||||
all_account_names = Account.query.all()
|
||||
all_user_names = User.query.all()
|
||||
|
||||
|
||||
|
||||
for d in all_domain_names:
|
||||
doms += d.name + " "
|
||||
for acc in all_account_names:
|
||||
accounts += acc.name + " "
|
||||
for usr in all_user_names:
|
||||
users += usr.username + " "
|
||||
else: # special autocomplete for users
|
||||
all_domain_names = db.session.query(Domain) \
|
||||
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
||||
.outerjoin(Account, Domain.account_id == Account.id) \
|
||||
.outerjoin(AccountUser, Account.id == AccountUser.account_id) \
|
||||
.filter(
|
||||
db.or_(
|
||||
DomainUser.user_id == current_user.id,
|
||||
AccountUser.user_id == current_user.id
|
||||
)).all()
|
||||
|
||||
all_account_names = db.session.query(Account) \
|
||||
.outerjoin(Domain, Domain.account_id == Account.id) \
|
||||
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
||||
.outerjoin(AccountUser, Account.id == AccountUser.account_id) \
|
||||
.filter(
|
||||
db.or_(
|
||||
DomainUser.user_id == current_user.id,
|
||||
AccountUser.user_id == current_user.id
|
||||
)).all()
|
||||
|
||||
|
||||
all_user_names = []
|
||||
for a in all_account_names:
|
||||
temp = db.session.query(User) \
|
||||
.join(AccountUser, AccountUser.user_id == User.id) \
|
||||
.outerjoin(Account, Account.id == AccountUser.account_id) \
|
||||
.filter(
|
||||
db.or_(
|
||||
Account.id == a.id,
|
||||
AccountUser.account_id == a.id
|
||||
)
|
||||
) \
|
||||
.all()
|
||||
for u in temp:
|
||||
if u in all_user_names:
|
||||
continue
|
||||
all_user_names.append(u)
|
||||
|
||||
for d in all_domain_names:
|
||||
doms += d.name + " "
|
||||
|
||||
for a in all_account_names:
|
||||
accounts += a.name + " "
|
||||
for u in all_user_names:
|
||||
users += u.username + " "
|
||||
return render_template('admin_history.html', all_domain_names=doms, all_account_names=accounts, all_usernames=users)
|
||||
|
||||
# local_offset is the offset of the utc to the local time
|
||||
# offset must be int
|
||||
# return the date converted and simplified
|
||||
def from_utc_to_local(local_offset, timeframe):
|
||||
offset = str(local_offset *(-1))
|
||||
date_split = str(timeframe).split(".")[0]
|
||||
date_converted = datetime.datetime.strptime(date_split, '%Y-%m-%d %H:%M:%S') + datetime.timedelta(minutes=int(offset))
|
||||
return date_converted
|
||||
|
||||
@admin_bp.route('/history_table', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@history_access_required
|
||||
def history_table(): # ajax call data
|
||||
|
||||
if request.method == 'POST':
|
||||
if current_user.role.name != 'Administrator':
|
||||
return make_response(
|
||||
jsonify({
|
||||
'status': 'error',
|
||||
'msg': 'You do not have permission to remove history.'
|
||||
}), 401)
|
||||
|
||||
h = History()
|
||||
result = h.remove_all()
|
||||
if result:
|
||||
history = History(msg='Remove all histories',
|
||||
created_by=current_user.username)
|
||||
history.add()
|
||||
return make_response(
|
||||
jsonify({
|
||||
'status': 'ok',
|
||||
'msg': 'Changed user role successfully.'
|
||||
}), 200)
|
||||
else:
|
||||
return make_response(
|
||||
jsonify({
|
||||
'status': 'error',
|
||||
'msg': 'Can not remove histories.'
|
||||
}), 500)
|
||||
|
||||
detailedHistories = []
|
||||
lim = int(Setting().get('max_history_records')) # max num of records
|
||||
|
||||
if request.method == 'GET':
|
||||
if current_user.role.name in [ 'Administrator', 'Operator' ]:
|
||||
histories = History.query.all()
|
||||
base_query = History.query
|
||||
else:
|
||||
# if the user isn't an administrator or operator,
|
||||
# allow_user_view_history must be enabled to get here,
|
||||
# so include history for the domains for the user
|
||||
histories = db.session.query(History) \
|
||||
base_query = db.session.query(History) \
|
||||
.join(Domain, History.domain_id == Domain.id) \
|
||||
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
||||
.outerjoin(Account, Domain.account_id == Account.id) \
|
||||
@ -629,7 +1019,189 @@ def history():
|
||||
AccountUser.user_id == current_user.id
|
||||
))
|
||||
|
||||
return render_template('admin_history.html', histories=histories)
|
||||
domain_name = request.args.get('domain_name_filter') if request.args.get('domain_name_filter') != None \
|
||||
and len(request.args.get('domain_name_filter')) != 0 else None
|
||||
account_name = request.args.get('account_name_filter') if request.args.get('account_name_filter') != None \
|
||||
and len(request.args.get('account_name_filter')) != 0 else None
|
||||
user_name = request.args.get('auth_name_filter') if request.args.get('auth_name_filter') != None \
|
||||
and len(request.args.get('auth_name_filter')) != 0 else None
|
||||
|
||||
min_date = request.args.get('min') if request.args.get('min') != None and len( request.args.get('min')) != 0 else None
|
||||
if min_date != None: # get 1 day earlier, to check for timezone errors
|
||||
min_date = str(datetime.datetime.strptime(min_date, '%Y-%m-%d') - datetime.timedelta(days=1))
|
||||
max_date = request.args.get('max') if request.args.get('max') != None and len( request.args.get('max')) != 0 else None
|
||||
if max_date != None: # get 1 day later, to check for timezone errors
|
||||
max_date = str(datetime.datetime.strptime(max_date, '%Y-%m-%d') + datetime.timedelta(days=1))
|
||||
tzoffset = request.args.get('tzoffset') if request.args.get('tzoffset') != None and len(request.args.get('tzoffset')) != 0 else None
|
||||
changed_by = request.args.get('user_name_filter') if request.args.get('user_name_filter') != None \
|
||||
and len(request.args.get('user_name_filter')) != 0 else None
|
||||
"""
|
||||
Auth methods: LOCAL, Github OAuth, Azure OAuth, SAML, OIDC OAuth, Google OAuth
|
||||
"""
|
||||
auth_methods = []
|
||||
if (request.args.get('auth_local_only_checkbox') is None \
|
||||
and request.args.get('auth_oauth_only_checkbox') is None \
|
||||
and request.args.get('auth_saml_only_checkbox') is None and request.args.get('auth_all_checkbox') is None):
|
||||
auth_methods = []
|
||||
if request.args.get('auth_all_checkbox') == "on":
|
||||
auth_methods.append("")
|
||||
if request.args.get('auth_local_only_checkbox') == "on":
|
||||
auth_methods.append("LOCAL")
|
||||
if request.args.get('auth_oauth_only_checkbox') == "on":
|
||||
auth_methods.append("OAuth")
|
||||
if request.args.get('auth_saml_only_checkbox') == "on":
|
||||
auth_methods.append("SAML")
|
||||
|
||||
if request.args.get('domain_changelog_only_checkbox') != None:
|
||||
changelog_only = True if request.args.get('domain_changelog_only_checkbox') == "on" else False
|
||||
else:
|
||||
changelog_only = False
|
||||
|
||||
|
||||
|
||||
|
||||
# users cannot search for authentication
|
||||
if user_name != None and current_user.role.name not in [ 'Administrator', 'Operator']:
|
||||
histories = []
|
||||
elif domain_name != None:
|
||||
|
||||
if not changelog_only:
|
||||
histories = base_query \
|
||||
.filter(
|
||||
db.and_(
|
||||
db.or_(
|
||||
History.msg.like("%domain "+ domain_name) if domain_name != "*" else History.msg.like("%domain%"),
|
||||
History.msg.like("%domain "+ domain_name + " access control") if domain_name != "*" else History.msg.like("%domain%access control")
|
||||
),
|
||||
History.created_on <= max_date if max_date != None else True,
|
||||
History.created_on >= min_date if min_date != None else True,
|
||||
History.created_by == changed_by if changed_by != None else True
|
||||
)
|
||||
).order_by(History.created_on.desc()).limit(lim).all()
|
||||
else:
|
||||
# search for records changes only
|
||||
histories = base_query \
|
||||
.filter(
|
||||
db.and_(
|
||||
History.msg.like("Apply record changes to domain " + domain_name) if domain_name != "*" \
|
||||
else History.msg.like("Apply record changes to domain%"),
|
||||
History.created_on <= max_date if max_date != None else True,
|
||||
History.created_on >= min_date if min_date != None else True,
|
||||
History.created_by == changed_by if changed_by != None else True
|
||||
|
||||
)
|
||||
).order_by(History.created_on.desc()) \
|
||||
.limit(lim).all()
|
||||
elif account_name != None:
|
||||
if current_user.role.name in ['Administrator', 'Operator']:
|
||||
histories = base_query \
|
||||
.join(Domain, History.domain_id == Domain.id) \
|
||||
.outerjoin(Account, Domain.account_id == Account.id) \
|
||||
.filter(
|
||||
db.and_(
|
||||
Account.id == Domain.account_id,
|
||||
account_name == Account.name if account_name != "*" else True,
|
||||
History.created_on <= max_date if max_date != None else True,
|
||||
History.created_on >= min_date if min_date != None else True,
|
||||
History.created_by == changed_by if changed_by != None else True
|
||||
)
|
||||
).order_by(History.created_on.desc()) \
|
||||
.limit(lim).all()
|
||||
else:
|
||||
histories = base_query \
|
||||
.filter(
|
||||
db.and_(
|
||||
Account.id == Domain.account_id,
|
||||
account_name == Account.name if account_name != "*" else True,
|
||||
History.created_on <= max_date if max_date != None else True,
|
||||
History.created_on >= min_date if min_date != None else True,
|
||||
History.created_by == changed_by if changed_by != None else True
|
||||
)
|
||||
).order_by(History.created_on.desc()) \
|
||||
.limit(lim).all()
|
||||
elif user_name != None and current_user.role.name in [ 'Administrator', 'Operator']: # only admins can see the user login-logouts
|
||||
|
||||
histories = History.query \
|
||||
.filter(
|
||||
db.and_(
|
||||
db.or_(
|
||||
History.msg.like("User "+ user_name + " authentication%") if user_name != "*" and user_name != None else History.msg.like("%authentication%"),
|
||||
History.msg.like("User "+ user_name + " was not authorized%") if user_name != "*" and user_name != None else History.msg.like("User%was not authorized%")
|
||||
),
|
||||
History.created_on <= max_date if max_date != None else True,
|
||||
History.created_on >= min_date if min_date != None else True,
|
||||
History.created_by == changed_by if changed_by != None else True
|
||||
)
|
||||
) \
|
||||
.order_by(History.created_on.desc()).limit(lim).all()
|
||||
temp = []
|
||||
for h in histories:
|
||||
for method in auth_methods:
|
||||
if method in h.detail:
|
||||
temp.append(h)
|
||||
break
|
||||
histories = temp
|
||||
elif (changed_by != None or max_date != None) and current_user.role.name in [ 'Administrator', 'Operator'] : # select changed by and date filters only
|
||||
histories = History.query \
|
||||
.filter(
|
||||
db.and_(
|
||||
History.created_on <= max_date if max_date != None else True,
|
||||
History.created_on >= min_date if min_date != None else True,
|
||||
History.created_by == changed_by if changed_by != None else True
|
||||
)
|
||||
) \
|
||||
.order_by(History.created_on.desc()).limit(lim).all()
|
||||
elif (changed_by != None or max_date != None): # special filtering for user because one user does not have access to log-ins logs
|
||||
histories = base_query \
|
||||
.filter(
|
||||
db.and_(
|
||||
History.created_on <= max_date if max_date != None else True,
|
||||
History.created_on >= min_date if min_date != None else True,
|
||||
History.created_by == changed_by if changed_by != None else True
|
||||
)
|
||||
) \
|
||||
.order_by(History.created_on.desc()).limit(lim).all()
|
||||
elif max_date != None: # if changed by == null and only date is applied
|
||||
histories = base_query.filter(
|
||||
db.and_(
|
||||
History.created_on <= max_date if max_date != None else True,
|
||||
History.created_on >= min_date if min_date != None else True,
|
||||
)
|
||||
).order_by(History.created_on.desc()).limit(lim).all()
|
||||
else: # default view
|
||||
if current_user.role.name in [ 'Administrator', 'Operator']:
|
||||
histories = History.query.order_by(History.created_on.desc()).limit(lim).all()
|
||||
else:
|
||||
histories = db.session.query(History) \
|
||||
.join(Domain, History.domain_id == Domain.id) \
|
||||
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
||||
.outerjoin(Account, Domain.account_id == Account.id) \
|
||||
.outerjoin(AccountUser, Account.id == AccountUser.account_id) \
|
||||
.order_by(History.created_on.desc()) \
|
||||
.filter(
|
||||
db.or_(
|
||||
DomainUser.user_id == current_user.id,
|
||||
AccountUser.user_id == current_user.id
|
||||
)).limit(lim).all()
|
||||
|
||||
detailedHistories = convert_histories(histories)
|
||||
|
||||
# Remove dates from previous or next day that were brought over
|
||||
if tzoffset != None:
|
||||
if min_date != None:
|
||||
min_date_split = min_date.split()[0]
|
||||
if max_date != None:
|
||||
max_date_split = max_date.split()[0]
|
||||
for i, history_rec in enumerate(detailedHistories):
|
||||
local_date = str(from_utc_to_local(int(tzoffset), history_rec.history.created_on).date())
|
||||
if (min_date != None and local_date == min_date_split) or (max_date != None and local_date == max_date_split):
|
||||
detailedHistories[i] = None
|
||||
|
||||
# Remove elements previously flagged as None
|
||||
detailedHistories = [h for h in detailedHistories if h is not None]
|
||||
|
||||
return render_template('admin_history_table.html', histories=detailedHistories, len_histories=len(detailedHistories), lim=lim)
|
||||
|
||||
|
||||
|
||||
@admin_bp.route('/setting/basic', methods=['GET'])
|
||||
@ -644,9 +1216,9 @@ def setting_basic():
|
||||
'pretty_ipv6_ptr', 'dnssec_admins_only',
|
||||
'allow_user_create_domain', 'allow_user_remove_domain', 'allow_user_view_history', 'bg_domain_updates', 'site_name',
|
||||
'session_timeout', 'warn_session_timeout', 'ttl_options',
|
||||
|
||||
'pdns_api_timeout', 'verify_ssl_connections', 'verify_user_email',
|
||||
'delete_sso_accounts', 'otp_field_enabled', 'custom_css', 'enable_api_rr_history'
|
||||
'delete_sso_accounts', 'otp_field_enabled', 'custom_css', 'enable_api_rr_history', 'max_history_records'
|
||||
|
||||
]
|
||||
|
||||
return render_template('admin_setting_basic.html', settings=settings)
|
||||
|
@ -61,7 +61,7 @@ def domains_custom(boxId):
|
||||
))
|
||||
|
||||
template = current_app.jinja_env.get_template("dashboard_domain.html")
|
||||
render = template.make_module(vars={"current_user": current_user})
|
||||
render = template.make_module(vars={"current_user": current_user, "allow_user_view_history": Setting().get('allow_user_view_history')})
|
||||
|
||||
columns = [
|
||||
Domain.name, Domain.dnssec, Domain.type, Domain.serial, Domain.master,
|
||||
@ -163,19 +163,20 @@ def dashboard():
|
||||
if current_user.role.name in ['Administrator', 'Operator']:
|
||||
domain_count = Domain.query.count()
|
||||
history_number = History.query.count()
|
||||
history = History.query.order_by(History.created_on.desc()).limit(4)
|
||||
history = History.query.order_by(History.created_on.desc()).limit(4).all()
|
||||
elif Setting().get('allow_user_view_history'):
|
||||
history = db.session.query(History) \
|
||||
.join(Domain, History.domain_id == Domain.id) \
|
||||
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
||||
.outerjoin(Account, Domain.account_id == Account.id) \
|
||||
.outerjoin(AccountUser, Account.id == AccountUser.account_id) \
|
||||
.order_by(History.created_on.desc()) \
|
||||
.filter(
|
||||
db.or_(
|
||||
DomainUser.user_id == current_user.id,
|
||||
AccountUser.user_id == current_user.id
|
||||
)).order_by(History.created_on.desc())
|
||||
history_number = history.count()
|
||||
)).all()
|
||||
history_number = len(history) # history.count()
|
||||
history = history[:4]
|
||||
domain_count = db.session.query(Domain) \
|
||||
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
||||
@ -186,6 +187,10 @@ def dashboard():
|
||||
DomainUser.user_id == current_user.id,
|
||||
AccountUser.user_id == current_user.id
|
||||
)).count()
|
||||
|
||||
from .admin import convert_histories, DetailedHistory
|
||||
detailedHistories = convert_histories(history)
|
||||
|
||||
server = Server(server_id='localhost')
|
||||
statistics = server.get_statistic()
|
||||
if statistics:
|
||||
@ -202,7 +207,7 @@ def dashboard():
|
||||
user_num=user_num,
|
||||
history_number=history_number,
|
||||
uptime=uptime,
|
||||
histories=history,
|
||||
histories=detailedHistories,
|
||||
show_bg_domain_button=show_bg_domain_button,
|
||||
pdns_version=Setting().get('pdns_version'))
|
||||
|
||||
|
@ -24,7 +24,8 @@ from ..models.domain_setting import DomainSetting
|
||||
from ..models.base import db
|
||||
from ..models.domain_user import DomainUser
|
||||
from ..models.account_user import AccountUser
|
||||
|
||||
from .admin import extract_changelogs_from_a_history_entry
|
||||
from ..decorators import history_access_required
|
||||
domain_bp = Blueprint('domain',
|
||||
__name__,
|
||||
template_folder='templates',
|
||||
@ -131,7 +132,8 @@ def domain(domain_name):
|
||||
records=records,
|
||||
editable_records=editable_records,
|
||||
quick_edit=quick_edit,
|
||||
ttl_options=ttl_options)
|
||||
ttl_options=ttl_options,
|
||||
current_user=current_user)
|
||||
|
||||
|
||||
@domain_bp.route('/remove', methods=['GET', 'POST'])
|
||||
@ -187,6 +189,160 @@ def remove():
|
||||
return render_template('domain_remove.html',
|
||||
domainss=domains)
|
||||
|
||||
@domain_bp.route('/<path:domain_name>/changelog', methods=['GET'])
|
||||
@login_required
|
||||
@can_access_domain
|
||||
@history_access_required
|
||||
def changelog(domain_name):
|
||||
g.user = current_user
|
||||
login_manager.anonymous_user = Anonymous
|
||||
domain = Domain.query.filter(Domain.name == domain_name).first()
|
||||
if not domain:
|
||||
abort(404)
|
||||
|
||||
# Query domain's rrsets from PowerDNS API
|
||||
rrsets = Record().get_rrsets(domain.name)
|
||||
current_app.logger.debug("Fetched rrests: \n{}".format(pretty_json(rrsets)))
|
||||
|
||||
# API server might be down, misconfigured
|
||||
if not rrsets and domain.type != 'Slave':
|
||||
abort(500)
|
||||
|
||||
records_allow_to_edit = Setting().get_records_allow_to_edit()
|
||||
records = []
|
||||
|
||||
# get all changelogs for this domain, in descening order
|
||||
if current_user.role.name in [ 'Administrator', 'Operator' ]:
|
||||
histories = History.query.filter(History.domain_id == domain.id).order_by(History.created_on.desc()).all()
|
||||
else:
|
||||
# if the user isn't an administrator or operator,
|
||||
# allow_user_view_history must be enabled to get here,
|
||||
# so include history for the domains for the user
|
||||
histories = db.session.query(History) \
|
||||
.join(Domain, History.domain_id == Domain.id) \
|
||||
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
||||
.outerjoin(Account, Domain.account_id == Account.id) \
|
||||
.outerjoin(AccountUser, Account.id == AccountUser.account_id) \
|
||||
.order_by(History.created_on.desc()) \
|
||||
.filter(
|
||||
db.and_(db.or_(
|
||||
DomainUser.user_id == current_user.id,
|
||||
AccountUser.user_id == current_user.id
|
||||
),
|
||||
History.domain_id == domain.id
|
||||
)
|
||||
).all()
|
||||
|
||||
if StrictVersion(Setting().get('pdns_version')) >= StrictVersion('4.0.0'):
|
||||
for r in rrsets:
|
||||
if r['type'] in records_allow_to_edit:
|
||||
r_name = r['name'].rstrip('.')
|
||||
|
||||
# If it is reverse zone and pretty_ipv6_ptr setting
|
||||
# is enabled, we reformat the name for ipv6 records.
|
||||
if Setting().get('pretty_ipv6_ptr') and r[
|
||||
'type'] == 'PTR' and 'ip6.arpa' in r_name and '*' not in r_name:
|
||||
r_name = dns.reversename.to_address(
|
||||
dns.name.from_text(r_name))
|
||||
|
||||
# Create the list of records in format that
|
||||
# PDA jinja2 template can understand.
|
||||
index = 0
|
||||
for record in r['records']:
|
||||
if (len(r['comments'])>index):
|
||||
c=r['comments'][index]['content']
|
||||
else:
|
||||
c=''
|
||||
record_entry = RecordEntry(
|
||||
name=r_name,
|
||||
type=r['type'],
|
||||
status='Disabled' if record['disabled'] else 'Active',
|
||||
ttl=r['ttl'],
|
||||
data=record['content'],
|
||||
comment=c,
|
||||
is_allowed_edit=True)
|
||||
index += 1
|
||||
records.append(record_entry)
|
||||
else:
|
||||
# Unsupported version
|
||||
abort(500)
|
||||
|
||||
changes_set = dict()
|
||||
for i in range(len(histories)):
|
||||
extract_changelogs_from_a_history_entry(changes_set, histories[i], i)
|
||||
if i in changes_set and len(changes_set[i]) == 0: # if empty, then remove the key
|
||||
changes_set.pop(i)
|
||||
return render_template('domain_changelog.html', domain=domain, allHistoryChanges=changes_set)
|
||||
|
||||
"""
|
||||
Returns a changelog for a specific pair of (record_name, record_type)
|
||||
"""
|
||||
@domain_bp.route('/<path:domain_name>/changelog/<path:record_name>-<path:record_type>', methods=['GET'])
|
||||
@login_required
|
||||
@can_access_domain
|
||||
@history_access_required
|
||||
def record_changelog(domain_name, record_name, record_type):
|
||||
|
||||
g.user = current_user
|
||||
login_manager.anonymous_user = Anonymous
|
||||
domain = Domain.query.filter(Domain.name == domain_name).first()
|
||||
if not domain:
|
||||
abort(404)
|
||||
# Query domain's rrsets from PowerDNS API
|
||||
rrsets = Record().get_rrsets(domain.name)
|
||||
current_app.logger.debug("Fetched rrests: \n{}".format(pretty_json(rrsets)))
|
||||
|
||||
# API server might be down, misconfigured
|
||||
if not rrsets and domain.type != 'Slave':
|
||||
abort(500)
|
||||
|
||||
# get all changelogs for this domain, in descening order
|
||||
if current_user.role.name in [ 'Administrator', 'Operator' ]:
|
||||
histories = History.query.filter(History.domain_id == domain.id).order_by(History.created_on.desc()).all()
|
||||
else:
|
||||
# if the user isn't an administrator or operator,
|
||||
# allow_user_view_history must be enabled to get here,
|
||||
# so include history for the domains for the user
|
||||
histories = db.session.query(History) \
|
||||
.join(Domain, History.domain_id == Domain.id) \
|
||||
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
||||
.outerjoin(Account, Domain.account_id == Account.id) \
|
||||
.outerjoin(AccountUser, Account.id == AccountUser.account_id) \
|
||||
.order_by(History.created_on.desc()) \
|
||||
.filter(
|
||||
db.and_(db.or_(
|
||||
DomainUser.user_id == current_user.id,
|
||||
AccountUser.user_id == current_user.id
|
||||
),
|
||||
History.domain_id == domain.id
|
||||
)
|
||||
).all()
|
||||
|
||||
changes_set_of_record = dict()
|
||||
for i in range(len(histories)):
|
||||
extract_changelogs_from_a_history_entry(changes_set_of_record, histories[i], i, record_name, record_type)
|
||||
if i in changes_set_of_record and len(changes_set_of_record[i]) == 0: # if empty, then remove the key
|
||||
changes_set_of_record.pop(i)
|
||||
|
||||
indexes_to_pop = []
|
||||
for change_num in changes_set_of_record:
|
||||
changes_i = changes_set_of_record[change_num]
|
||||
for hre in changes_i: # for each history record entry in changes_i
|
||||
if 'type' in hre.add_rrest and hre.add_rrest['name'] == record_name and hre.add_rrest['type'] == record_type:
|
||||
continue
|
||||
elif 'type' in hre.del_rrest and hre.del_rrest['name'] == record_name and hre.del_rrest['type'] == record_type:
|
||||
continue
|
||||
else:
|
||||
changes_set_of_record[change_num].remove(hre)
|
||||
if change_num in changes_set_of_record and len(changes_set_of_record[change_num]) == 0: # if empty, then remove the key
|
||||
indexes_to_pop.append(change_num)
|
||||
|
||||
for i in indexes_to_pop:
|
||||
changes_set_of_record.pop(i)
|
||||
|
||||
return render_template('domain_changelog.html', domain=domain, allHistoryChanges=changes_set_of_record)
|
||||
|
||||
|
||||
|
||||
@domain_bp.route('/add', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
|
102
powerdnsadmin/static/assets/css/style.css
Normal file
@ -0,0 +1,102 @@
|
||||
/* Customize the label (the container) */
|
||||
.container {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 50px;
|
||||
margin-bottom: 12px;
|
||||
left:100px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Hide the browser's default checkbox */
|
||||
.container input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
/* Create a custom checkbox */
|
||||
.checkmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
/* On mouse-over, add a grey background color */
|
||||
.container:hover input ~ .checkmark {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
/* When the checkbox is checked, add a blue background */
|
||||
.container input:checked ~ .checkmark {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
/* Create the checkmark/indicator (hidden when not checked) */
|
||||
.checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show the checkmark when checked */
|
||||
.container input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Style the checkmark/indicator */
|
||||
.container .checkmark:after {
|
||||
left: 9px;
|
||||
top: 5px;
|
||||
width: 5px;
|
||||
height: 10px;
|
||||
border: solid white;
|
||||
border-width: 0 3px 3px 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.autocomplete {
|
||||
/*the container must be positioned relative:*/
|
||||
position: relative;
|
||||
/* display: inline-block; */
|
||||
}
|
||||
.autocomplete-items {
|
||||
position: absolute;
|
||||
border: 1px solid #d4d4d4;
|
||||
border-bottom: none;
|
||||
border-top: none;
|
||||
z-index: 99;
|
||||
/*position the autocomplete items to be the same width as the container:*/
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.autocomplete-items div {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #d4d4d4;
|
||||
}
|
||||
.autocomplete-items div:hover {
|
||||
/*when hovering an item:*/
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
.autocomplete-active {
|
||||
/*when navigating through the items using the arrow keys:*/
|
||||
background-color: DodgerBlue !important;
|
||||
color: #ffffff;
|
||||
}
|
10881
powerdnsadmin/static/assets/jquery-ui-smooth-datepicker/external/jquery/jquery.js
vendored
Normal file
After Width: | Height: | Size: 393 B |
After Width: | Height: | Size: 265 B |
After Width: | Height: | Size: 323 B |
After Width: | Height: | Size: 324 B |
After Width: | Height: | Size: 390 B |
After Width: | Height: | Size: 325 B |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 4.5 KiB |
700
powerdnsadmin/static/assets/jquery-ui-smooth-datepicker/jquery-ui.css
vendored
Normal file
@ -0,0 +1,700 @@
|
||||
/*! jQuery UI - v1.13.0 - 2021-11-11
|
||||
* http://jqueryui.com
|
||||
* Includes: core.css, datepicker.css, theme.css
|
||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=smoothness&cornerRadiusShadow=8px&offsetLeftShadow=-8px&offsetTopShadow=-8px&thicknessShadow=8px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=aaaaaa&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cd0a0a&fcError=cd0a0a&borderColorError=cd0a0a&bgImgOpacityError=95&bgTextureError=glass&bgColorError=fef1ec&iconColorHighlight=2e83ff&fcHighlight=363636&borderColorHighlight=fcefa1&bgImgOpacityHighlight=55&bgTextureHighlight=glass&bgColorHighlight=fbf9ee&iconColorActive=454545&fcActive=212121&borderColorActive=aaaaaa&bgImgOpacityActive=65&bgTextureActive=glass&bgColorActive=ffffff&iconColorHover=454545&fcHover=212121&borderColorHover=999999&bgImgOpacityHover=75&bgTextureHover=glass&bgColorHover=dadada&iconColorDefault=888888&fcDefault=555555&borderColorDefault=d3d3d3&bgImgOpacityDefault=75&bgTextureDefault=glass&bgColorDefault=e6e6e6&iconColorContent=222222&fcContent=222222&borderColorContent=aaaaaa&bgImgOpacityContent=75&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=222222&fcHeader=222222&borderColorHeader=aaaaaa&bgImgOpacityHeader=75&bgTextureHeader=highlight_soft&bgColorHeader=cccccc&cornerRadius=4px&fsDefault=1.1em&fwDefault=normal&ffDefault=Verdana%2CArial%2Csans-serif
|
||||
* Copyright jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
/* Layout helpers
|
||||
----------------------------------*/
|
||||
.ui-helper-hidden {
|
||||
display: none;
|
||||
}
|
||||
.ui-helper-hidden-accessible {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
.ui-helper-reset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
font-size: 100%;
|
||||
list-style: none;
|
||||
}
|
||||
.ui-helper-clearfix:before,
|
||||
.ui-helper-clearfix:after {
|
||||
content: "";
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.ui-helper-clearfix:after {
|
||||
clear: both;
|
||||
}
|
||||
.ui-helper-zfix {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
-ms-filter: "alpha(opacity=0)"; /* support: IE8 */
|
||||
}
|
||||
|
||||
.ui-front {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-disabled {
|
||||
cursor: default !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
.ui-icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-top: -.25em;
|
||||
position: relative;
|
||||
text-indent: -99999px;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.ui-widget-icon-block {
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-datepicker {
|
||||
width: 17em;
|
||||
padding: .2em .2em 0;
|
||||
display: none;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-header {
|
||||
position: relative;
|
||||
padding: .2em 0;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev,
|
||||
.ui-datepicker .ui-datepicker-next {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev-hover,
|
||||
.ui-datepicker .ui-datepicker-next-hover {
|
||||
top: 1px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev {
|
||||
left: 2px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-next {
|
||||
right: 2px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev-hover {
|
||||
left: 1px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-next-hover {
|
||||
right: 1px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev span,
|
||||
.ui-datepicker .ui-datepicker-next span {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
top: 50%;
|
||||
margin-top: -8px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-title {
|
||||
margin: 0 2.3em;
|
||||
line-height: 1.8em;
|
||||
text-align: center;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-title select {
|
||||
font-size: 1em;
|
||||
margin: 1px 0;
|
||||
}
|
||||
.ui-datepicker select.ui-datepicker-month,
|
||||
.ui-datepicker select.ui-datepicker-year {
|
||||
width: 45%;
|
||||
}
|
||||
.ui-datepicker table {
|
||||
width: 100%;
|
||||
font-size: .9em;
|
||||
border-collapse: collapse;
|
||||
margin: 0 0 .4em;
|
||||
}
|
||||
.ui-datepicker th {
|
||||
padding: .7em .3em;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
}
|
||||
.ui-datepicker td {
|
||||
border: 0;
|
||||
padding: 1px;
|
||||
}
|
||||
.ui-datepicker td span,
|
||||
.ui-datepicker td a {
|
||||
display: block;
|
||||
padding: .2em;
|
||||
text-align: right;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-buttonpane {
|
||||
background-image: none;
|
||||
margin: .7em 0 0 0;
|
||||
padding: 0 .2em;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-buttonpane button {
|
||||
float: right;
|
||||
margin: .5em .2em .4em;
|
||||
cursor: pointer;
|
||||
padding: .2em .6em .3em .6em;
|
||||
width: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* with multiple calendars */
|
||||
.ui-datepicker.ui-datepicker-multi {
|
||||
width: auto;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-group {
|
||||
float: left;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-group table {
|
||||
width: 95%;
|
||||
margin: 0 auto .4em;
|
||||
}
|
||||
.ui-datepicker-multi-2 .ui-datepicker-group {
|
||||
width: 50%;
|
||||
}
|
||||
.ui-datepicker-multi-3 .ui-datepicker-group {
|
||||
width: 33.3%;
|
||||
}
|
||||
.ui-datepicker-multi-4 .ui-datepicker-group {
|
||||
width: 25%;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
|
||||
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
|
||||
border-left-width: 0;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-buttonpane {
|
||||
clear: left;
|
||||
}
|
||||
.ui-datepicker-row-break {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
/* RTL support */
|
||||
.ui-datepicker-rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-prev {
|
||||
right: 2px;
|
||||
left: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-next {
|
||||
left: 2px;
|
||||
right: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-prev:hover {
|
||||
right: 1px;
|
||||
left: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-next:hover {
|
||||
left: 1px;
|
||||
right: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane {
|
||||
clear: right;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane button {
|
||||
float: left;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
|
||||
.ui-datepicker-rtl .ui-datepicker-group {
|
||||
float: right;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
|
||||
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
|
||||
border-right-width: 0;
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
/* Icons */
|
||||
.ui-datepicker .ui-icon {
|
||||
display: block;
|
||||
text-indent: -99999px;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
left: .5em;
|
||||
top: .3em;
|
||||
}
|
||||
|
||||
/* Component containers
|
||||
----------------------------------*/
|
||||
.ui-widget {
|
||||
font-family: Verdana,Arial,sans-serif;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.ui-widget .ui-widget {
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget input,
|
||||
.ui-widget select,
|
||||
.ui-widget textarea,
|
||||
.ui-widget button {
|
||||
font-family: Verdana,Arial,sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget.ui-widget-content {
|
||||
border: 1px solid #d3d3d3;
|
||||
}
|
||||
.ui-widget-content {
|
||||
border: 1px solid #aaaaaa;
|
||||
background: #ffffff;
|
||||
color: #222222;
|
||||
}
|
||||
.ui-widget-content a {
|
||||
color: #222222;
|
||||
}
|
||||
.ui-widget-header {
|
||||
border: 1px solid #aaaaaa;
|
||||
background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x;
|
||||
color: #222222;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-widget-header a {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
/* Interaction states
|
||||
----------------------------------*/
|
||||
.ui-state-default,
|
||||
.ui-widget-content .ui-state-default,
|
||||
.ui-widget-header .ui-state-default,
|
||||
.ui-button,
|
||||
|
||||
/* We use html here because we need a greater specificity to make sure disabled
|
||||
works properly when clicked or hovered */
|
||||
html .ui-button.ui-state-disabled:hover,
|
||||
html .ui-button.ui-state-disabled:active {
|
||||
border: 1px solid #d3d3d3;
|
||||
background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: #555555;
|
||||
}
|
||||
.ui-state-default a,
|
||||
.ui-state-default a:link,
|
||||
.ui-state-default a:visited,
|
||||
a.ui-button,
|
||||
a:link.ui-button,
|
||||
a:visited.ui-button,
|
||||
.ui-button {
|
||||
color: #555555;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-state-hover,
|
||||
.ui-widget-content .ui-state-hover,
|
||||
.ui-widget-header .ui-state-hover,
|
||||
.ui-state-focus,
|
||||
.ui-widget-content .ui-state-focus,
|
||||
.ui-widget-header .ui-state-focus,
|
||||
.ui-button:hover,
|
||||
.ui-button:focus {
|
||||
border: 1px solid #999999;
|
||||
background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: #212121;
|
||||
}
|
||||
.ui-state-hover a,
|
||||
.ui-state-hover a:hover,
|
||||
.ui-state-hover a:link,
|
||||
.ui-state-hover a:visited,
|
||||
.ui-state-focus a,
|
||||
.ui-state-focus a:hover,
|
||||
.ui-state-focus a:link,
|
||||
.ui-state-focus a:visited,
|
||||
a.ui-button:hover,
|
||||
a.ui-button:focus {
|
||||
color: #212121;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ui-visual-focus {
|
||||
box-shadow: 0 0 3px 1px rgb(94, 158, 214);
|
||||
}
|
||||
.ui-state-active,
|
||||
.ui-widget-content .ui-state-active,
|
||||
.ui-widget-header .ui-state-active,
|
||||
a.ui-button:active,
|
||||
.ui-button:active,
|
||||
.ui-button.ui-state-active:hover {
|
||||
border: 1px solid #aaaaaa;
|
||||
background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: #212121;
|
||||
}
|
||||
.ui-icon-background,
|
||||
.ui-state-active .ui-icon-background {
|
||||
border: #aaaaaa;
|
||||
background-color: #212121;
|
||||
}
|
||||
.ui-state-active a,
|
||||
.ui-state-active a:link,
|
||||
.ui-state-active a:visited {
|
||||
color: #212121;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-highlight,
|
||||
.ui-widget-content .ui-state-highlight,
|
||||
.ui-widget-header .ui-state-highlight {
|
||||
border: 1px solid #fcefa1;
|
||||
background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x;
|
||||
color: #363636;
|
||||
}
|
||||
.ui-state-checked {
|
||||
border: 1px solid #fcefa1;
|
||||
background: #fbf9ee;
|
||||
}
|
||||
.ui-state-highlight a,
|
||||
.ui-widget-content .ui-state-highlight a,
|
||||
.ui-widget-header .ui-state-highlight a {
|
||||
color: #363636;
|
||||
}
|
||||
.ui-state-error,
|
||||
.ui-widget-content .ui-state-error,
|
||||
.ui-widget-header .ui-state-error {
|
||||
border: 1px solid #cd0a0a;
|
||||
background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;
|
||||
color: #cd0a0a;
|
||||
}
|
||||
.ui-state-error a,
|
||||
.ui-widget-content .ui-state-error a,
|
||||
.ui-widget-header .ui-state-error a {
|
||||
color: #cd0a0a;
|
||||
}
|
||||
.ui-state-error-text,
|
||||
.ui-widget-content .ui-state-error-text,
|
||||
.ui-widget-header .ui-state-error-text {
|
||||
color: #cd0a0a;
|
||||
}
|
||||
.ui-priority-primary,
|
||||
.ui-widget-content .ui-priority-primary,
|
||||
.ui-widget-header .ui-priority-primary {
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-priority-secondary,
|
||||
.ui-widget-content .ui-priority-secondary,
|
||||
.ui-widget-header .ui-priority-secondary {
|
||||
opacity: .7;
|
||||
-ms-filter: "alpha(opacity=70)"; /* support: IE8 */
|
||||
font-weight: normal;
|
||||
}
|
||||
.ui-state-disabled,
|
||||
.ui-widget-content .ui-state-disabled,
|
||||
.ui-widget-header .ui-state-disabled {
|
||||
opacity: .35;
|
||||
-ms-filter: "alpha(opacity=35)"; /* support: IE8 */
|
||||
background-image: none;
|
||||
}
|
||||
.ui-state-disabled .ui-icon {
|
||||
-ms-filter: "alpha(opacity=35)"; /* support: IE8 - See #6059 */
|
||||
}
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
|
||||
/* states and images */
|
||||
.ui-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.ui-icon,
|
||||
.ui-widget-content .ui-icon {
|
||||
background-image: url("images/ui-icons_222222_256x240.png");
|
||||
}
|
||||
.ui-widget-header .ui-icon {
|
||||
background-image: url("images/ui-icons_222222_256x240.png");
|
||||
}
|
||||
.ui-state-hover .ui-icon,
|
||||
.ui-state-focus .ui-icon,
|
||||
.ui-button:hover .ui-icon,
|
||||
.ui-button:focus .ui-icon {
|
||||
background-image: url("images/ui-icons_454545_256x240.png");
|
||||
}
|
||||
.ui-state-active .ui-icon,
|
||||
.ui-button:active .ui-icon {
|
||||
background-image: url("images/ui-icons_454545_256x240.png");
|
||||
}
|
||||
.ui-state-highlight .ui-icon,
|
||||
.ui-button .ui-state-highlight.ui-icon {
|
||||
background-image: url("images/ui-icons_2e83ff_256x240.png");
|
||||
}
|
||||
.ui-state-error .ui-icon,
|
||||
.ui-state-error-text .ui-icon {
|
||||
background-image: url("images/ui-icons_cd0a0a_256x240.png");
|
||||
}
|
||||
.ui-button .ui-icon {
|
||||
background-image: url("images/ui-icons_888888_256x240.png");
|
||||
}
|
||||
|
||||
/* positioning */
|
||||
/* Three classes needed to override `.ui-button:hover .ui-icon` */
|
||||
.ui-icon-blank.ui-icon-blank.ui-icon-blank {
|
||||
background-image: none;
|
||||
}
|
||||
.ui-icon-caret-1-n { background-position: 0 0; }
|
||||
.ui-icon-caret-1-ne { background-position: -16px 0; }
|
||||
.ui-icon-caret-1-e { background-position: -32px 0; }
|
||||
.ui-icon-caret-1-se { background-position: -48px 0; }
|
||||
.ui-icon-caret-1-s { background-position: -65px 0; }
|
||||
.ui-icon-caret-1-sw { background-position: -80px 0; }
|
||||
.ui-icon-caret-1-w { background-position: -96px 0; }
|
||||
.ui-icon-caret-1-nw { background-position: -112px 0; }
|
||||
.ui-icon-caret-2-n-s { background-position: -128px 0; }
|
||||
.ui-icon-caret-2-e-w { background-position: -144px 0; }
|
||||
.ui-icon-triangle-1-n { background-position: 0 -16px; }
|
||||
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
|
||||
.ui-icon-triangle-1-e { background-position: -32px -16px; }
|
||||
.ui-icon-triangle-1-se { background-position: -48px -16px; }
|
||||
.ui-icon-triangle-1-s { background-position: -65px -16px; }
|
||||
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
|
||||
.ui-icon-triangle-1-w { background-position: -96px -16px; }
|
||||
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
|
||||
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
|
||||
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
|
||||
.ui-icon-arrow-1-n { background-position: 0 -32px; }
|
||||
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
|
||||
.ui-icon-arrow-1-e { background-position: -32px -32px; }
|
||||
.ui-icon-arrow-1-se { background-position: -48px -32px; }
|
||||
.ui-icon-arrow-1-s { background-position: -65px -32px; }
|
||||
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
|
||||
.ui-icon-arrow-1-w { background-position: -96px -32px; }
|
||||
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
|
||||
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
|
||||
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
|
||||
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
|
||||
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
|
||||
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
|
||||
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
|
||||
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
|
||||
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
|
||||
.ui-icon-arrowthick-1-n { background-position: 1px -48px; }
|
||||
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
|
||||
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
|
||||
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
|
||||
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
|
||||
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
|
||||
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
|
||||
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
|
||||
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
|
||||
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
|
||||
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
|
||||
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
|
||||
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
|
||||
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
|
||||
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
|
||||
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
|
||||
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
|
||||
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
|
||||
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
|
||||
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
|
||||
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
|
||||
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
|
||||
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
|
||||
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
|
||||
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
|
||||
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
|
||||
.ui-icon-arrow-4 { background-position: 0 -80px; }
|
||||
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
|
||||
.ui-icon-extlink { background-position: -32px -80px; }
|
||||
.ui-icon-newwin { background-position: -48px -80px; }
|
||||
.ui-icon-refresh { background-position: -64px -80px; }
|
||||
.ui-icon-shuffle { background-position: -80px -80px; }
|
||||
.ui-icon-transfer-e-w { background-position: -96px -80px; }
|
||||
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
|
||||
.ui-icon-folder-collapsed { background-position: 0 -96px; }
|
||||
.ui-icon-folder-open { background-position: -16px -96px; }
|
||||
.ui-icon-document { background-position: -32px -96px; }
|
||||
.ui-icon-document-b { background-position: -48px -96px; }
|
||||
.ui-icon-note { background-position: -64px -96px; }
|
||||
.ui-icon-mail-closed { background-position: -80px -96px; }
|
||||
.ui-icon-mail-open { background-position: -96px -96px; }
|
||||
.ui-icon-suitcase { background-position: -112px -96px; }
|
||||
.ui-icon-comment { background-position: -128px -96px; }
|
||||
.ui-icon-person { background-position: -144px -96px; }
|
||||
.ui-icon-print { background-position: -160px -96px; }
|
||||
.ui-icon-trash { background-position: -176px -96px; }
|
||||
.ui-icon-locked { background-position: -192px -96px; }
|
||||
.ui-icon-unlocked { background-position: -208px -96px; }
|
||||
.ui-icon-bookmark { background-position: -224px -96px; }
|
||||
.ui-icon-tag { background-position: -240px -96px; }
|
||||
.ui-icon-home { background-position: 0 -112px; }
|
||||
.ui-icon-flag { background-position: -16px -112px; }
|
||||
.ui-icon-calendar { background-position: -32px -112px; }
|
||||
.ui-icon-cart { background-position: -48px -112px; }
|
||||
.ui-icon-pencil { background-position: -64px -112px; }
|
||||
.ui-icon-clock { background-position: -80px -112px; }
|
||||
.ui-icon-disk { background-position: -96px -112px; }
|
||||
.ui-icon-calculator { background-position: -112px -112px; }
|
||||
.ui-icon-zoomin { background-position: -128px -112px; }
|
||||
.ui-icon-zoomout { background-position: -144px -112px; }
|
||||
.ui-icon-search { background-position: -160px -112px; }
|
||||
.ui-icon-wrench { background-position: -176px -112px; }
|
||||
.ui-icon-gear { background-position: -192px -112px; }
|
||||
.ui-icon-heart { background-position: -208px -112px; }
|
||||
.ui-icon-star { background-position: -224px -112px; }
|
||||
.ui-icon-link { background-position: -240px -112px; }
|
||||
.ui-icon-cancel { background-position: 0 -128px; }
|
||||
.ui-icon-plus { background-position: -16px -128px; }
|
||||
.ui-icon-plusthick { background-position: -32px -128px; }
|
||||
.ui-icon-minus { background-position: -48px -128px; }
|
||||
.ui-icon-minusthick { background-position: -64px -128px; }
|
||||
.ui-icon-close { background-position: -80px -128px; }
|
||||
.ui-icon-closethick { background-position: -96px -128px; }
|
||||
.ui-icon-key { background-position: -112px -128px; }
|
||||
.ui-icon-lightbulb { background-position: -128px -128px; }
|
||||
.ui-icon-scissors { background-position: -144px -128px; }
|
||||
.ui-icon-clipboard { background-position: -160px -128px; }
|
||||
.ui-icon-copy { background-position: -176px -128px; }
|
||||
.ui-icon-contact { background-position: -192px -128px; }
|
||||
.ui-icon-image { background-position: -208px -128px; }
|
||||
.ui-icon-video { background-position: -224px -128px; }
|
||||
.ui-icon-script { background-position: -240px -128px; }
|
||||
.ui-icon-alert { background-position: 0 -144px; }
|
||||
.ui-icon-info { background-position: -16px -144px; }
|
||||
.ui-icon-notice { background-position: -32px -144px; }
|
||||
.ui-icon-help { background-position: -48px -144px; }
|
||||
.ui-icon-check { background-position: -64px -144px; }
|
||||
.ui-icon-bullet { background-position: -80px -144px; }
|
||||
.ui-icon-radio-on { background-position: -96px -144px; }
|
||||
.ui-icon-radio-off { background-position: -112px -144px; }
|
||||
.ui-icon-pin-w { background-position: -128px -144px; }
|
||||
.ui-icon-pin-s { background-position: -144px -144px; }
|
||||
.ui-icon-play { background-position: 0 -160px; }
|
||||
.ui-icon-pause { background-position: -16px -160px; }
|
||||
.ui-icon-seek-next { background-position: -32px -160px; }
|
||||
.ui-icon-seek-prev { background-position: -48px -160px; }
|
||||
.ui-icon-seek-end { background-position: -64px -160px; }
|
||||
.ui-icon-seek-start { background-position: -80px -160px; }
|
||||
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
|
||||
.ui-icon-seek-first { background-position: -80px -160px; }
|
||||
.ui-icon-stop { background-position: -96px -160px; }
|
||||
.ui-icon-eject { background-position: -112px -160px; }
|
||||
.ui-icon-volume-off { background-position: -128px -160px; }
|
||||
.ui-icon-volume-on { background-position: -144px -160px; }
|
||||
.ui-icon-power { background-position: 0 -176px; }
|
||||
.ui-icon-signal-diag { background-position: -16px -176px; }
|
||||
.ui-icon-signal { background-position: -32px -176px; }
|
||||
.ui-icon-battery-0 { background-position: -48px -176px; }
|
||||
.ui-icon-battery-1 { background-position: -64px -176px; }
|
||||
.ui-icon-battery-2 { background-position: -80px -176px; }
|
||||
.ui-icon-battery-3 { background-position: -96px -176px; }
|
||||
.ui-icon-circle-plus { background-position: 0 -192px; }
|
||||
.ui-icon-circle-minus { background-position: -16px -192px; }
|
||||
.ui-icon-circle-close { background-position: -32px -192px; }
|
||||
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
|
||||
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
|
||||
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
|
||||
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
|
||||
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
|
||||
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
|
||||
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
|
||||
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
|
||||
.ui-icon-circle-zoomin { background-position: -176px -192px; }
|
||||
.ui-icon-circle-zoomout { background-position: -192px -192px; }
|
||||
.ui-icon-circle-check { background-position: -208px -192px; }
|
||||
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
|
||||
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
|
||||
.ui-icon-circlesmall-close { background-position: -32px -208px; }
|
||||
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
|
||||
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
|
||||
.ui-icon-squaresmall-close { background-position: -80px -208px; }
|
||||
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
|
||||
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
|
||||
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
|
||||
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
|
||||
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
|
||||
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
|
||||
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Corner radius */
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-left,
|
||||
.ui-corner-tl {
|
||||
border-top-left-radius: 4px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-right,
|
||||
.ui-corner-tr {
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-left,
|
||||
.ui-corner-bl {
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-right,
|
||||
.ui-corner-br {
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
background: #aaaaaa;
|
||||
opacity: .3;
|
||||
-ms-filter: Alpha(Opacity=30); /* support: IE8 */
|
||||
}
|
||||
.ui-widget-shadow {
|
||||
-webkit-box-shadow: -8px -8px 8px #aaaaaa;
|
||||
box-shadow: -8px -8px 8px #aaaaaa;
|
||||
}
|
4893
powerdnsadmin/static/assets/jquery-ui-smooth-datepicker/jquery-ui.js
vendored
Normal file
7
powerdnsadmin/static/assets/jquery-ui-smooth-datepicker/jquery-ui.min.css
vendored
Normal file
6
powerdnsadmin/static/assets/jquery-ui-smooth-datepicker/jquery-ui.min.js
vendored
Normal file
271
powerdnsadmin/static/assets/jquery-ui-smooth-datepicker/jquery-ui.structure.css
vendored
Normal file
@ -0,0 +1,271 @@
|
||||
/*!
|
||||
* jQuery UI CSS Framework 1.13.0
|
||||
* http://jqueryui.com
|
||||
*
|
||||
* Copyright jQuery Foundation and other contributors
|
||||
* Released under the MIT license.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://api.jqueryui.com/category/theming/
|
||||
*/
|
||||
/* Layout helpers
|
||||
----------------------------------*/
|
||||
.ui-helper-hidden {
|
||||
display: none;
|
||||
}
|
||||
.ui-helper-hidden-accessible {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
.ui-helper-reset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
font-size: 100%;
|
||||
list-style: none;
|
||||
}
|
||||
.ui-helper-clearfix:before,
|
||||
.ui-helper-clearfix:after {
|
||||
content: "";
|
||||
display: table;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.ui-helper-clearfix:after {
|
||||
clear: both;
|
||||
}
|
||||
.ui-helper-zfix {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
-ms-filter: "alpha(opacity=0)"; /* support: IE8 */
|
||||
}
|
||||
|
||||
.ui-front {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-disabled {
|
||||
cursor: default !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
.ui-icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-top: -.25em;
|
||||
position: relative;
|
||||
text-indent: -99999px;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.ui-widget-icon-block {
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.ui-datepicker {
|
||||
width: 17em;
|
||||
padding: .2em .2em 0;
|
||||
display: none;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-header {
|
||||
position: relative;
|
||||
padding: .2em 0;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev,
|
||||
.ui-datepicker .ui-datepicker-next {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev-hover,
|
||||
.ui-datepicker .ui-datepicker-next-hover {
|
||||
top: 1px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev {
|
||||
left: 2px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-next {
|
||||
right: 2px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev-hover {
|
||||
left: 1px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-next-hover {
|
||||
right: 1px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-prev span,
|
||||
.ui-datepicker .ui-datepicker-next span {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
top: 50%;
|
||||
margin-top: -8px;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-title {
|
||||
margin: 0 2.3em;
|
||||
line-height: 1.8em;
|
||||
text-align: center;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-title select {
|
||||
font-size: 1em;
|
||||
margin: 1px 0;
|
||||
}
|
||||
.ui-datepicker select.ui-datepicker-month,
|
||||
.ui-datepicker select.ui-datepicker-year {
|
||||
width: 45%;
|
||||
}
|
||||
.ui-datepicker table {
|
||||
width: 100%;
|
||||
font-size: .9em;
|
||||
border-collapse: collapse;
|
||||
margin: 0 0 .4em;
|
||||
}
|
||||
.ui-datepicker th {
|
||||
padding: .7em .3em;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
}
|
||||
.ui-datepicker td {
|
||||
border: 0;
|
||||
padding: 1px;
|
||||
}
|
||||
.ui-datepicker td span,
|
||||
.ui-datepicker td a {
|
||||
display: block;
|
||||
padding: .2em;
|
||||
text-align: right;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-buttonpane {
|
||||
background-image: none;
|
||||
margin: .7em 0 0 0;
|
||||
padding: 0 .2em;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-buttonpane button {
|
||||
float: right;
|
||||
margin: .5em .2em .4em;
|
||||
cursor: pointer;
|
||||
padding: .2em .6em .3em .6em;
|
||||
width: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* with multiple calendars */
|
||||
.ui-datepicker.ui-datepicker-multi {
|
||||
width: auto;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-group {
|
||||
float: left;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-group table {
|
||||
width: 95%;
|
||||
margin: 0 auto .4em;
|
||||
}
|
||||
.ui-datepicker-multi-2 .ui-datepicker-group {
|
||||
width: 50%;
|
||||
}
|
||||
.ui-datepicker-multi-3 .ui-datepicker-group {
|
||||
width: 33.3%;
|
||||
}
|
||||
.ui-datepicker-multi-4 .ui-datepicker-group {
|
||||
width: 25%;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
|
||||
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
|
||||
border-left-width: 0;
|
||||
}
|
||||
.ui-datepicker-multi .ui-datepicker-buttonpane {
|
||||
clear: left;
|
||||
}
|
||||
.ui-datepicker-row-break {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
/* RTL support */
|
||||
.ui-datepicker-rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-prev {
|
||||
right: 2px;
|
||||
left: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-next {
|
||||
left: 2px;
|
||||
right: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-prev:hover {
|
||||
right: 1px;
|
||||
left: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-next:hover {
|
||||
left: 1px;
|
||||
right: auto;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane {
|
||||
clear: right;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane button {
|
||||
float: left;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
|
||||
.ui-datepicker-rtl .ui-datepicker-group {
|
||||
float: right;
|
||||
}
|
||||
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
|
||||
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
|
||||
border-right-width: 0;
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
/* Icons */
|
||||
.ui-datepicker .ui-icon {
|
||||
display: block;
|
||||
text-indent: -99999px;
|
||||
overflow: hidden;
|
||||
background-repeat: no-repeat;
|
||||
left: .5em;
|
||||
top: .3em;
|
||||
}
|
5
powerdnsadmin/static/assets/jquery-ui-smooth-datepicker/jquery-ui.structure.min.css
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/*! jQuery UI - v1.13.0 - 2021-11-11
|
||||
* http://jqueryui.com
|
||||
* Copyright jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;-ms-filter:"alpha(opacity=0)"}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker .ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat;left:.5em;top:.3em}
|
446
powerdnsadmin/static/assets/jquery-ui-smooth-datepicker/jquery-ui.theme.css
vendored
Normal file
@ -0,0 +1,446 @@
|
||||
/*!
|
||||
* jQuery UI CSS Framework 1.13.0
|
||||
* http://jqueryui.com
|
||||
*
|
||||
* Copyright jQuery Foundation and other contributors
|
||||
* Released under the MIT license.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://api.jqueryui.com/category/theming/
|
||||
*
|
||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=smoothness&cornerRadiusShadow=8px&offsetLeftShadow=-8px&offsetTopShadow=-8px&thicknessShadow=8px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=aaaaaa&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cd0a0a&fcError=cd0a0a&borderColorError=cd0a0a&bgImgOpacityError=95&bgTextureError=glass&bgColorError=fef1ec&iconColorHighlight=2e83ff&fcHighlight=363636&borderColorHighlight=fcefa1&bgImgOpacityHighlight=55&bgTextureHighlight=glass&bgColorHighlight=fbf9ee&iconColorActive=454545&fcActive=212121&borderColorActive=aaaaaa&bgImgOpacityActive=65&bgTextureActive=glass&bgColorActive=ffffff&iconColorHover=454545&fcHover=212121&borderColorHover=999999&bgImgOpacityHover=75&bgTextureHover=glass&bgColorHover=dadada&iconColorDefault=888888&fcDefault=555555&borderColorDefault=d3d3d3&bgImgOpacityDefault=75&bgTextureDefault=glass&bgColorDefault=e6e6e6&iconColorContent=222222&fcContent=222222&borderColorContent=aaaaaa&bgImgOpacityContent=75&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=222222&fcHeader=222222&borderColorHeader=aaaaaa&bgImgOpacityHeader=75&bgTextureHeader=highlight_soft&bgColorHeader=cccccc&cornerRadius=4px&fsDefault=1.1em&fwDefault=normal&ffDefault=Verdana%2CArial%2Csans-serif
|
||||
*/
|
||||
|
||||
|
||||
/* Component containers
|
||||
----------------------------------*/
|
||||
.ui-widget {
|
||||
font-family: Verdana,Arial,sans-serif;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.ui-widget .ui-widget {
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget input,
|
||||
.ui-widget select,
|
||||
.ui-widget textarea,
|
||||
.ui-widget button {
|
||||
font-family: Verdana,Arial,sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
.ui-widget.ui-widget-content {
|
||||
border: 1px solid #d3d3d3;
|
||||
}
|
||||
.ui-widget-content {
|
||||
border: 1px solid #aaaaaa;
|
||||
background: #ffffff;
|
||||
color: #222222;
|
||||
}
|
||||
.ui-widget-content a {
|
||||
color: #222222;
|
||||
}
|
||||
.ui-widget-header {
|
||||
border: 1px solid #aaaaaa;
|
||||
background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x;
|
||||
color: #222222;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-widget-header a {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
/* Interaction states
|
||||
----------------------------------*/
|
||||
.ui-state-default,
|
||||
.ui-widget-content .ui-state-default,
|
||||
.ui-widget-header .ui-state-default,
|
||||
.ui-button,
|
||||
|
||||
/* We use html here because we need a greater specificity to make sure disabled
|
||||
works properly when clicked or hovered */
|
||||
html .ui-button.ui-state-disabled:hover,
|
||||
html .ui-button.ui-state-disabled:active {
|
||||
border: 1px solid #d3d3d3;
|
||||
background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: #555555;
|
||||
}
|
||||
.ui-state-default a,
|
||||
.ui-state-default a:link,
|
||||
.ui-state-default a:visited,
|
||||
a.ui-button,
|
||||
a:link.ui-button,
|
||||
a:visited.ui-button,
|
||||
.ui-button {
|
||||
color: #555555;
|
||||
text-decoration: none;
|
||||
}
|
||||
.ui-state-hover,
|
||||
.ui-widget-content .ui-state-hover,
|
||||
.ui-widget-header .ui-state-hover,
|
||||
.ui-state-focus,
|
||||
.ui-widget-content .ui-state-focus,
|
||||
.ui-widget-header .ui-state-focus,
|
||||
.ui-button:hover,
|
||||
.ui-button:focus {
|
||||
border: 1px solid #999999;
|
||||
background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: #212121;
|
||||
}
|
||||
.ui-state-hover a,
|
||||
.ui-state-hover a:hover,
|
||||
.ui-state-hover a:link,
|
||||
.ui-state-hover a:visited,
|
||||
.ui-state-focus a,
|
||||
.ui-state-focus a:hover,
|
||||
.ui-state-focus a:link,
|
||||
.ui-state-focus a:visited,
|
||||
a.ui-button:hover,
|
||||
a.ui-button:focus {
|
||||
color: #212121;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ui-visual-focus {
|
||||
box-shadow: 0 0 3px 1px rgb(94, 158, 214);
|
||||
}
|
||||
.ui-state-active,
|
||||
.ui-widget-content .ui-state-active,
|
||||
.ui-widget-header .ui-state-active,
|
||||
a.ui-button:active,
|
||||
.ui-button:active,
|
||||
.ui-button.ui-state-active:hover {
|
||||
border: 1px solid #aaaaaa;
|
||||
background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;
|
||||
font-weight: normal;
|
||||
color: #212121;
|
||||
}
|
||||
.ui-icon-background,
|
||||
.ui-state-active .ui-icon-background {
|
||||
border: #aaaaaa;
|
||||
background-color: #212121;
|
||||
}
|
||||
.ui-state-active a,
|
||||
.ui-state-active a:link,
|
||||
.ui-state-active a:visited {
|
||||
color: #212121;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-highlight,
|
||||
.ui-widget-content .ui-state-highlight,
|
||||
.ui-widget-header .ui-state-highlight {
|
||||
border: 1px solid #fcefa1;
|
||||
background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x;
|
||||
color: #363636;
|
||||
}
|
||||
.ui-state-checked {
|
||||
border: 1px solid #fcefa1;
|
||||
background: #fbf9ee;
|
||||
}
|
||||
.ui-state-highlight a,
|
||||
.ui-widget-content .ui-state-highlight a,
|
||||
.ui-widget-header .ui-state-highlight a {
|
||||
color: #363636;
|
||||
}
|
||||
.ui-state-error,
|
||||
.ui-widget-content .ui-state-error,
|
||||
.ui-widget-header .ui-state-error {
|
||||
border: 1px solid #cd0a0a;
|
||||
background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;
|
||||
color: #cd0a0a;
|
||||
}
|
||||
.ui-state-error a,
|
||||
.ui-widget-content .ui-state-error a,
|
||||
.ui-widget-header .ui-state-error a {
|
||||
color: #cd0a0a;
|
||||
}
|
||||
.ui-state-error-text,
|
||||
.ui-widget-content .ui-state-error-text,
|
||||
.ui-widget-header .ui-state-error-text {
|
||||
color: #cd0a0a;
|
||||
}
|
||||
.ui-priority-primary,
|
||||
.ui-widget-content .ui-priority-primary,
|
||||
.ui-widget-header .ui-priority-primary {
|
||||
font-weight: bold;
|
||||
}
|
||||
.ui-priority-secondary,
|
||||
.ui-widget-content .ui-priority-secondary,
|
||||
.ui-widget-header .ui-priority-secondary {
|
||||
opacity: .7;
|
||||
-ms-filter: "alpha(opacity=70)"; /* support: IE8 */
|
||||
font-weight: normal;
|
||||
}
|
||||
.ui-state-disabled,
|
||||
.ui-widget-content .ui-state-disabled,
|
||||
.ui-widget-header .ui-state-disabled {
|
||||
opacity: .35;
|
||||
-ms-filter: "alpha(opacity=35)"; /* support: IE8 */
|
||||
background-image: none;
|
||||
}
|
||||
.ui-state-disabled .ui-icon {
|
||||
-ms-filter: "alpha(opacity=35)"; /* support: IE8 - See #6059 */
|
||||
}
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
|
||||
/* states and images */
|
||||
.ui-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.ui-icon,
|
||||
.ui-widget-content .ui-icon {
|
||||
background-image: url("images/ui-icons_222222_256x240.png");
|
||||
}
|
||||
.ui-widget-header .ui-icon {
|
||||
background-image: url("images/ui-icons_222222_256x240.png");
|
||||
}
|
||||
.ui-state-hover .ui-icon,
|
||||
.ui-state-focus .ui-icon,
|
||||
.ui-button:hover .ui-icon,
|
||||
.ui-button:focus .ui-icon {
|
||||
background-image: url("images/ui-icons_454545_256x240.png");
|
||||
}
|
||||
.ui-state-active .ui-icon,
|
||||
.ui-button:active .ui-icon {
|
||||
background-image: url("images/ui-icons_454545_256x240.png");
|
||||
}
|
||||
.ui-state-highlight .ui-icon,
|
||||
.ui-button .ui-state-highlight.ui-icon {
|
||||
background-image: url("images/ui-icons_2e83ff_256x240.png");
|
||||
}
|
||||
.ui-state-error .ui-icon,
|
||||
.ui-state-error-text .ui-icon {
|
||||
background-image: url("images/ui-icons_cd0a0a_256x240.png");
|
||||
}
|
||||
.ui-button .ui-icon {
|
||||
background-image: url("images/ui-icons_888888_256x240.png");
|
||||
}
|
||||
|
||||
/* positioning */
|
||||
/* Three classes needed to override `.ui-button:hover .ui-icon` */
|
||||
.ui-icon-blank.ui-icon-blank.ui-icon-blank {
|
||||
background-image: none;
|
||||
}
|
||||
.ui-icon-caret-1-n { background-position: 0 0; }
|
||||
.ui-icon-caret-1-ne { background-position: -16px 0; }
|
||||
.ui-icon-caret-1-e { background-position: -32px 0; }
|
||||
.ui-icon-caret-1-se { background-position: -48px 0; }
|
||||
.ui-icon-caret-1-s { background-position: -65px 0; }
|
||||
.ui-icon-caret-1-sw { background-position: -80px 0; }
|
||||
.ui-icon-caret-1-w { background-position: -96px 0; }
|
||||
.ui-icon-caret-1-nw { background-position: -112px 0; }
|
||||
.ui-icon-caret-2-n-s { background-position: -128px 0; }
|
||||
.ui-icon-caret-2-e-w { background-position: -144px 0; }
|
||||
.ui-icon-triangle-1-n { background-position: 0 -16px; }
|
||||
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
|
||||
.ui-icon-triangle-1-e { background-position: -32px -16px; }
|
||||
.ui-icon-triangle-1-se { background-position: -48px -16px; }
|
||||
.ui-icon-triangle-1-s { background-position: -65px -16px; }
|
||||
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
|
||||
.ui-icon-triangle-1-w { background-position: -96px -16px; }
|
||||
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
|
||||
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
|
||||
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
|
||||
.ui-icon-arrow-1-n { background-position: 0 -32px; }
|
||||
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
|
||||
.ui-icon-arrow-1-e { background-position: -32px -32px; }
|
||||
.ui-icon-arrow-1-se { background-position: -48px -32px; }
|
||||
.ui-icon-arrow-1-s { background-position: -65px -32px; }
|
||||
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
|
||||
.ui-icon-arrow-1-w { background-position: -96px -32px; }
|
||||
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
|
||||
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
|
||||
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
|
||||
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
|
||||
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
|
||||
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
|
||||
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
|
||||
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
|
||||
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
|
||||
.ui-icon-arrowthick-1-n { background-position: 1px -48px; }
|
||||
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
|
||||
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
|
||||
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
|
||||
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
|
||||
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
|
||||
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
|
||||
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
|
||||
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
|
||||
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
|
||||
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
|
||||
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
|
||||
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
|
||||
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
|
||||
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
|
||||
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
|
||||
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
|
||||
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
|
||||
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
|
||||
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
|
||||
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
|
||||
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
|
||||
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
|
||||
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
|
||||
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
|
||||
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
|
||||
.ui-icon-arrow-4 { background-position: 0 -80px; }
|
||||
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
|
||||
.ui-icon-extlink { background-position: -32px -80px; }
|
||||
.ui-icon-newwin { background-position: -48px -80px; }
|
||||
.ui-icon-refresh { background-position: -64px -80px; }
|
||||
.ui-icon-shuffle { background-position: -80px -80px; }
|
||||
.ui-icon-transfer-e-w { background-position: -96px -80px; }
|
||||
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
|
||||
.ui-icon-folder-collapsed { background-position: 0 -96px; }
|
||||
.ui-icon-folder-open { background-position: -16px -96px; }
|
||||
.ui-icon-document { background-position: -32px -96px; }
|
||||
.ui-icon-document-b { background-position: -48px -96px; }
|
||||
.ui-icon-note { background-position: -64px -96px; }
|
||||
.ui-icon-mail-closed { background-position: -80px -96px; }
|
||||
.ui-icon-mail-open { background-position: -96px -96px; }
|
||||
.ui-icon-suitcase { background-position: -112px -96px; }
|
||||
.ui-icon-comment { background-position: -128px -96px; }
|
||||
.ui-icon-person { background-position: -144px -96px; }
|
||||
.ui-icon-print { background-position: -160px -96px; }
|
||||
.ui-icon-trash { background-position: -176px -96px; }
|
||||
.ui-icon-locked { background-position: -192px -96px; }
|
||||
.ui-icon-unlocked { background-position: -208px -96px; }
|
||||
.ui-icon-bookmark { background-position: -224px -96px; }
|
||||
.ui-icon-tag { background-position: -240px -96px; }
|
||||
.ui-icon-home { background-position: 0 -112px; }
|
||||
.ui-icon-flag { background-position: -16px -112px; }
|
||||
.ui-icon-calendar { background-position: -32px -112px; }
|
||||
.ui-icon-cart { background-position: -48px -112px; }
|
||||
.ui-icon-pencil { background-position: -64px -112px; }
|
||||
.ui-icon-clock { background-position: -80px -112px; }
|
||||
.ui-icon-disk { background-position: -96px -112px; }
|
||||
.ui-icon-calculator { background-position: -112px -112px; }
|
||||
.ui-icon-zoomin { background-position: -128px -112px; }
|
||||
.ui-icon-zoomout { background-position: -144px -112px; }
|
||||
.ui-icon-search { background-position: -160px -112px; }
|
||||
.ui-icon-wrench { background-position: -176px -112px; }
|
||||
.ui-icon-gear { background-position: -192px -112px; }
|
||||
.ui-icon-heart { background-position: -208px -112px; }
|
||||
.ui-icon-star { background-position: -224px -112px; }
|
||||
.ui-icon-link { background-position: -240px -112px; }
|
||||
.ui-icon-cancel { background-position: 0 -128px; }
|
||||
.ui-icon-plus { background-position: -16px -128px; }
|
||||
.ui-icon-plusthick { background-position: -32px -128px; }
|
||||
.ui-icon-minus { background-position: -48px -128px; }
|
||||
.ui-icon-minusthick { background-position: -64px -128px; }
|
||||
.ui-icon-close { background-position: -80px -128px; }
|
||||
.ui-icon-closethick { background-position: -96px -128px; }
|
||||
.ui-icon-key { background-position: -112px -128px; }
|
||||
.ui-icon-lightbulb { background-position: -128px -128px; }
|
||||
.ui-icon-scissors { background-position: -144px -128px; }
|
||||
.ui-icon-clipboard { background-position: -160px -128px; }
|
||||
.ui-icon-copy { background-position: -176px -128px; }
|
||||
.ui-icon-contact { background-position: -192px -128px; }
|
||||
.ui-icon-image { background-position: -208px -128px; }
|
||||
.ui-icon-video { background-position: -224px -128px; }
|
||||
.ui-icon-script { background-position: -240px -128px; }
|
||||
.ui-icon-alert { background-position: 0 -144px; }
|
||||
.ui-icon-info { background-position: -16px -144px; }
|
||||
.ui-icon-notice { background-position: -32px -144px; }
|
||||
.ui-icon-help { background-position: -48px -144px; }
|
||||
.ui-icon-check { background-position: -64px -144px; }
|
||||
.ui-icon-bullet { background-position: -80px -144px; }
|
||||
.ui-icon-radio-on { background-position: -96px -144px; }
|
||||
.ui-icon-radio-off { background-position: -112px -144px; }
|
||||
.ui-icon-pin-w { background-position: -128px -144px; }
|
||||
.ui-icon-pin-s { background-position: -144px -144px; }
|
||||
.ui-icon-play { background-position: 0 -160px; }
|
||||
.ui-icon-pause { background-position: -16px -160px; }
|
||||
.ui-icon-seek-next { background-position: -32px -160px; }
|
||||
.ui-icon-seek-prev { background-position: -48px -160px; }
|
||||
.ui-icon-seek-end { background-position: -64px -160px; }
|
||||
.ui-icon-seek-start { background-position: -80px -160px; }
|
||||
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
|
||||
.ui-icon-seek-first { background-position: -80px -160px; }
|
||||
.ui-icon-stop { background-position: -96px -160px; }
|
||||
.ui-icon-eject { background-position: -112px -160px; }
|
||||
.ui-icon-volume-off { background-position: -128px -160px; }
|
||||
.ui-icon-volume-on { background-position: -144px -160px; }
|
||||
.ui-icon-power { background-position: 0 -176px; }
|
||||
.ui-icon-signal-diag { background-position: -16px -176px; }
|
||||
.ui-icon-signal { background-position: -32px -176px; }
|
||||
.ui-icon-battery-0 { background-position: -48px -176px; }
|
||||
.ui-icon-battery-1 { background-position: -64px -176px; }
|
||||
.ui-icon-battery-2 { background-position: -80px -176px; }
|
||||
.ui-icon-battery-3 { background-position: -96px -176px; }
|
||||
.ui-icon-circle-plus { background-position: 0 -192px; }
|
||||
.ui-icon-circle-minus { background-position: -16px -192px; }
|
||||
.ui-icon-circle-close { background-position: -32px -192px; }
|
||||
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
|
||||
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
|
||||
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
|
||||
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
|
||||
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
|
||||
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
|
||||
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
|
||||
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
|
||||
.ui-icon-circle-zoomin { background-position: -176px -192px; }
|
||||
.ui-icon-circle-zoomout { background-position: -192px -192px; }
|
||||
.ui-icon-circle-check { background-position: -208px -192px; }
|
||||
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
|
||||
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
|
||||
.ui-icon-circlesmall-close { background-position: -32px -208px; }
|
||||
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
|
||||
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
|
||||
.ui-icon-squaresmall-close { background-position: -80px -208px; }
|
||||
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
|
||||
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
|
||||
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
|
||||
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
|
||||
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
|
||||
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
|
||||
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Corner radius */
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-left,
|
||||
.ui-corner-tl {
|
||||
border-top-left-radius: 4px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-top,
|
||||
.ui-corner-right,
|
||||
.ui-corner-tr {
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-left,
|
||||
.ui-corner-bl {
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.ui-corner-all,
|
||||
.ui-corner-bottom,
|
||||
.ui-corner-right,
|
||||
.ui-corner-br {
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay {
|
||||
background: #aaaaaa;
|
||||
opacity: .3;
|
||||
-ms-filter: Alpha(Opacity=30); /* support: IE8 */
|
||||
}
|
||||
.ui-widget-shadow {
|
||||
-webkit-box-shadow: -8px -8px 8px #aaaaaa;
|
||||
box-shadow: -8px -8px 8px #aaaaaa;
|
||||
}
|
5
powerdnsadmin/static/assets/jquery-ui-smooth-datepicker/jquery-ui.theme.min.css
vendored
Normal file
@ -53,6 +53,7 @@ function applyRecordChanges(data, domain) {
|
||||
var modal = $("#modal_success");
|
||||
modal.find('.modal-body p').text("Applied changes successfully");
|
||||
modal.modal('show');
|
||||
setTimeout(() => {window.location.reload()}, 2000);
|
||||
},
|
||||
|
||||
error : function(jqXHR, status) {
|
||||
|
@ -13,7 +13,12 @@
|
||||
<li class="active">History</li>
|
||||
</ol>
|
||||
</section>
|
||||
{% endblock %} {% block content %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% import 'applied_change_macro.html' as applied_change_macro %}
|
||||
|
||||
|
||||
|
||||
<section class="content">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
@ -28,32 +33,134 @@
|
||||
Clear History <i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="box-body clearfix">
|
||||
<form id="history-search-form" autocomplete="off">
|
||||
<!-- Custom Tabs -->
|
||||
<div class="nav-tabs-custom" id="tabs">
|
||||
<ul class="nav nav-tabs" id="nav_nav_tabs" name="nav_nav_tabs">
|
||||
<li id="activity_tab" class="active"><a href="#tabs-act" data-toggle="tab">Search for All Activity</a></li>
|
||||
<li id="domain_tab"><a href="#tabs-domain" data-toggle="tab">Search By Domain</a></li>
|
||||
<li id="account_tab"><a href="#tabs-account" data-toggle="tab">Search By Account</a></li>
|
||||
{% if current_user.role.name != 'User' %}
|
||||
<li id="user_auth_tab"><a href="#tabs-auth" data-toggle="tab">Search for User Authentication</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane" id="tabs-act">
|
||||
</div>
|
||||
<div class="tab-pane" id="tabs-domain">
|
||||
<td><label>Domain Name</label></td>
|
||||
<td>
|
||||
<div class="autocomplete" style="width:250px;">
|
||||
<input type="text" class="form-control" id="domain_name_filter" name="domain_name_filter" placeholder="Enter * to search for any domain" value="">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div style="position: relative; top:10px;">
|
||||
<td>Record Changelog only  </td>
|
||||
<td>
|
||||
<input type="checkbox" id="domain_changelog_only_checkbox" name="domain_changelog_only_checkbox"
|
||||
class="checkbox" style="border:2px dotted #00f;display:block;background:#ff0000;">
|
||||
</td>
|
||||
</div>
|
||||
</td>
|
||||
</div>
|
||||
<div class="tab-pane" id="tabs-account">
|
||||
<td><label>Account Name</label></td>
|
||||
<td>
|
||||
<div class="autocomplete" style="width:250px;">
|
||||
<input type="text" class="form-control" id="account_name_filter" name="account_name_filter" placeholder="Enter * to search for any account" value="">
|
||||
</div>
|
||||
</td>
|
||||
</div>
|
||||
<div class="tab-pane" id="tabs-auth">
|
||||
<td><label>Username</label></td>
|
||||
<td>
|
||||
<div class="autocomplete" style="width:250px;">
|
||||
<input type="text" class="form-control" id="auth_name_filter" name="auth_name_filter" placeholder="Enter * to search for any username" value="">
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div style="position: relative; top:10px;">
|
||||
<td>Authenticator Types:  </td>
|
||||
<td>  All</td>
|
||||
<td>
|
||||
<input type="checkbox" checked id="auth_all_checkbox" name="auth_all_checkbox"
|
||||
class="checkbox" style="border:2px dotted #00f;display:block;background:#ff0000;">
|
||||
</td>
|
||||
<td>  LOCAL</td>
|
||||
<td>
|
||||
<input type="checkbox" checked id="auth_local_only_checkbox" name="auth_local_only_checkbox"
|
||||
class="checkbox" style="border:2px dotted #00f;display:block;background:#ff0000;">
|
||||
</td>
|
||||
<td>  OAuth</td>
|
||||
<td>
|
||||
<input type="checkbox" checked id="auth_oauth_only_checkbox" name="auth_oauth_only_checkbox"
|
||||
class="checkbox" style="border:2px dotted #00f;display:block;background:#ff0000;">
|
||||
</td>
|
||||
<td>  SAML</td>
|
||||
<td>
|
||||
<input type="checkbox" checked id="auth_saml_only_checkbox" name="auth_saml_only_checkbox"
|
||||
class="checkbox" style="border:2px dotted #00f;display:block;background:#ff0000;">
|
||||
</td>
|
||||
</div>
|
||||
</td>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Custom Tabs -->
|
||||
|
||||
<div class="box-body">
|
||||
<table id="tbl_history" class="table table-bordered table-striped">
|
||||
<table id="Filters-Table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Changed by</th>
|
||||
<th>Content</th>
|
||||
<th>Time</th>
|
||||
<th>Detail</th>
|
||||
</tr>
|
||||
<th>Filters</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for history in histories %}
|
||||
<tr class="odd gradeX">
|
||||
<td>{{ history.created_by }}</td>
|
||||
<td>{{ history.msg }}</td>
|
||||
<td>{{ history.created_on }}</td>
|
||||
<td width="6%">
|
||||
<button type="button" class="btn btn-flat btn-primary history-info-button"
|
||||
value='{{ history.detail }}'>Info <i class="fa fa-info"></i>
|
||||
</button>
|
||||
<tr>
|
||||
<td><label>Changed by:  </label></td>
|
||||
<td>
|
||||
<div class="autocomplete" style="width:250px;">
|
||||
<input type="text" style=" border:1px solid #d2d6de; width:250px; height: 34px;" id="user_name_filter" name="user_name_filter" value="">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="position: relative; top:10px;">
|
||||
<label>Minimum date:  </label>
|
||||
</td>
|
||||
<td style="position: relative; top:10px;">
|
||||
<input type="text" id="min" name="min" class="datepicker" autocomplete="off" style=" border:1px solid #d2d6de; width:250px; height: 34px;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="position: relative; top:20px;">
|
||||
<label>Maximum date:  </label>
|
||||
</td>
|
||||
<td style="position: relative; top:20px;">
|
||||
<input type="text" id="max" name="max" class="datepicker" autocomplete="off" style=" border:1px solid #d2d6de; width:250px; height: 34px;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td> </td></tr>
|
||||
<tr><td> </td></tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button type="submit" id="search-submit" name="search-submit" class="btn btn-flat btn-primary button-filter">Search <i class="fa fa-search"></i></button>
|
||||
</td>
|
||||
<td>
|
||||
<!-- -->
|
||||
<button id="clear-filters" name="clear-filters" class="btn btn-flat btn-warning button-clearf">Clear Filters <i class="fa fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="table_from_ajax"></div>
|
||||
|
||||
|
||||
<!-- /.box-body -->
|
||||
</div>
|
||||
<!-- /.box -->
|
||||
@ -65,31 +172,302 @@
|
||||
{% endblock %}
|
||||
{% block extrascripts %}
|
||||
<script>
|
||||
// set up history data table
|
||||
$("#tbl_history").DataTable({
|
||||
"paging": true,
|
||||
"lengthChange": false,
|
||||
"searching": true,
|
||||
"ordering": true,
|
||||
"info": true,
|
||||
"autoWidth": false,
|
||||
"order": [
|
||||
[2, "desc"]
|
||||
],
|
||||
"columnDefs": [{
|
||||
"type": "time",
|
||||
"render": function (data, type, row) {
|
||||
return moment.utc(data).local().format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
/* Don't let user search with a blank main field */
|
||||
var canSearch=true;
|
||||
|
||||
$(document).ready(function () {
|
||||
$.ajax({
|
||||
url: "/admin/history_table",
|
||||
type: "get",
|
||||
success: function(response) {
|
||||
console.log('Submission was successful.');
|
||||
$("#table_from_ajax").html(response);
|
||||
},
|
||||
"targets": 2
|
||||
}]
|
||||
error: function(xhr) {
|
||||
console.log("Sending data: ", data, " failed")
|
||||
}
|
||||
});
|
||||
$(document.body).on('click', '.history-info-button', function () {
|
||||
var modal = $("#modal_history_info");
|
||||
var info = $(this).val();
|
||||
$('#modal-code-content').html(json_library.prettyPrint(info));
|
||||
|
||||
var minDate = $('#min');
|
||||
var maxDate = $('#max');
|
||||
domain_changelog = $('domain_changelog_only_checkbox');
|
||||
|
||||
// Show/hide filters
|
||||
$('#domain_name_filter, #account_name_filter, #auth_name_filter').on('keyup change', function (e) {
|
||||
if ( $('#domain_name_filter').val() == "" && $('#account_name_filter').val() == "" && $('#auth_name_filter').val() == "")
|
||||
canSearch=false;
|
||||
else
|
||||
canSearch=true;
|
||||
});
|
||||
|
||||
// Handle giving later mindate than current max
|
||||
$('#min').on('change', function () {
|
||||
if (minDate.val() > maxDate.val())
|
||||
$('#max').datepicker('setDate', minDate.val() );
|
||||
});
|
||||
|
||||
// Handle giving earlier maxdate than current min
|
||||
$('#max').on('keyup change', function () {
|
||||
if (maxDate.val() < minDate.val())
|
||||
$('#min').datepicker('setDate', maxDate.val() );
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$( ".datepicker" ).datepicker({
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
maxDate: '+0'
|
||||
});
|
||||
$(".datepicker").datepicker("option", "dateFormat", "yy-mm-dd")
|
||||
});
|
||||
});
|
||||
|
||||
$('.checkbox,.radio').iCheck({
|
||||
checkboxClass: 'icheckbox_square-blue',
|
||||
radioClass: 'iradio_square-blue',
|
||||
increaseArea: '20%'
|
||||
});
|
||||
|
||||
//Handle "ALL" Checkbox
|
||||
$('#auth_all_checkbox').on('ifChecked',function() {
|
||||
$('#auth_local_only_checkbox').iCheck('check');
|
||||
$('#auth_oauth_only_checkbox').iCheck('check');
|
||||
$('#auth_saml_only_checkbox').iCheck('check');
|
||||
});
|
||||
|
||||
$('#auth_all_checkbox').on('ifUnchecked',function() {
|
||||
//check if all were checked
|
||||
if($('#auth_local_only_checkbox').is(':checked') && $('#auth_oauth_only_checkbox').is(':checked') && $('#auth_saml_only_checkbox').is(':checked'))
|
||||
{
|
||||
$('#auth_local_only_checkbox').iCheck('uncheck');
|
||||
$('#auth_oauth_only_checkbox').iCheck('uncheck');
|
||||
$('#auth_saml_only_checkbox').iCheck('uncheck');
|
||||
}
|
||||
});
|
||||
|
||||
//Handle other auth checkboxes
|
||||
$('#auth_local_only_checkbox').on('ifChecked',function() {
|
||||
//check if all others were checked
|
||||
if($('#auth_oauth_only_checkbox').is(':checked') && $('#auth_saml_only_checkbox').is(':checked'))
|
||||
$('#auth_all_checkbox').iCheck('check');
|
||||
});
|
||||
$('#auth_local_only_checkbox').on('ifUnchecked',function() {
|
||||
$('#auth_all_checkbox').iCheck('uncheck');
|
||||
});
|
||||
|
||||
$('#auth_oauth_only_checkbox').on('ifChecked',function() {
|
||||
if($('#auth_local_only_checkbox').is(':checked') && $('#auth_saml_only_checkbox').is(':checked'))
|
||||
$('#auth_all_checkbox').iCheck('check');
|
||||
});
|
||||
$('#auth_oauth_only_checkbox').on('ifUnchecked',function() {
|
||||
$('#auth_all_checkbox').iCheck('uncheck');
|
||||
});
|
||||
|
||||
$('#auth_saml_only_checkbox').on('ifChecked',function() {
|
||||
if($('#auth_local_only_checkbox').is(':checked') && $('#auth_oauth_only_checkbox').is(':checked'))
|
||||
$('#auth_all_checkbox').iCheck('check');
|
||||
});
|
||||
$('#auth_saml_only_checkbox').on('ifUnchecked',function() {
|
||||
$('#auth_all_checkbox').iCheck('uncheck');
|
||||
});
|
||||
|
||||
$(document.body).on("click", ".button-clearf", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
$('#user_name_filter').val('');
|
||||
$('#min').val('');
|
||||
$('#max').val('');
|
||||
$('#domain_name_filter').val('');
|
||||
$('#account_name_filter').val('');
|
||||
$('#auth_name_filter').val('');
|
||||
$('#auth_all_checkbox').iCheck('check');
|
||||
$('#domain_changelog_only_checkbox').iCheck('uncheck');
|
||||
});
|
||||
|
||||
var all_doms = "{{all_domain_names}}".split(" ");
|
||||
var all_accounts = "{{all_account_names}}".split(" ");
|
||||
var all_usernames = "{{all_usernames}}".split(" ");
|
||||
all_doms.pop(); // remove last element which is " "
|
||||
all_accounts.pop();
|
||||
all_usernames.pop();
|
||||
|
||||
function autocomplete(inp, arr) {
|
||||
/*the autocomplete function takes two arguments,
|
||||
the text field element and an array of possible autocompleted values:*/
|
||||
var currentFocus;
|
||||
/*execute a function when someone writes in the text field:*/
|
||||
inp.addEventListener("input", function(e) {
|
||||
var a, b, i, val = this.value;
|
||||
/*close any already open lists of autocompleted values*/
|
||||
closeAllLists();
|
||||
if (!val) { return false;}
|
||||
currentFocus = -1;
|
||||
/*create a DIV element that will contain the items (values):*/
|
||||
a = document.createElement("DIV");
|
||||
a.setAttribute("id", this.id + "autocomplete-list");
|
||||
a.setAttribute("class", "autocomplete-items");
|
||||
/*append the DIV element as a child of the autocomplete container:*/
|
||||
this.parentNode.appendChild(a);
|
||||
/*for each item in the array...*/
|
||||
for (i = 0; i < arr.length; i++) {
|
||||
/*check if the item starts with the same letters as the text field value:*/
|
||||
if (arr[i].substr(0, val.length).toUpperCase() == val.toUpperCase()) {
|
||||
/*create a DIV element for each matching element:*/
|
||||
b = document.createElement("DIV");
|
||||
/*make the matching letters bold:*/
|
||||
b.innerHTML = "<strong>" + arr[i].substr(0, val.length) + "</strong>";
|
||||
b.innerHTML += arr[i].substr(val.length);
|
||||
/*insert a input field that will hold the current array item's value:*/
|
||||
b.innerHTML += "<input type='hidden' value='" + arr[i] + "'>";
|
||||
/*execute a function when someone clicks on the item value (DIV element):*/
|
||||
b.addEventListener("click", function(e) {
|
||||
/*insert the value for the autocomplete text field:*/
|
||||
inp.value = this.getElementsByTagName("input")[0].value;
|
||||
/*close the list of autocompleted values,
|
||||
(or any other open lists of autocompleted values:*/
|
||||
closeAllLists();
|
||||
});
|
||||
a.appendChild(b);
|
||||
}
|
||||
}
|
||||
});
|
||||
/*execute a function presses a key on the keyboard:*/
|
||||
inp.addEventListener("keydown", function(e) {
|
||||
var x = document.getElementById(this.id + "autocomplete-list");
|
||||
if (x) x = x.getElementsByTagName("div");
|
||||
if (e.keyCode == 40) {
|
||||
/*If the arrow DOWN key is pressed,
|
||||
increase the currentFocus variable:*/
|
||||
currentFocus++;
|
||||
/*and and make the current item more visible:*/
|
||||
addActive(x);
|
||||
} else if (e.keyCode == 38) { //up
|
||||
/*If the arrow UP key is pressed,
|
||||
decrease the currentFocus variable:*/
|
||||
currentFocus--;
|
||||
/*and and make the current item more visible:*/
|
||||
addActive(x);
|
||||
} else if (e.keyCode == 13) {
|
||||
/*If the ENTER key is pressed, prevent the form from being submitted,*/
|
||||
e.preventDefault();
|
||||
if (currentFocus > -1) {
|
||||
/*and simulate a click on the "active" item:*/
|
||||
if (x) x[currentFocus].click();
|
||||
}
|
||||
}
|
||||
});
|
||||
function addActive(x) {
|
||||
/*a function to classify an item as "active":*/
|
||||
if (!x) return false;
|
||||
/*start by removing the "active" class on all items:*/
|
||||
removeActive(x);
|
||||
if (currentFocus >= x.length) currentFocus = 0;
|
||||
if (currentFocus < 0) currentFocus = (x.length - 1);
|
||||
/*add class "autocomplete-active":*/
|
||||
x[currentFocus].classList.add("autocomplete-active");
|
||||
}
|
||||
function removeActive(x) {
|
||||
/*a function to remove the "active" class from all autocomplete items:*/
|
||||
for (var i = 0; i < x.length; i++) {
|
||||
x[i].classList.remove("autocomplete-active");
|
||||
}
|
||||
}
|
||||
function closeAllLists(elmnt) {
|
||||
/*close all autocomplete lists in the document,
|
||||
except the one passed as an argument:*/
|
||||
var x = document.getElementsByClassName("autocomplete-items");
|
||||
for (var i = 0; i < x.length; i++) {
|
||||
if (elmnt != x[i] && elmnt != inp) {
|
||||
x[i].parentNode.removeChild(x[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*execute a function when someone clicks in the document:*/
|
||||
document.addEventListener("click", function (e) {
|
||||
closeAllLists(e.target);
|
||||
});
|
||||
}
|
||||
|
||||
/*initiate the autocomplete function on the "myInput" element, and pass along the countries array as possible autocomplete values:*/
|
||||
autocomplete(document.getElementById("domain_name_filter"), all_doms);
|
||||
autocomplete(document.getElementById("account_name_filter"), all_accounts);
|
||||
autocomplete(document.getElementById("auth_name_filter"), all_usernames);
|
||||
autocomplete(document.getElementById("user_name_filter"), all_usernames);
|
||||
|
||||
|
||||
// prevent multiple filter field at the same time
|
||||
$('#domain_tab').click(function() {
|
||||
$('#account_name_filter').val('');
|
||||
$('#auth_name_filter').val('');
|
||||
$('#user_name_filter').removeAttr('disabled');
|
||||
canSearch=false;
|
||||
main_field="Domain Name"
|
||||
});
|
||||
|
||||
$('#account_tab').click(function() {
|
||||
$('#domain_name_filter').val('');
|
||||
$('#auth_name_filter').val('');
|
||||
$('#user_name_filter').removeAttr('disabled');
|
||||
canSearch=false;
|
||||
main_field="Account Name"
|
||||
});
|
||||
|
||||
$('#user_auth_tab').click( function() {
|
||||
$('#domain_name_filter').val('');
|
||||
$('#account_name_filter').val('');
|
||||
$('#user_name_filter').val('');
|
||||
$('#user_name_filter').attr('disabled','disabled');
|
||||
canSearch=false;
|
||||
main_field="Username"
|
||||
});
|
||||
|
||||
$('#activity_tab').click( function() {
|
||||
$('#domain_name_filter').val('');
|
||||
$('#account_name_filter').val('');
|
||||
$('#auth_name_filter').val('');
|
||||
$('#user_name_filter').removeAttr('disabled');
|
||||
$('#search-submit').removeAttr('disabled','disabled');
|
||||
canSearch=true;
|
||||
main_field=""
|
||||
});
|
||||
|
||||
// if search submit is pressed, and max date not initialized
|
||||
// then initialize it
|
||||
$('#search-submit').on('click', function() {
|
||||
if ($('#max').val() === "" || $('#max').val() === undefined)
|
||||
$('#max').datepicker('setDate', new Date());
|
||||
});
|
||||
|
||||
$("#history-search-form").submit(function(e){ // ajax call to load results on submition
|
||||
e.preventDefault(); // prevent page reloading
|
||||
|
||||
if(!canSearch)
|
||||
{
|
||||
var modal = $("#modal_error");
|
||||
modal.find('.modal-body p').text("Please fill out the " + main_field + " field.");
|
||||
modal.modal('show');
|
||||
}
|
||||
else
|
||||
{
|
||||
var form = $(this);
|
||||
var tzoffset = (new Date()).getTimezoneOffset();
|
||||
$.ajax({
|
||||
url: "/admin/history_table",
|
||||
type: "get",
|
||||
data: form.serialize() + "&tzoffset=" + tzoffset,
|
||||
success: function(response) {
|
||||
console.log('Submission was successful.');
|
||||
$("#table_from_ajax").html(response);
|
||||
|
||||
},
|
||||
error: function(xhr) {
|
||||
console.log("Sending data: ", data, " failed")
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block modals %}
|
||||
@ -127,7 +505,7 @@
|
||||
<h4 class="modal-title">History Details</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre><code id="modal-code-content"></code></pre>
|
||||
<div id="modal-info-content"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-flat btn-default pull-right" data-dismiss="modal">Close</button>
|
||||
|
104
powerdnsadmin/templates/admin_history_table.html
Normal file
@ -0,0 +1,104 @@
|
||||
|
||||
{% import 'applied_change_macro.html' as applied_change_macro %}
|
||||
|
||||
|
||||
{% if len_histories >= lim %}
|
||||
<p style="color: rgb(224, 3, 3);"><b>Limit of loaded history records has been reached! Only {{lim}} history records are shown. </b></p>
|
||||
{% endif %}
|
||||
|
||||
<div class="box-body"></div>
|
||||
<table id="tbl_history" class="table table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Changed by</th>
|
||||
<th>Content</th>
|
||||
<th>Time</th>
|
||||
<th>Detail</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for history in histories %}
|
||||
<tr class="odd gradeX">
|
||||
<td>{{ history.history.created_by }}</td>
|
||||
<td>{{ history.history.msg }}</td>
|
||||
<td>{{ history.history.created_on }}</td>
|
||||
|
||||
<td width="6%">
|
||||
<button type="button" class="btn btn-flat btn-primary history-info-button"
|
||||
{% if history.detailed_msg == "" and history.change_set == None %}
|
||||
style="visibility: hidden;"
|
||||
{% endif%}
|
||||
value='{{ history.detailed_msg }}
|
||||
{% if history.change_set != None %}
|
||||
<div class="content">
|
||||
<div id="change_index_definition"></div>
|
||||
{% call applied_change_macro.applied_change_template(history.change_set) %}
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endif %}
|
||||
'>Info <i class="fa fa-info"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
|
||||
|
||||
var table;
|
||||
$(document).ready(function () {
|
||||
|
||||
|
||||
table = $('#tbl_history').DataTable({
|
||||
"order": [
|
||||
[2, "desc"]
|
||||
],
|
||||
"searching": true,
|
||||
"columnDefs": [{
|
||||
"type": "time",
|
||||
"render": function (data, type, row) {
|
||||
return moment.utc(data).local().format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
"targets": 2
|
||||
}],
|
||||
"info": true,
|
||||
"autoWidth": false,
|
||||
orderCellsTop: true,
|
||||
fixedHeader: true
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$(document.body).on('click', '.history-info-button', function () {
|
||||
var modal = $("#modal_history_info");
|
||||
var info = $(this).val();
|
||||
$('#modal-info-content').html(info);
|
||||
modal.modal('show');
|
||||
});
|
||||
|
||||
|
||||
|
||||
$(document.body).on("click", ".button-filter", function (e) {
|
||||
e.stopPropagation();
|
||||
var nextRow = $("#filter-table")
|
||||
if (nextRow.css("visibility") == "visible")
|
||||
nextRow.css("visibility", "collapse")
|
||||
else
|
||||
nextRow.css("visibility", "visible")
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
@ -51,7 +51,7 @@
|
||||
<div class="nav-tabs-custom" id="tabs">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#tabs-general" data-toggle="tab">General</a></li>
|
||||
<li class="active"><a href="#tabs-ldap" data-toggle="tab">LDAP</a></li>
|
||||
<li><a href="#tabs-ldap" data-toggle="tab">LDAP</a></li>
|
||||
<li><a href="#tabs-google" data-toggle="tab">Google OAuth</a></li>
|
||||
<li><a href="#tabs-github" data-toggle="tab">Github OAuth</a></li>
|
||||
<li><a href="#tabs-azure" data-toggle="tab">Microsoft OAuth</a></li>
|
||||
@ -76,7 +76,7 @@
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane active" id="tabs-ldap">
|
||||
<div class="tab-pane" id="tabs-ldap">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{% if error %}
|
||||
@ -693,12 +693,12 @@
|
||||
<script>
|
||||
|
||||
$(function() {
|
||||
$('#tabs').tabs({
|
||||
// add url anchor tags
|
||||
activate: function(event, ui) {
|
||||
window.location.hash = ui.newPanel.attr('id');
|
||||
}
|
||||
});
|
||||
// $('#tabs').tabs({
|
||||
// // add url anchor tags
|
||||
// activate: function(event, ui) {
|
||||
// window.location.hash = ui.newPanel.attr('id');
|
||||
// }
|
||||
// });
|
||||
// re-set active tab (ui)
|
||||
var activeTabIdx = $('#tabs').tabs('option','active');
|
||||
$('#tabs li:eq('+activeTabIdx+')').tab('show')
|
||||
|
133
powerdnsadmin/templates/applied_change_macro.html
Normal file
@ -0,0 +1,133 @@
|
||||
{% macro applied_change_template(change_set) -%}
|
||||
{{ caller() }}
|
||||
{% for hist_rec_entry in change_set %}
|
||||
<table id="tbl_records" class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="3">
|
||||
{% if hist_rec_entry.change_type == "+" %}
|
||||
<span
|
||||
style="background-color: lightgreen">{{hist_rec_entry.add_rrest['name']}}
|
||||
{{hist_rec_entry.add_rrest['type']}}</span>
|
||||
{% elif hist_rec_entry.change_type == "-" %}
|
||||
<s
|
||||
style="text-decoration-color: rgba(194, 10,10, 0.6); text-decoration-thickness: 2px;">
|
||||
{{hist_rec_entry.del_rrest['name']}}
|
||||
{{hist_rec_entry.del_rrest['type']}}
|
||||
</s>
|
||||
{% else %}
|
||||
{{hist_rec_entry.add_rrest['name']}}
|
||||
{{hist_rec_entry.add_rrest['type']}}
|
||||
{% endif %}
|
||||
|
||||
, TTL:
|
||||
{% if "ttl" in hist_rec_entry.changed_fields %}
|
||||
<s
|
||||
style="text-decoration-color: rgba(194, 10,10, 0.6); text-decoration-thickness: 2px;">
|
||||
{{hist_rec_entry.del_rrest['ttl']}}</s>
|
||||
<span
|
||||
style="background-color: lightgreen">{{hist_rec_entry.add_rrest['ttl']}}</span>
|
||||
{% else %}
|
||||
{{hist_rec_entry.add_rrest['ttl']}}
|
||||
{% endif %}
|
||||
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
<th style="width: 150px;">Status</th>
|
||||
<th style="width: 400px;">Data</th>
|
||||
<th style="width: 400px;">Comment</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table>
|
||||
<tbody>
|
||||
{% for changes in hist_rec_entry.changeSet %}
|
||||
<tr>
|
||||
{% if changes[2] == "unchanged" %}
|
||||
<td>{{ "Activated" if changes[0]['disabled'] ==
|
||||
False else
|
||||
"Disabled"}} </td>
|
||||
{% elif changes[2] == "addition" %}
|
||||
<td>
|
||||
<span style="background-color: lightgreen">
|
||||
{{ "Activated" if changes[1]['disabled'] ==
|
||||
False else
|
||||
"Disabled"}}
|
||||
</span>
|
||||
</td>
|
||||
{% elif changes[2] == "status" %}
|
||||
<td>
|
||||
<s
|
||||
style="text-decoration-color: rgba(194, 10,10, 0.6); text-decoration-thickness: 2px;">
|
||||
{{ "Activated" if changes[0]['disabled'] ==
|
||||
False else
|
||||
"Disabled"}}</s>
|
||||
<span style="background-color: lightgreen">{{
|
||||
"Activated" if changes[1]['disabled'] ==
|
||||
False else
|
||||
"Disabled"}}</span>
|
||||
</td>
|
||||
{% elif changes[2] == "deletion" %}
|
||||
<td>
|
||||
<s
|
||||
style="text-decoration-color: rgba(194, 10,10, 0.6); text-decoration-thickness: 2px;">
|
||||
{{ "Activated" if changes[0]['disabled'] ==
|
||||
False else
|
||||
"Disabled"}}</s>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tbody>
|
||||
{% for changes in hist_rec_entry.changeSet %}
|
||||
<tr>
|
||||
{% if changes[2] == "unchanged" %}
|
||||
<td>
|
||||
{{changes[0]['content']}}
|
||||
</td>
|
||||
{% elif changes[2] == "addition" %}
|
||||
<td>
|
||||
<span style="background-color: lightgreen">
|
||||
{{changes[1]['content']}}
|
||||
</span>
|
||||
</td>
|
||||
{% elif changes[2] == "deletion" %}
|
||||
<td>
|
||||
<s
|
||||
style="text-decoration-color: rgba(194, 10, 10, 0.6); text-decoration-thickness: 2px;">
|
||||
{{changes[0]['content']}}
|
||||
</s>
|
||||
</td>
|
||||
{% elif changes[2] == "status" %}
|
||||
<td>
|
||||
{{changes[0]['content']}}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
{% for comments in hist_rec_entry.add_rrest['comments'] %}
|
||||
{{comments['content'] }}
|
||||
<br/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
{%- endmacro %}
|
@ -6,6 +6,16 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="icon" href="{{ url_for('static', filename='img/favicon.png') }}">
|
||||
{% block title %}<title>{{ SITE_NAME }}</title>{% endblock %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='assets/css/style.css') }}">
|
||||
<!-- Get jquery UI -->
|
||||
{% if OFFLINE_MODE %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='assets/jquery-ui-smooth-datepicker/jquery-ui.js') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='assets/jquery-ui-smooth-datepicker/jquery-ui.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='assets/jquery-ui-smooth-datepicker/jquery-ui.structure.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='assets/jquery-ui-smooth-datepicker/jquery-ui.theme.css') }}">
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css" type="text/css"/>
|
||||
{% endif %}
|
||||
<!-- Get Google Fonts we like -->
|
||||
{% if OFFLINE_MODE %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='assets/css/source_sans_pro.css') }}">
|
||||
|
@ -2,6 +2,7 @@
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}<title>Dashboard - {{ SITE_NAME }}</title>{% endblock %}
|
||||
|
||||
|
||||
{% block dashboard_stat %}
|
||||
<!-- Content Header (Page header) -->
|
||||
<section class="content-header">
|
||||
@ -16,6 +17,8 @@
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% import 'applied_change_macro.html' as applied_change_macro %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Main content -->
|
||||
<section class="content">
|
||||
@ -107,11 +110,23 @@
|
||||
<tbody>
|
||||
{% for history in histories %}
|
||||
<tr class="odd">
|
||||
<td>{{ history.created_by }}</td>
|
||||
<td>{{ history.msg }}</td>
|
||||
<td>{{ history.created_on }}</td>
|
||||
<td>{{ history.history.created_by }}</td>
|
||||
<td>{{ history.history.msg }}</td>
|
||||
<td>{{ history.history.created_on }}</td>
|
||||
<td width="6%">
|
||||
<button type="button" class="btn btn-flat btn-primary history-info-button" value='{{ history.detail }}'>
|
||||
<button type="button" class="btn btn-flat btn-primary history-info-button"
|
||||
{% if history.detailed_msg == "" and history.change_set == None %}
|
||||
style="visibility: hidden;"
|
||||
{% endif%}
|
||||
value='{{ history.detailed_msg }}
|
||||
{% if history.change_set != None %}
|
||||
<div class="content">
|
||||
<div id="change_index_definition"></div>
|
||||
{% call applied_change_macro.applied_change_template(history.change_set) %}
|
||||
{% endcall %}
|
||||
</div>
|
||||
{% endif %}
|
||||
'>
|
||||
Info <i class="fa fa-info"></i>
|
||||
</button>
|
||||
</td>
|
||||
@ -226,11 +241,10 @@
|
||||
]
|
||||
});
|
||||
|
||||
$(document.body).on('click', '.history-info-button', function()
|
||||
{
|
||||
$(document.body).on('click', '.history-info-button', function () {
|
||||
var modal = $("#modal_history_info");
|
||||
var info = $(this).val();
|
||||
$('#modal-code-content').html(json_library.prettyPrint(info));
|
||||
$('#modal-info-content').html(info);
|
||||
modal.modal('show');
|
||||
});
|
||||
|
||||
@ -297,7 +311,7 @@
|
||||
<h4 class="modal-title">History Details</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre><code id="modal-code-content"></code></pre>
|
||||
<div id="modal-info-content"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-flat btn-default pull-right"
|
||||
|
@ -40,12 +40,20 @@
|
||||
<button type="button" class="btn btn-flat btn-danger" onclick="window.location.href='{{ url_for('domain.setting', domain_name=domain.name) }}'">
|
||||
Admin <i class="fa fa-cog"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-flat btn-primary" onclick="window.location.href='{{ url_for('domain.changelog', domain_name=domain.name) }}'">
|
||||
Changelog <i class="fa fa-history" aria-hidden="true"></i>
|
||||
</button>
|
||||
</td>
|
||||
{% else %}
|
||||
<td width="6%">
|
||||
<button type="button" class="btn btn-flat btn-success" onclick="window.location.href='{{ url_for('domain.domain', domain_name=domain.name) }}'">
|
||||
Manage <i class="fa fa-cog"></i>
|
||||
</button>
|
||||
{% if allow_user_view_history %}
|
||||
<button type="button" class="btn btn-flat btn-primary" onclick="window.location.href='{{ url_for('domain.changelog', domain_name=domain.name) }}'">
|
||||
Changelog <i class="fa fa-history" aria-hidden="true"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
@ -33,6 +33,12 @@
|
||||
Update from Master <i class="fa fa-download"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if current_user.role.name in ['Administrator', 'Operator'] or SETTING.get('allow_user_view_history') %}
|
||||
<button type="button" style="position: relative; margin-left: 20px" class="btn btn-flat btn-primary button_changelog" id="{{ domain.name }}">
|
||||
Changelog <i class="fa fa-history" aria-hidden="true"></i>
|
||||
</i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<table id="tbl_records" class="table table-bordered table-striped">
|
||||
@ -46,6 +52,9 @@
|
||||
<th>Comment</th>
|
||||
<th>Edit</th>
|
||||
<th>Delete</th>
|
||||
{% if current_user.role.name in ['Administrator', 'Operator'] or SETTING.get('allow_user_view_history') %}
|
||||
<th >Changelog</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -91,6 +100,13 @@
|
||||
</td>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if current_user.role.name in ['Administrator', 'Operator'] or SETTING.get('allow_user_view_history') %}
|
||||
<td width="6%">
|
||||
<button type="button" onclick="show_record_changelog('{{record.name}}','{{record.type}}',event)" class="btn btn-flat btn-primary">
|
||||
<i class="fa fa-history" aria-hidden="true"></i>
|
||||
</button>
|
||||
</td>
|
||||
{% endif %}
|
||||
<!-- hidden column that we can sort on -->
|
||||
<td>1</td>
|
||||
</tr>
|
||||
@ -144,14 +160,33 @@
|
||||
// hidden column so that we can add new records on top
|
||||
// regardless of whatever sorting is done. See orderFixed
|
||||
visible: false,
|
||||
{% if current_user.role.name in ['Administrator', 'Operator'] or SETTING.get('allow_user_view_history') %}
|
||||
targets: [ 9 ]
|
||||
{% else %}
|
||||
targets: [ 8 ]
|
||||
{% endif %}
|
||||
},
|
||||
{
|
||||
className: "length-break",
|
||||
targets: [ 4, 5 ]
|
||||
}
|
||||
],
|
||||
{% if current_user.role.name in ['Administrator', 'Operator'] or SETTING.get('allow_user_view_history') %}
|
||||
"orderFixed": [[9, 'asc']]
|
||||
{% else %}
|
||||
"orderFixed": [[8, 'asc']]
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
|
||||
function show_record_changelog(record_name, record_type, e) {
|
||||
e.stopPropagation();
|
||||
window.location.href = "/domain/{{domain.name}}/changelog/" + record_name + ".-" + record_type;
|
||||
}
|
||||
// handle changelog button
|
||||
$(document.body).on("click", ".button_changelog", function(e) {
|
||||
e.stopPropagation();
|
||||
window.location.href = "/domain/{{domain.name}}/changelog";
|
||||
});
|
||||
|
||||
// handle delete button
|
||||
@ -243,7 +278,11 @@
|
||||
|
||||
// add new row
|
||||
var default_type = records_allow_edit[0]
|
||||
{% if current_user.role.name in ['Administrator', 'Operator'] or SETTING.get('allow_user_view_history') %}
|
||||
var nRow = jQuery('#tbl_records').dataTable().fnAddData(['', default_type, 'Active', window.ttl_options[0][0], '', '', '', '', '', '0']);
|
||||
{% else %}
|
||||
var nRow = jQuery('#tbl_records').dataTable().fnAddData(['', default_type, 'Active', window.ttl_options[0][0], '', '', '', '', '0']);
|
||||
{% endif %}
|
||||
editRow($("#tbl_records").DataTable(), nRow);
|
||||
document.getElementById("edit-row-focus").focus();
|
||||
nEditing = nRow;
|
||||
|
112
powerdnsadmin/templates/domain_changelog.html
Normal file
@ -0,0 +1,112 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}<title>{{ domain.name | pretty_domain_name }} - {{ SITE_NAME }}</title>{% endblock %}
|
||||
|
||||
{% block dashboard_stat %}
|
||||
<section class="content-header">
|
||||
<h1>
|
||||
Domain changelog: <b>{{ domain.name | pretty_domain_name }}</b>
|
||||
</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{{ url_for('dashboard.dashboard') }}"><i class="fa fa-dashboard"></i> Home</a></li>
|
||||
<li>Domain</li>
|
||||
<li class="active">{{ domain.name | pretty_domain_name }}</li>
|
||||
</ol>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% import 'applied_change_macro.html' as applied_change_macro %}
|
||||
|
||||
{% block content %}
|
||||
<section class="content">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="box">
|
||||
<div class="box-body">
|
||||
<button type="button" class="btn btn-flat btn-primary pull-left button_show_records"
|
||||
id="{{ domain.name }}">
|
||||
Manage <i class="fa fa-arrow-left"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
|
||||
<table id="tbl_changelog" class="table table-bordered table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Changed on</th>
|
||||
<th>Changed by</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for applied_change in allHistoryChanges %}
|
||||
|
||||
<tr class="odd row_record" id="{{ domain.name }}">
|
||||
<td id="changed_on" class="changed_on">
|
||||
{{ allHistoryChanges[applied_change][0].history_entry.created_on }}
|
||||
</td>
|
||||
<td>
|
||||
{{allHistoryChanges[applied_change][0].history_entry.created_by }}
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Nested Table -->
|
||||
<tr style='visibility:collapse'>
|
||||
<td colspan="2">
|
||||
<div class="content">
|
||||
{% call applied_change_macro.applied_change_template(allHistoryChanges[applied_change]) %}
|
||||
{% endcall %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- end nested table -->
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrascripts %}
|
||||
<script>
|
||||
// handle "show records" button
|
||||
$(document.body).on("click", ".button_show_records", function (e) {
|
||||
e.stopPropagation();
|
||||
window.location.href = "/domain/{{domain.name}}";
|
||||
});
|
||||
|
||||
var coll = document.getElementsByClassName("collapsible");
|
||||
var i;
|
||||
|
||||
for (i = 0; i < coll.length; i++) {
|
||||
coll[i].addEventListener("click", function () {
|
||||
this.classList.toggle("active");
|
||||
var content = this.nextElementSibling;
|
||||
if (content.style.maxHeight) {
|
||||
content.style.maxHeight = null;
|
||||
} else {
|
||||
content.style.maxHeight = content.scrollHeight + "px";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// handle click on history record
|
||||
$(document.body).on("click", ".row_record", function (e) {
|
||||
e.stopPropagation();
|
||||
var nextRow = $(this).next('tr')
|
||||
if (nextRow.css("visibility") == "visible")
|
||||
nextRow.css("visibility", "collapse")
|
||||
else
|
||||
nextRow.css("visibility", "visible")
|
||||
|
||||
});
|
||||
|
||||
|
||||
var els = document.getElementsByClassName("changed_on");
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
// els[i].innerHTML = moment.utc(els[i].innerHTML).local().format('YYYY-MM-DD HH:mm:ss');
|
||||
els[i].innerHTML = moment.utc(els[i].innerHTML,'YYYY-MM-DD HH:mm:ss').local().format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
@ -123,12 +123,12 @@
|
||||
<!-- TODO: add password and password confirmation comparison check -->
|
||||
<script>
|
||||
$(function () {
|
||||
$('#tabs').tabs({
|
||||
// add url anchor tags
|
||||
activate: function (event, ui) {
|
||||
window.location.hash = ui.newPanel.attr('id');
|
||||
}
|
||||
});
|
||||
// $('#tabs').tabs({
|
||||
// // add url anchor tags
|
||||
// activate: function (event, ui) {
|
||||
// window.location.hash = ui.newPanel.attr('id');
|
||||
// }
|
||||
// });
|
||||
// re-set active tab (ui)
|
||||
var activeTabIdx = $('#tabs').tabs('option', 'active');
|
||||
$('#tabs li:eq(' + activeTabIdx + ')').tab('show')
|
||||
|