diff --git a/app/routes/users.py b/app/routes/users.py index bdf20333..d024063a 100644 --- a/app/routes/users.py +++ b/app/routes/users.py @@ -17,8 +17,6 @@ def my_wrapper(*args, **kwargs): abort(403) return my_wrapper - - @users.route("/data") @admin_required def users_data(): @@ -37,7 +35,22 @@ def users_data(): if f_name := filters.get("name", None): filter_query["name"] = {"$regex": f_name} - + if f_formats := filters.get("all_formats", None): + filter_query["formats"] = {"$regex": f_formats} + + if f_criteria := filters.get("all_criteria", None): + filter_query["criteria"] = {"$regex": f_criteria} + + if f_check_counts := filters.get("check_counts", None): + try: + f_check_counts_value, f_check_counts_cond = int(f_check_counts.split()[1]), f_check_counts.split()[0] + if f_check_counts_cond == '>': + filter_query["$expr"] = {"$gte": [{"$size": "$presentations"}, f_check_counts_value]} + elif f_check_counts_cond == "<": + filter_query["$expr"] = {"$lte": [{"$size": "$presentations"}, f_check_counts_value]} + except ValueError: + pass + limit = request.args.get("limit", "") limit = int(limit) if limit.isnumeric() else 10 @@ -57,6 +70,10 @@ def users_data(): "rows": [{ "username": item["username"], "name": item["name"], + "all_formats": item["formats"], + "all_criteria": item["criteria"], + "check_counts": len(item["presentations"]), + } for item in rows] } return jsonify(response) @@ -65,12 +82,10 @@ def users_data(): @users.route('/', methods=["GET"]) @admin_required def index(): - users = list(get_all_users()) - usernames = [(user['name'], user['username']) for user in users] - return render_template('user_list.html', usernames=usernames) + return render_template('user_list.html') @users.route('/', methods=["GET"]) @admin_required def user_info(username): user_info = get_user(username) - return render_template('one_user_info.html', user_info=user_info, check_counts = len(user_info.presentations)) + return render_template('one_user_info.html', user_info=user_info) diff --git a/app/templates/one_user_info.html b/app/templates/one_user_info.html index 23c52ce1..fae90366 100644 --- a/app/templates/one_user_info.html +++ b/app/templates/one_user_info.html @@ -1,50 +1,65 @@ {# Accepts: header dependicies, results, id, filename #} + {% extends "root.html" %} -{% block title %}Информация о пользователе{% endblock %} +{% block title %}Информация о пользователях{% endblock %} {% block main %} +
{% include "header.html" %}
-
-

- Информация о пользователе {{ user_info.username }}: +
+
+

+ Страница пользователя: {{ user_info.username }}

- + Список всех загрузок пользователя +
- - - - - - + + + + + - - - - - - - - - - - - - -
UsernameNameFormatscriteriacount of checkUsernameNameFormatsCriteriaCount of checks
- - {{ user_info.username }}{{ user_info.name }}{{ user_info.criteria }}{{ user_info.formats }}{{ check_counts}}
- -
+
-{% endblock %} +{% endblock main %} diff --git a/app/templates/user_list.html b/app/templates/user_list.html index aa5a82f2..d6c0baf1 100644 --- a/app/templates/user_list.html +++ b/app/templates/user_list.html @@ -51,6 +51,9 @@

Username Name + Formats + Criteria + Count of checks diff --git a/assets/scripts/main.js b/assets/scripts/main.js index 2ae1ce2e..18bd5f35 100644 --- a/assets/scripts/main.js +++ b/assets/scripts/main.js @@ -29,6 +29,7 @@ import './check_list'; import './logs'; import './admin_criterions'; import './user_list'; +import './one_user_info' import '../favicon.ico'; import '../styles/404.css'; diff --git a/assets/scripts/one_user_info.js b/assets/scripts/one_user_info.js new file mode 100644 index 00000000..c8c46e39 --- /dev/null +++ b/assets/scripts/one_user_info.js @@ -0,0 +1,167 @@ +import { debounce, isFloat, resetTable, ajaxRequest, onPopState } from "./utils" + +let $table; +const AJAX_URL = "/users/data" +let debounceInterval = 500; + + +String.prototype.insert = function (index, string) { + if (index > 0) { + return this.substring(0, index) + string + this.substr(index) + } + return string + this +} + + +$(() => { + initTable() + window.onpopstate = onPopState + + const $dataFilter = $(".bootstrap-table-filter-control-result") + $dataFilter.on("keypress", (e) => { + const val = $dataFilter.val() + const numbers = val.split("-") + + if (e.key === ".") { + const carret = $dataFilter[0].selectionStart + let expectedStr + if (carret <= numbers[0].length) { + expectedStr = numbers[0].insert(carret, ".") + } else { + expectedStr = numbers[1].insert(carret - numbers[0].length - 1, ".") + } + + if (isFloat(expectedStr)) { + return + } + } + + if (e.key >= "0" && e.key <= "9") { + return + } + + if (e.key === "-") { + if (numbers.length === 1) { + return + } + } + + e.preventDefault() + }) +}) + + +function initTable() { + $table = $("#one-user-table"); + + // get query string + const queryString = window.location.search; + + // parse query search to js object + const params = Object.fromEntries(new URLSearchParams(queryString).entries()) + + // check correct order query + if (params.order !== "asc" && params.order !== "desc" && params.order !== "") { + params.order = "" + } + + // check correct sort query + if (params.sort !== "") { + let match = false + $table.find("th[data-sortable='true']").each(function () { + if ($(this).data("field") === params.sort) { + match = true + return false + } + }) + + if (match === false) { + params.sort = "" + } + } + + + // check pair of sort and order + if ([params.sort, params.order].includes("")) { + params.sort = "" + params.order = "" + } + + // Fill filters + $table.on("created-controls.bs.table", function () { + if (params.filter) { + params.filter = JSON.parse(decodeURI(params.filter)) + for (const [key, value] of Object.entries(params.filter)) { + const $input = $(`.bootstrap-table-filter-control-${key}`) + $input.val(value) + } + } + }) + + // activate bs table + $table.bootstrapTable({ + pageNumber: parseInt(params.page) || 1, + pageSize: parseInt(params.size) || 10, + sortName: params.sort, + sortOrder: params.order, + buttons: buttons, + detailView: true, + detailViewIcon: false, + detailViewByClick: true, + detailFormatter: detailFormatter, + + queryParams: queryParams, + ajax: debouncedAjaxRequest, + }) +} + +// debounced ajax calls. +const debouncedAjaxRequest = debounce(function(params) {ajaxRequest(AJAX_URL, params)}, debounceInterval); + + +function queryParams(params) { + let filters = {} + $('.filter-control').each(function () { + const name = $(this).parents("th").data("field") + const val = this.querySelector("input").value + if (val) { + filters[name] = val + } + }) + + const query = { + limit: params.limit, + offset: params.offset, + sort: params.sort, + order: params.order, + } + + if (!$.isEmptyObject(filters)) { + query.filter = JSON.stringify(filters) + } + + return query +} + + +function buttons() { + let buttonsObj = {} + + buttonsObj["ResetTable"] = { + text: 'Reset', + event: function() { resetTable($table, queryParams) } + } + + return buttonsObj +} + + +function detailFormatter(index, row) { + var html = [] + $.each(row, function (key, value) { + if (key === 'message' || key === 'pathname') { + html.push('

' + key + ': ' + row[key] + '

') + } + }) + return html.join('') +}