621 lines
25 KiB
Plaintext
621 lines
25 KiB
Plaintext
<%- await include('parts/header.ejs', locals) %>
|
|
|
|
<style>
|
|
.header-elements {
|
|
margin-top: auto;
|
|
margin-bottom: auto;
|
|
}
|
|
.name-truncate {
|
|
max-width: 8rem;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.discord-avatar {
|
|
width: 1.25rem;
|
|
height: 1.25rem;
|
|
vertical-align: text-bottom;
|
|
}
|
|
.discord-tag {
|
|
margin-left: 2px;
|
|
}
|
|
.action-buttons {
|
|
text-decoration: none;
|
|
font-size: 1.25em;
|
|
margin-left: 4px;
|
|
}
|
|
.action-buttons:hover {
|
|
text-decoration: none;
|
|
}
|
|
.action-buttons[disabled] {
|
|
cursor: not-allowed;
|
|
/* pointer-events: none; */
|
|
}
|
|
.action-buttons:hover:not([disabled]) > i {
|
|
font-weight: 600;
|
|
}
|
|
</style>
|
|
|
|
<% if (currentWhitelistMode !== 'approvedLicense') { %>
|
|
<div class="row justify-content-md-center">
|
|
<div class="col-md-6">
|
|
<div class="alert alert-warning text-center" role="alert">
|
|
<strong>Warning: The server is not in the "Approved License" whitelist mode.</strong> <br>
|
|
This means that any changes made in this page will not reflect in the ability of players to connect to the server while the whitelist is in another mode or disabled. <br>
|
|
This can be changed in the page <a href="#" onclick="navigateParentTo('/settings')">Settings > Player Manager</a>.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% } %>
|
|
|
|
<div class="row justify-content-md-center">
|
|
<!-- Requests card -->
|
|
<div class="col-md-6 mw-col8">
|
|
<div class="card card-accent-success">
|
|
<div class="card-header" style="display: flex; justify-content: space-between;">
|
|
<div>
|
|
<span style="font-size: large">Whitelist Requests: <span id="reqs-counter"></span></span> <br>
|
|
<small>Players that tried to join the server but were not whitelisted.</small>
|
|
</div>
|
|
<div class="header-elements">
|
|
<div class="form-group mb-0">
|
|
<div class="input-group input-group-sm">
|
|
<input class="form-control" id="reqs-searchInput" type="text"
|
|
placeholder="player name, R1234">
|
|
<span class="input-group-append">
|
|
<button class="btn btn-sm btn-secondary" type="button" id="reqs-searchButton">
|
|
Search
|
|
</button>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="reqs-spinner" style="min-height: 125px;">
|
|
<div class="txSpinner">Loading...</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<h3 class="mx-auto pt-3 text-muted d-none" id="reqs-message">no players here yet</h3>
|
|
</div>
|
|
<div class="table-responsive text-center d-nonex" id="reqs-content">
|
|
<table class="table table-responsive-sm table-borderless table-striped table-sm text-left">
|
|
<thead>
|
|
<tr class="text-left">
|
|
<th>ID</th>
|
|
<th>Name</th>
|
|
<th>Discord</th>
|
|
<th>Join Time</th>
|
|
<th class="text-right">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="reqs-tableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-footer d-flex" style="flex-direction: row; justify-content: space-between;">
|
|
<button class="btn btn-sm btn-outline-danger" type="button" id="reqs-denyAllBtton" onclick="denyAllRequests()">
|
|
<i class="icon-close"></i> Deny All
|
|
</button>
|
|
<ul class="pagination justify-content-center mb-0">
|
|
<li class="page-item disabled" id="reqs-paginator-prev">
|
|
<a class="page-link" href="#" onclick="event.preventDefault(); reqsPrevPage()">Previous</a>
|
|
</li>
|
|
<li class="page-item disabled" id="reqs-paginator-mid">
|
|
<a class="page-link" href="#">?</a>
|
|
</li>
|
|
<li class="page-item disabled" id="reqs-paginator-next">
|
|
<a class="page-link" href="#" onclick="event.preventDefault(); reqsNextPage()">Next</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Approved card -->
|
|
<div class="col-md-6 mw-col8">
|
|
<div class="card card-accent-success">
|
|
<div class="card-header" style="display: flex; justify-content: space-between;">
|
|
<div>
|
|
<span style="font-size: large">Approved Whitelists Pending Join: <span id="approved-counter"></span></span> <br>
|
|
<small>Players that are already approved, but haven't joined the server yet.</small>
|
|
</div>
|
|
<div class="header-elements">
|
|
<button class="btn btn-sm btn-success" type="button" onclick="addApproval()">
|
|
<i class="icon-plus"></i> Add Approval
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="approved-spinner" style="min-height: 125px;">
|
|
<div class="txSpinner">Loading...</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<h3 class="mx-auto pt-3 text-muted d-none" id="approved-message">no players here yet</h3>
|
|
</div>
|
|
<div class="table-responsive text-center d-nonex" id="approved-content">
|
|
<table class="table table-responsive-sm table-borderless table-striped table-sm text-left">
|
|
<thead>
|
|
<tr class="text-left">
|
|
<!-- <th>Identifier</th> -->
|
|
<th>Player</th>
|
|
<th>Approved By</th>
|
|
<th>Approval Date</th>
|
|
<th class="text-right">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="approved-tableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Template for Whitelists Requests entry lines -->
|
|
<template id="reqs-entryTemplate">
|
|
<td></td>
|
|
<td class="name-truncate"></td>
|
|
<td class="name-truncate">
|
|
<div class="c-avatar discord-avatar">
|
|
<img class="c-avatar-img" src="">
|
|
</div>
|
|
<span class="discord-tag"></span>
|
|
</td>
|
|
<td></td>
|
|
<td class="tableActions">
|
|
<a href="#" class="action-buttons text-success" title="Approve">
|
|
<i class="icon-check"></i>
|
|
</a>
|
|
<a href="#" class="action-buttons text-danger" title="Deny">
|
|
<i class="icon-close"></i>
|
|
</a>
|
|
</td>
|
|
</template>
|
|
|
|
<!-- Template for Approved Whitelists entry lines -->
|
|
<template id="approved-entryTemplate">
|
|
<td class="name-truncate">
|
|
<div class="c-avatar discord-avatar">
|
|
<img class="c-avatar-img" src="">
|
|
</div>
|
|
<span class="discord-tag"></span>
|
|
</td>
|
|
<td></td>
|
|
<td></td>
|
|
<td class="tableActions">
|
|
<a href="#" class="action-buttons text-danger" title="Deny">
|
|
<i class="icon-close"></i>
|
|
</a>
|
|
</td>
|
|
</template>
|
|
|
|
|
|
<%- await include('parts/footer.ejs', locals) %>
|
|
|
|
|
|
<script>
|
|
const hasWhitelistPermission = ('<%= hasWhitelistPermission %>' === 'true');
|
|
const disableActionButton = (e) => {
|
|
e.title = 'You do not have this permission.';
|
|
e.setAttribute('disabled', '');
|
|
e.classList.remove('text-success', 'text-danger');
|
|
e.classList.add('text-muted');
|
|
e.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* WHITELIST REQUESTS
|
|
*/
|
|
const reqElements = {
|
|
spinner: document.getElementById('reqs-spinner'),
|
|
message: document.getElementById('reqs-message'),
|
|
content: document.getElementById('reqs-content'),
|
|
counter: document.getElementById('reqs-counter'),
|
|
tableBody: document.getElementById('reqs-tableBody'),
|
|
entryTemplate: document.getElementById('reqs-entryTemplate'),
|
|
searchInput: document.getElementById('reqs-searchInput'),
|
|
searchButton: document.getElementById('reqs-searchButton'),
|
|
denyAllBtton: document.getElementById('reqs-denyAllBtton'),
|
|
};
|
|
const paginator = {
|
|
prev: document.getElementById('reqs-paginator-prev'),
|
|
mid: document.getElementById('reqs-paginator-mid'),
|
|
next: document.getElementById('reqs-paginator-next'),
|
|
}
|
|
let currentPage = 1;
|
|
let activeSearchString = '';
|
|
let newestRequest = 0;
|
|
|
|
//If no perms, edit template
|
|
if(!hasWhitelistPermission){
|
|
reqElements.entryTemplate.content.querySelectorAll('.action-buttons').forEach(disableActionButton);
|
|
}
|
|
|
|
reqElements.searchButton.addEventListener('click', (e) => {
|
|
if(reqElements.searchButton.innerText === 'Search'){
|
|
performSearch();
|
|
}else{
|
|
clearSearch();
|
|
}
|
|
});
|
|
reqElements.searchInput.addEventListener('keyup', (e) => {
|
|
if(e.keyCode !== 13){
|
|
if(reqElements.searchButton.innerText !== 'Search'){
|
|
reqElements.searchButton.innerText = 'Search';
|
|
}
|
|
}else{
|
|
if(reqElements.searchInput.value.length){
|
|
performSearch();
|
|
}else{
|
|
clearSearch();
|
|
}
|
|
}
|
|
});
|
|
|
|
//Clear search
|
|
function clearSearch(){
|
|
reqElements.searchInput.value = '';
|
|
reqElements.spinner.classList.remove('d-none');
|
|
reqElements.content.classList.add('d-none');
|
|
reqElements.message.classList.add('d-none');
|
|
reqElements.searchButton.innerText = 'Search';
|
|
activeSearchString = '';
|
|
queryRequests();
|
|
}
|
|
|
|
//Perform search
|
|
function performSearch(){
|
|
activeSearchString = reqElements.searchInput.value.trim();
|
|
if(!activeSearchString.length) return clearSearch();
|
|
reqElements.spinner.classList.remove('d-none');
|
|
reqElements.content.classList.add('d-none');
|
|
reqElements.message.classList.add('d-none');
|
|
reqElements.searchButton.innerText = 'Clear';
|
|
queryRequests();
|
|
}
|
|
|
|
//Performs auto-search
|
|
const urlHash = decodeURI(location.hash.substr(1).trim());
|
|
if(urlHash.length){
|
|
reqElements.searchInput.value = urlHash;
|
|
}
|
|
performSearch();
|
|
|
|
const fillRequestsTableRows = (reqsArray) => {
|
|
reqElements.tableBody.innerHTML = '';
|
|
|
|
for (const req of reqsArray) {
|
|
const newNode = document.createElement("tr");
|
|
newNode.innerHTML = reqElements.entryTemplate.innerHTML;
|
|
newNode.querySelector('td:nth-child(1)').textContent = req.id;
|
|
newNode.querySelector('td:nth-child(2)').textContent = req.playerDisplayName;
|
|
newNode.querySelector('td:nth-child(2)').title = `${req.playerDisplayName}\nlicense:${req.license}`;
|
|
newNode.querySelector('td:nth-child(3)').title = req.discordTag;
|
|
newNode.querySelector('td:nth-child(3) div img').src = req.discordAvatar
|
|
? req.discordAvatar
|
|
: 'img/default_avatar.png';
|
|
newNode.querySelector('td:nth-child(3) span').textContent = req.discordTag
|
|
? req.discordTag
|
|
: 'not available';
|
|
|
|
const tsLastAttempt = new Date(req.tsLastAttempt * 1000)
|
|
newNode.querySelector('td:nth-child(4)').title = tsLastAttempt.toLocaleString(
|
|
navigator.language,
|
|
{ dateStyle: 'long', timeStyle: 'long' }
|
|
);
|
|
newNode.querySelector('td:nth-child(4)').textContent = tsLastAttempt.toLocaleString(
|
|
navigator.language,
|
|
{ dateStyle: 'short', timeStyle: 'short' }
|
|
);
|
|
|
|
newNode.querySelector('td:nth-child(5) a:nth-child(1)').addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
if(!hasWhitelistPermission) return;
|
|
approveDenyRequest(req.id, 'approve');
|
|
});
|
|
newNode.querySelector('td:nth-child(5) a:nth-child(2)').addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
if(!hasWhitelistPermission) return;
|
|
approveDenyRequest(req.id, 'deny');
|
|
});
|
|
|
|
reqElements.tableBody.appendChild(newNode);
|
|
}
|
|
}
|
|
|
|
function setPaginator(currPage, totalPages){
|
|
currentPage = currPage;
|
|
paginator.mid.querySelector('a').textContent = currPage;
|
|
if(currPage > 1){
|
|
paginator.prev.classList.remove('disabled');
|
|
}else{
|
|
paginator.prev.classList.add('disabled');
|
|
}
|
|
if(totalPages > currPage){
|
|
paginator.next.classList.remove('disabled');
|
|
}else{
|
|
paginator.next.classList.add('disabled');
|
|
}
|
|
}
|
|
|
|
function reqsPrevPage(){
|
|
queryRequests(currentPage > 1 ? currentPage - 1 : 1);
|
|
}
|
|
function reqsNextPage(){
|
|
queryRequests(currentPage + 1);
|
|
}
|
|
|
|
function queryRequests(page){
|
|
location.hash = encodeURI(activeSearchString);
|
|
const urlSearch = new URLSearchParams();
|
|
if(activeSearchString) urlSearch.append('searchString', activeSearchString);
|
|
if(page) urlSearch.append('page', page);
|
|
|
|
txAdminAPI({
|
|
type: "GET",
|
|
url: `/whitelist/requests?${urlSearch.toString()}`,
|
|
timeout: REQ_TIMEOUT_LONG,
|
|
success: function (data) {
|
|
reqElements.spinner.classList.add('d-none');
|
|
reqElements.denyAllBtton.disabled = true;
|
|
if(data.error){
|
|
reqElements.message.classList.remove('d-none', 'text-muted');
|
|
reqElements.message.classList.add('text-danger');
|
|
reqElements.message.textContent = `Error: ${data.error}`;
|
|
}else if(!Array.isArray(data.requests)){
|
|
reqElements.message.classList.remove('d-none', 'text-muted');
|
|
reqElements.message.classList.add('text-danger');
|
|
reqElements.message.textContent = 'Error reading API response';
|
|
}else if(!data.requests.length){
|
|
setPaginator(1, 1);
|
|
newestRequest = data.newest;
|
|
reqElements.counter.innerText = 0;
|
|
reqElements.content.classList.add('d-none');
|
|
reqElements.message.classList.remove('d-none');
|
|
}else{
|
|
newestRequest = data.newest;
|
|
setPaginator(data.currPage, data.totalPages);
|
|
if(data.cntTotal === data.cntFiltered){
|
|
reqElements.counter.innerText = data.cntTotal;
|
|
reqElements.denyAllBtton.disabled = false;
|
|
}else{
|
|
reqElements.counter.innerText = `${data.cntFiltered} of ${data.cntTotal}`;
|
|
}
|
|
reqElements.message.classList.add('d-none');
|
|
reqElements.content.classList.remove('d-none');
|
|
fillRequestsTableRows(data.requests);
|
|
}
|
|
},
|
|
error: function (xmlhttprequest, textstatus, message) {
|
|
reqElements.spinner.classList.add('d-none');
|
|
reqElements.message.classList.remove('d-none', 'text-muted');
|
|
reqElements.message.classList.add('text-danger');
|
|
reqElements.message.textContent = 'Error reading API response';
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
function approveDenyRequest(reqId, action){
|
|
const notify = $.notify({ message: '<p class="text-center">Processing...</p>' }, {});
|
|
|
|
txAdminAPI({
|
|
type: 'POST',
|
|
url: `/whitelist/requests/${action}`,
|
|
data: {reqId},
|
|
timeout: REQ_TIMEOUT_LONG,
|
|
success: function (data) {
|
|
notify.update('progress', 0);
|
|
if (data.success === true) {
|
|
window.location.reload(true);
|
|
return true;
|
|
} else {
|
|
notify.update('type', 'danger');
|
|
notify.update('message', data.error || 'unknown error');
|
|
}
|
|
},
|
|
error: function (xmlhttprequest, textstatus, message) {
|
|
notify.update('progress', 0);
|
|
notify.update('type', 'danger');
|
|
notify.update('message', message);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function denyAllRequests(reqId, action){
|
|
const confirmOptions = {
|
|
title: 'Deny all whitelist requests?',
|
|
content: `Players will still be able to try to join again, and will receive a new request ID.`,
|
|
};
|
|
if (!await txAdminConfirm(confirmOptions)) return;
|
|
|
|
const notify = $.notify({ message: '<p class="text-center">Processing...</p>' }, {});
|
|
|
|
txAdminAPI({
|
|
type: 'POST',
|
|
url: `/whitelist/requests/deny_all`,
|
|
data: {newestVisible: newestRequest},
|
|
timeout: REQ_TIMEOUT_LONG,
|
|
success: function (data) {
|
|
notify.update('progress', 0);
|
|
if (data.success === true) {
|
|
window.location.reload(true);
|
|
return true;
|
|
} else {
|
|
notify.update('type', 'danger');
|
|
notify.update('message', data.error || 'unknown error');
|
|
}
|
|
},
|
|
error: function (xmlhttprequest, textstatus, message) {
|
|
notify.update('progress', 0);
|
|
notify.update('type', 'danger');
|
|
notify.update('message', message);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* APPROVED WHITELISTS
|
|
*/
|
|
const approvedElements = {
|
|
spinner: document.getElementById('approved-spinner'),
|
|
message: document.getElementById('approved-message'),
|
|
content: document.getElementById('approved-content'),
|
|
counter: document.getElementById('approved-counter'),
|
|
tableBody: document.getElementById('approved-tableBody'),
|
|
entryTemplate: document.getElementById('approved-entryTemplate'),
|
|
};
|
|
|
|
//If no perms, edit template
|
|
if(!hasWhitelistPermission){
|
|
approvedElements.entryTemplate.content.querySelectorAll('.action-buttons').forEach(disableActionButton);
|
|
}
|
|
|
|
const fillApprovedTableRows = (approvalsArray) => {
|
|
for (const approval of approvalsArray) {
|
|
const newNode = document.createElement("tr");
|
|
newNode.innerHTML = approvedElements.entryTemplate.innerHTML;
|
|
newNode.querySelector('td:nth-child(1) div img').src = approval.playerAvatar
|
|
? approval.playerAvatar
|
|
: 'img/default_avatar.png';
|
|
newNode.querySelector('td:nth-child(1) span').textContent = approval.playerName;
|
|
newNode.querySelector('td:nth-child(1) span').title = `${approval.playerName}\n${approval.identifier}`;
|
|
newNode.querySelector('td:nth-child(2)').textContent = approval.approvedBy;
|
|
|
|
const tsApproved = new Date(approval.tsApproved * 1000)
|
|
newNode.querySelector('td:nth-child(3)').title = tsApproved.toLocaleString(
|
|
navigator.language,
|
|
{ dateStyle: 'long', timeStyle: 'long' }
|
|
);
|
|
newNode.querySelector('td:nth-child(3)').textContent = tsApproved.toLocaleString(
|
|
navigator.language,
|
|
{ dateStyle: 'short', timeStyle: 'short' }
|
|
);
|
|
|
|
newNode.querySelector('td:nth-child(4) a:nth-child(1)').addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
if(!hasWhitelistPermission) return;
|
|
removeApproval(approval.identifier)
|
|
});
|
|
|
|
approvedElements.tableBody.appendChild(newNode);
|
|
}
|
|
}
|
|
|
|
txAdminAPI({
|
|
type: "GET",
|
|
url: '/whitelist/approvals',
|
|
timeout: REQ_TIMEOUT_LONG,
|
|
success: function (data) {
|
|
approvedElements.spinner.classList.add('d-none');
|
|
if(data.error){
|
|
approvedElements.message.classList.remove('d-none', 'text-muted');
|
|
approvedElements.message.classList.add('text-danger');
|
|
approvedElements.message.textContent = `Error: ${data.error}`;
|
|
}else if(!Array.isArray(data)){
|
|
approvedElements.message.classList.remove('d-none', 'text-muted');
|
|
approvedElements.message.classList.add('text-danger');
|
|
approvedElements.message.textContent = 'Error reading API response';
|
|
}else if(!data.length){
|
|
approvedElements.counter.innerText = 0;
|
|
approvedElements.content.classList.add('d-none');
|
|
approvedElements.message.classList.remove('d-none');
|
|
}else{
|
|
approvedElements.counter.innerText = data.length;
|
|
approvedElements.message.classList.add('d-none');
|
|
approvedElements.content.classList.remove('d-none');
|
|
fillApprovedTableRows(data);
|
|
}
|
|
},
|
|
error: function (xmlhttprequest, textstatus, message) {
|
|
approvedElements.spinner.classList.add('d-none');
|
|
approvedElements.message.classList.remove('d-none', 'text-muted');
|
|
approvedElements.message.classList.add('text-danger');
|
|
approvedElements.message.textContent = 'Error reading API response';
|
|
}
|
|
});
|
|
|
|
async function addApproval(){
|
|
//TODO: when migrating to react, get this from the shared folder
|
|
const acceptedIdTypes = ['discord', 'fivem', 'license', 'license2', 'live', 'steam', 'xbl'];
|
|
let identifier = await txAdminPrompt({
|
|
modalColor: 'green',
|
|
confirmBtnClass: 'btn-green',
|
|
title: 'Whitelist player',
|
|
description: `Type in the Player Identifier you want to whitelist. <br>
|
|
This can be any of the following: <code>${acceptedIdTypes.join(', ')}</code>`,
|
|
placeholder: 'discord:272800190639898628'
|
|
});
|
|
if(typeof identifier !== 'string') return;
|
|
|
|
//Check input validity
|
|
identifier = identifier.trim();
|
|
if(!identifier.length || !acceptedIdTypes.includes(identifier.split(':', 1)[0])){
|
|
return $.notify({ message: 'Error: the provided identifier does not seem to be valid.' }, { type: 'danger' });
|
|
}
|
|
|
|
const notify = $.notify({ message: '<p class="text-center">Processing...</p>' }, {});
|
|
txAdminAPI({
|
|
type: 'POST',
|
|
url: '/whitelist/approvals/add',
|
|
data: {identifier},
|
|
timeout: REQ_TIMEOUT_LONG,
|
|
success: function (data) {
|
|
notify.update('progress', 0);
|
|
if (data.success === true) {
|
|
// notify.update('type', 'success');
|
|
// notify.update('message', 'xxxxxxx.');
|
|
window.location.reload(true);
|
|
return true;
|
|
} else {
|
|
notify.update('type', 'danger');
|
|
notify.update('message', data.error || 'unknown error');
|
|
}
|
|
},
|
|
error: function (xmlhttprequest, textstatus, message) {
|
|
notify.update('progress', 0);
|
|
notify.update('type', 'danger');
|
|
notify.update('message', message);
|
|
}
|
|
});
|
|
}
|
|
|
|
function removeApproval(identifier){
|
|
const notify = $.notify({ message: '<p class="text-center">Processing...</p>' }, {});
|
|
|
|
txAdminAPI({
|
|
type: 'POST',
|
|
url: '/whitelist/approvals/remove',
|
|
data: {identifier},
|
|
timeout: REQ_TIMEOUT_LONG,
|
|
success: function (data) {
|
|
notify.update('progress', 0);
|
|
if (data.success === true) {
|
|
// notify.update('type', 'success');
|
|
// notify.update('message', 'xxxxxxx.');
|
|
window.location.reload(true);
|
|
return true;
|
|
} else {
|
|
notify.update('type', 'danger');
|
|
notify.update('message', data.error || 'unknown error');
|
|
}
|
|
},
|
|
error: function (xmlhttprequest, textstatus, message) {
|
|
notify.update('progress', 0);
|
|
notify.update('type', 'danger');
|
|
notify.update('message', message);
|
|
}
|
|
});
|
|
}
|
|
|
|
</script>
|