Start pagination

SQL is finished

Signed-off-by: Etzelia <etzelia@hotmail.com>
develop
Etzelia 2019-08-15 17:00:54 -05:00
parent 0c0a9a0a0a
commit 5357e64591
No known key found for this signature in database
GPG Key ID: 3CAEB74806C4ADE5
7 changed files with 203 additions and 354 deletions

View File

@ -1,302 +0,0 @@
# Generated by Django 2.2.3 on 2019-07-27 03:17
import django.contrib.auth.models
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0011_update_proxy_permissions'),
]
operations = [
migrations.CreateModel(
name='CoArtMap',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('art', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'Art Map',
'verbose_name_plural': 'Art Maps',
'db_table': 'co_art_map',
'managed': False,
},
),
migrations.CreateModel(
name='CoBlock',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.IntegerField(blank=True, null=True)),
('user', models.IntegerField(blank=True, null=True)),
('wid', models.IntegerField(blank=True, null=True)),
('x', models.IntegerField(blank=True, null=True)),
('y', models.IntegerField(blank=True, null=True)),
('z', models.IntegerField(blank=True, null=True)),
('type', models.IntegerField(blank=True, null=True)),
('data', models.IntegerField(blank=True, null=True)),
('meta', models.BinaryField(blank=True, null=True)),
('blockdata', models.BinaryField(blank=True, null=True)),
('action', models.IntegerField(blank=True, null=True)),
('rolled_back', models.IntegerField(blank=True, null=True)),
],
options={
'verbose_name': 'Block',
'verbose_name_plural': 'Blocks',
'db_table': 'co_block',
'managed': False,
},
),
migrations.CreateModel(
name='CoBlockdataMap',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('data', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'BlockData Map',
'verbose_name_plural': 'BlockData Maps',
'db_table': 'co_blockdata_map',
'managed': False,
},
),
migrations.CreateModel(
name='CoChat',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.IntegerField(blank=True, null=True)),
('user', models.IntegerField(blank=True, null=True)),
('message', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'Chat Message',
'verbose_name_plural': 'Chat Messages',
'db_table': 'co_chat',
'managed': False,
},
),
migrations.CreateModel(
name='CoCommand',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.IntegerField(blank=True, null=True)),
('user', models.IntegerField(blank=True, null=True)),
('message', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'Command',
'verbose_name_plural': 'Commands',
'db_table': 'co_command',
'managed': False,
},
),
migrations.CreateModel(
name='CoContainer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.IntegerField(blank=True, null=True)),
('user', models.IntegerField(blank=True, null=True)),
('wid', models.IntegerField(blank=True, null=True)),
('x', models.IntegerField(blank=True, null=True)),
('y', models.IntegerField(blank=True, null=True)),
('z', models.IntegerField(blank=True, null=True)),
('type', models.IntegerField(blank=True, null=True)),
('data', models.IntegerField(blank=True, null=True)),
('amount', models.IntegerField(blank=True, null=True)),
('metadata', models.BinaryField(blank=True, null=True)),
('action', models.IntegerField(blank=True, null=True)),
('rolled_back', models.IntegerField(blank=True, null=True)),
],
options={
'verbose_name': 'Container Transaction',
'verbose_name_plural': 'Container Transactions',
'db_table': 'co_container',
'managed': False,
},
),
migrations.CreateModel(
name='CoDatabaseLock',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.IntegerField(blank=True, null=True)),
('time', models.IntegerField(blank=True, null=True)),
],
options={
'verbose_name': 'Database Lock',
'verbose_name_plural': 'Database Locks',
'db_table': 'co_database_lock',
'managed': False,
},
),
migrations.CreateModel(
name='CoEntity',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.IntegerField(blank=True, null=True)),
('data', models.BinaryField(blank=True, null=True)),
],
options={
'verbose_name': 'Entity',
'verbose_name_plural': 'Entities',
'db_table': 'co_entity',
'managed': False,
},
),
migrations.CreateModel(
name='CoEntityMap',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('entity', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'Entity Mapping',
'verbose_name_plural': 'Entity Mappings',
'db_table': 'co_entity_map',
'managed': False,
},
),
migrations.CreateModel(
name='CoMaterialMap',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('material', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'Material Mapping',
'verbose_name_plural': 'Material Mappings',
'db_table': 'co_material_map',
'managed': False,
},
),
migrations.CreateModel(
name='CoSession',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.IntegerField(blank=True, null=True)),
('user', models.IntegerField(blank=True, null=True)),
('wid', models.IntegerField(blank=True, null=True)),
('x', models.IntegerField(blank=True, null=True)),
('y', models.IntegerField(blank=True, null=True)),
('z', models.IntegerField(blank=True, null=True)),
('action', models.IntegerField(blank=True, null=True)),
],
options={
'verbose_name': 'Session',
'verbose_name_plural': 'Sessions',
'db_table': 'co_session',
'managed': False,
},
),
migrations.CreateModel(
name='CoSign',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.IntegerField(blank=True, null=True)),
('user', models.IntegerField(blank=True, null=True)),
('wid', models.IntegerField(blank=True, null=True)),
('x', models.IntegerField(blank=True, null=True)),
('y', models.IntegerField(blank=True, null=True)),
('z', models.IntegerField(blank=True, null=True)),
('color', models.IntegerField(blank=True, null=True)),
('line_1', models.TextField(blank=True, null=True)),
('line_2', models.TextField(blank=True, null=True)),
('line_3', models.TextField(blank=True, null=True)),
('line_4', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'Sign',
'verbose_name_plural': 'Signs',
'db_table': 'co_sign',
'managed': False,
},
),
migrations.CreateModel(
name='CoSkull',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.IntegerField(blank=True, null=True)),
('owner', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'Skull',
'verbose_name_plural': 'Skulls',
'db_table': 'co_skull',
'managed': False,
},
),
migrations.CreateModel(
name='CoUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.IntegerField(blank=True, null=True)),
('user', models.TextField(blank=True, null=True)),
('uuid', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'User',
'verbose_name_plural': 'Users',
'db_table': 'co_user',
'managed': False,
},
),
migrations.CreateModel(
name='CoUsernameLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.IntegerField(blank=True, null=True)),
('uuid', models.TextField(blank=True, null=True)),
('user', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'Username',
'verbose_name_plural': 'Usernames',
'db_table': 'co_username_log',
'managed': False,
},
),
migrations.CreateModel(
name='CoVersion',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('time', models.IntegerField(blank=True, null=True)),
('version', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'Version',
'verbose_name_plural': 'Versions',
'db_table': 'co_version',
'managed': False,
},
),
migrations.CreateModel(
name='CoWorld',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('world', models.TextField(blank=True, null=True)),
],
options={
'verbose_name': 'World',
'verbose_name_plural': 'Worlds',
'db_table': 'co_world',
'managed': False,
},
),
migrations.CreateModel(
name='CoreProtectUser',
fields=[
],
options={
'permissions': (('coreprotect_base', 'Can use CoreProtect GUI'), ('coreprotect_extra', 'Can search Chat/Commands'), ('coreprotect_activity', 'Can use CoreProtect Activity Monitor')),
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('auth.user',),
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

View File

@ -312,19 +312,31 @@ class CoResult(models.Model):
return "Block Break" if self.action == 0 else "Block Place" if self.action == 1 else "Interact/Used"
if self.type == "chat":
return "Chat"
if self.type == "command":
return "Command"
if self.type == "container":
return "Took from Container" if self.action == 0 else "Placed in Container"
if self.type == "command":
return "Command"
if self.type == "session":
return "Logout" if self.action == 0 else "Login"
if self.type == "sign":
return "Sign"
return self.action
@property
def display_data(self):
def material_name(namespace):
m = namespace.replace("minecraft:", "")
pp = m.split("_")
return " ".join([p.capitalize() for p in pp])
if self.type == "block":
material = self.data.replace("minecraft:", "")
parts = material.split("_")
return " ".join([part.capitalize() for part in parts])
return material_name(self.data)
if self.type == "container":
parts = self.data.split()
return "{} {}".format(parts[0], material_name(parts[1]))
return self.data
class Meta:
managed = False
verbose_name = "Result"
verbose_name_plural = "Results"

View File

@ -0,0 +1,3 @@
.click-row {
cursor: pointer;
}

View File

@ -28,4 +28,51 @@ function formURI(formData) {
function elementHTML(id, html) {
document.getElementById(id).innerHTML = html
}
function downloadCSV(csv, filename) {
let csvFile;
let downloadLink;
// CSV file
csvFile = new Blob([csv], {type: "text/csv"});
// Download link
downloadLink = document.createElement("a");
// File name
downloadLink.download = filename;
// Create a link to the file
downloadLink.href = window.URL.createObjectURL(csvFile);
// Hide download link
downloadLink.style.display = "none";
// Add the link to DOM
document.body.appendChild(downloadLink);
// Click download link
downloadLink.click();
}
function exportTableToCSV(filename, elementId) {
let csv = [];
let selector = "table tr";
if (elementId !== "") {
selector = "#" + elementId + " tr"
}
let rows = document.querySelectorAll(selector);
for (let i = 0; i < rows.length; i++) {
let row = [], cols = rows[i].querySelectorAll("td, th");
for (let j = 0; j < cols.length; j++)
row.push('"' + cols[j].innerText + '"');
csv.push(row.join(","));
}
// Download CSV file
downloadCSV(csv.join("\n"), filename);
}

View File

@ -9,6 +9,7 @@
<link rel="stylesheet" type="text/css" href="{% static 'coreprotect/css/bulma-extensions.min.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'coreprotect/css/flatpickr.min.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'coreprotect/css/flatpickr.dark.css' %}"/>
<link rel="stylesheet" type="text/css" href="{% static 'coreprotect/css/main.css' %}"/>
<script src="{% static 'coreprotect/js/flatpickr.js' %}"></script>
@ -86,24 +87,23 @@
<br/>
<h4 class="title is-5 has-text-primary">
Limit Results
Page Size
</h4>
<div class="field">
<p class="control">
<span class="select">
<select id="limit_results" name="limit_results">
<option value="200" {% if form.limit_results == "200" %}selected{% endif %}>200</option>
<option value="500" {% if form.limit_results == "500" %}selected{% endif %}>500</option>
<option value="1000" {% if form.limit_results == "1000" %}selected{% endif %}>1000</option>
<option value="2000" {% if form.limit_results == "2000" %}selected{% endif %}>2000</option>
<option value="4000" {% if form.limit_results == "4000" %}selected{% endif %}>4000</option>
<option value="10000" {% if form.limit_results == "10000" %}selected{% endif %}>10000</option>
<option value="25000" {% if form.limit_results == "25000" %}selected{% endif %}>25000</option>
<option value="-1" {% if form.limit_results == "-1" %}selected{% endif %}>All</option>
<select id="page_size" name="page_size">
<option value="20" {% if form.page_size == "20" %}selected{% endif %}>20</option>
<option value="50" {% if form.page_size == "50" %}selected{% endif %}>50</option>
<option value="100" {% if form.page_size == "100" %}selected{% endif %}>100</option>
<option value="200" {% if form.page_size == "200" %}selected{% endif %}>200</option>
<option value="500" {% if form.page_size == "500" %}selected{% endif %}>500</option>
<option value="1000" {% if form.page_size == "1000" %}selected{% endif %}>1000</option>
</select>
</span>
</p>
</div>
<input type="hidden" id="page" name="page" value="{{form.page}}"/>
</div>
@ -171,6 +171,9 @@
<p class="control">
<button class="button is-dark" id="clear" type="button">Clear</button>
</p>
<p class="control">
<button class="button is-info" id="export" type="button">Export</button>
</p>
</div>
</div>
</div>
@ -183,6 +186,35 @@
<script src="{% static 'coreprotect/js/bulma-extensions.min.js' %}"></script>
<script src="{% static 'coreprotect/js/main.js' %}"></script>
<script>
let text = "Copy the below command to teleport";
function attachClickRows() {
Array.from(document.querySelectorAll(".click-row")).forEach(elem => {
elem.addEventListener("click", function() {
prompt(text, elem.getAttribute("data-prompt"));
});
});
}
let url = new URL(window.location);
let pageParam = url.searchParams.get("page");
let page = 1;
if (pageParam !== "") {
page = parseInt(pageParam);
if (isNaN(page)) {
page = 1;
}
}
function attachPagination() {
document.getElementById("prev").addEventListener("click", function() {
document.getElementById("page").value = (page-1).toString();
document.getElementById("search").click();
});
document.getElementById("next").addEventListener("click", function() {
document.getElementById("page").value = (page+1).toString();
document.getElementById("search").click();
});
}
document.getElementById("search").addEventListener("click", function() {
elementHTML("results", 'Querying Database...<br/><progress class="progress is-primary"></progress>');
@ -190,7 +222,10 @@
let formData = new FormData(form);
ajax("GET", "{% url "coreprotect_query" %}?" + formURI(formData), function(status, data) {
elementHTML("results", data);
attachClickRows();
attachPagination();
});
window.history.pushState("", "", "{% url "coreprotect_index" %}?" + formURI(formData));
});
document.getElementById("clear").addEventListener("click", function() {
@ -212,8 +247,9 @@
// Options
document.getElementById("ignore_environment").checked = false;
// Limit Results
document.getElementById("limit_results").selectedIndex = "0";
// Page Size
document.getElementById("page_size").selectedIndex = "0";
document.getElementById("page").value = "1";
// Players
document.getElementById("players").value = "";
@ -231,5 +267,9 @@
document.getElementById("date_from")._flatpickr.setDate();
document.getElementById("date_to")._flatpickr.setDate();
});
document.getElementById("export").addEventListener("click", function() {
exportTableToCSV("coreprotect.csv", "results");
});
</script>
</html>

View File

@ -1,4 +1,4 @@
<table class="table is-fullwidth is-striped is-bordered">
<table class="table is-fullwidth is-striped is-bordered is-hoverable">
<thead>
<tr>
<th>Time</th>
@ -13,7 +13,7 @@
</thead>
<tbody>
{% for result in results %}
<tr>
<tr {% if result.x %}class="click-row" data-prompt="/tp {{ result.x }} {{ result.y }} {{ result.z }}"{% endif %}>
<td>{{ result.display_time }}</td>
<td>{{ result.player }}</td>
<td>{{ result.display_action }}</td>
@ -25,4 +25,9 @@
</tr>
{% endfor %}
</tbody>
</table>
</table>
<nav class="pagination is-rounded is-right" role="navigation">
<a class="pagination-previous" id="prev" {% if not prev %}disabled{% endif %}>Previous</a>
<a class="pagination-next" id="next" {% if not next %}disabled{% endif %}>Next</a>
<ul class="pagination-list"></ul>
</nav>

108
views.py
View File

@ -23,7 +23,13 @@ class Query(View):
if "format" in request.GET and request.GET["format"] == "json":
return JsonResponse(results)
return render(request, "coreprotect/table.html", {"results": results})
prev_page, next_page = False, False
if form.page != "1":
prev_page = True
if int(form.page) * int(form.page_size) < 1000:
next_page = True
return render(request, "coreprotect/table.html", {"results": results, "num": len(results), "prev": prev_page, "next": next_page})
def post(self, request):
pass
@ -42,8 +48,9 @@ class Form:
self.sign_place = False
self.worlds = []
self.ignore_environment = False
self.page = ""
self.page_size = ""
self.start = ""
self.limit_results = ""
self.players = ""
self.x = ""
self.y = ""
@ -84,8 +91,13 @@ def form_data(request_data):
form.ignore_environment = checkbox(request_data["ignore_environment"]) if "ignore_environment" in request_data else False
# Limit Results
form.start = request_data["start"] if "start" in request_data else "0"
form.limit_results = request_data["limit_results"] if "limit_results" in request_data else "200"
form.page = request_data["page"] if "page" in request_data else "1"
form.page_size = request_data["page_size"] if "page_size" in request_data else "20"
if int(form.page_size) > 1000:
form.page_size = "1000"
start = (int(form.page) * int(form.page_size))
form.start = str(start) if start <= 1000 - int(form.page_size) else str(1000 - int(form.page_size))
# Players
form.players = request_data["players"] if "players" in request_data else ""
@ -102,10 +114,7 @@ def form_data(request_data):
# Date and Time
form.date_from = request_data["date_from"] if "date_from" in request_data else ""
form.date_to = request_data["date_to"] if "date_to" in request_data else ""
if form.date_from and not form.date_to:
form.date_to = datetime.now().timestamp()
if form.date_to and not form.date_from:
form.date_from = datetime.now().timestamp()
return form
@ -141,8 +150,15 @@ def result_data(form):
worlds_clause = " AND cw.id IN ({})".format(",".join(worlds))
time_clause = ""
if form.date_from and form.date_to:
time_clause = " AND unix BETWEEN {} AND {} ".format(form.date_from, form.date_to)
if form.date_from or form.date_to:
df, dt = None, None
if form.date_from and not form.date_to:
df = form.date_from
dt = datetime.now().timestamp()
if form.date_to and not form.date_from:
df = datetime.now().timestamp()
dt = form.date_to
time_clause = " AND unix BETWEEN {} AND {} ".format(df, dt)
# Block Break, Block Place, and Interact
block_actions = []
@ -161,30 +177,19 @@ def result_data(form):
JOIN co_world cw ON cb.wid = cw.id
WHERE cb.action IN ({action})
{ignore_environment}
{coords}
{players}
{players}
{coords}
{worlds}
{time}
'''.format(action=",".join(block_actions), ignore_environment=ignore_environment, coords=coords_clause,
players=players_clause, worlds=worlds_clause, time=time_clause))
'''.format(action=",".join(block_actions), ignore_environment=ignore_environment, players=players_clause,
coords=coords_clause, worlds=worlds_clause, time=time_clause))
# Chat
if form.chat:
queries.append('''SELECT
0 AS id, "chat" AS type, cc.time AS unix, cu.user AS player, "" AS action, cc.message AS data, "" AS x, "" AS y, "" AS z, "" AS world
FROM co_chat cc
JOIN co_user cu ON cch.user = cu.id
WHERE 1 = 1
{players}
{time}
'''.format(players=players_clause, time=time_clause))
# Commands
if form.command:
queries.append('''SELECT
0 AS id, "command" AS type, cc.time AS unix, cu.user AS player, "" AS action, cc.message AS data, "" AS x, "" AS y, "" AS z, "" AS world
FROM co_chat cc
JOIN co_user cu ON cco.user = cu.id
JOIN co_user cu ON cc.user = cu.id
WHERE 1 = 1
{players}
{time}
@ -199,16 +204,55 @@ def result_data(form):
JOIN co_material_map cmm ON cc.type = cmm.id
JOIN co_world cw ON cc.wid = cw.id
WHERE 1 = 1
{coords}
{players}
{worlds}
{players}
{coords}
{time}
'''.format(coords=coords_clause,
players=players_clause, worlds=worlds_clause, time=time_clause))
'''.format(worlds=worlds_clause, players=players_clause, coords=coords_clause, time=time_clause))
# Commands
if form.command:
queries.append('''SELECT
0 AS id, "command" AS type, cc.time AS unix, cu.user AS player, "" AS action, cc.message AS data, "" AS x, "" AS y, "" AS z, "" AS world
FROM co_command cc
JOIN co_user cu ON cc.user = cu.id
WHERE 1 = 1
{players}
{coords}
{time}
'''.format(players=players_clause, coords=coords_clause, time=time_clause))
# Login/Logout
if form.login_logout:
queries.append('''SELECT
0 AS id, "session" AS type, cs.time AS unix, cu.user AS player, cs.action, "" AS data, cs.x, cs.y, cs.z, cw.world
FROM co_session cs
JOIN co_user cu ON cs.user = cu.id
JOIN co_world cw ON cs.wid = cw.id
WHERE 1 = 1
{worlds}
{players}
{coords}
{time}
'''.format(worlds=worlds_clause, players=players_clause, coords=coords_clause, time=time_clause))
# Sign Place
if form.sign_place:
queries.append('''SELECT
0 AS id, "sign" AS type, cs.time AS unix, cu.user AS player, "" AS action, cs.line_1 || "|" || cs.line_2 || "|" || cs.line_3 || "|" || cs.line_4 AS data, cs.x, cs.y, cs.z, cw.world
FROM co_sign cs
JOIN co_user cu ON cs.user = cu.id
JOIN co_world cw ON cs.wid = cw.id
WHERE 1 = 1
{worlds}
{players}
{coords}
{time}
'''.format(worlds=worlds_clause, players=players_clause, coords=coords_clause, time=time_clause))
query = " UNION ".join(queries)
if query and form.limit_results != "-1":
query += " LIMIT {}, {}".format(form.start, form.limit_results)
if query:
query += " ORDER BY unix LIMIT {}, {}".format(form.start, form.page_size)
print(query)
if query:
return CoResult.objects.raw(query)