Blacklist

This commit is contained in:
2019-05-27 03:40:00 +01:00
parent b9faad4175
commit 8f41184f74
20 changed files with 5419 additions and 16 deletions

View File

@ -1,7 +1,6 @@
package manage
import (
"fmt"
"github.com/TicketsBot/GoPanel/app/http/template"
"github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table"
@ -50,23 +49,63 @@ func BlacklistHandler(ctx *gin.Context) {
return
}
curid, err := strconv.ParseInt("210105426849562625", 10, 64)
table.RemoveBlacklist(guildId, curid)
fmt.Println(table.IsBlacklisted(guildId, curid))
blacklistedUsers := table.GetBlacklistNodes(guildId)
//blacklistedUsers := table.GetBlacklistNodes(guildId)
//var blacklisted []map[string]interface{}
//for _, node := range blacklistedUsers {
var blacklistedIds []int64
for _, user := range blacklistedUsers {
blacklistedIds = append(blacklistedIds, user.User)
}
//}
usernames := table.GetUsernames(blacklistedIds)
fmt.Println(len(guild.Members))
var blacklisted []map[string]interface{}
for _, node := range blacklistedUsers {
blacklisted = append(blacklisted, map[string]interface{}{
"userId": node.User,
"username": usernames[node.User],
})
}
userNotFound := false
isStaff := false
if store.Get("csrf").(string) == ctx.Query("csrf") { // CSRF is correct *and* set
targetIdStr := ctx.Query("userid")
targetId, err := strconv.ParseInt(targetIdStr, 10, 64)
if err != nil {
userNotFound = true
} else {
// Verify that the user ID is real and in a shared guild
username := table.GetUsername(targetId)
exists := username != ""
if exists {
if guild.OwnerId == targetIdStr || table.IsSupport(guildId, targetId) { // Prevent users from blacklisting staff
isStaff = true
} else {
if !utils.Contains(blacklistedIds, targetId) { // Prevent duplicates
table.AddBlacklist(guildId, targetId)
blacklisted = append(blacklisted, map[string]interface{}{
"userId": targetIdStr,
"username": username,
})
}
}
} else {
userNotFound = true
}
}
}
utils.Respond(ctx, template.TemplateBlacklist.Render(map[string]interface{}{
"name": store.Get("name").(string),
"guildId": guildIdStr,
"csrf": store.Get("csrf").(string),
"avatar": store.Get("avatar").(string),
"baseUrl": config.Conf.Server.BaseUrl,
"blacklisted": blacklisted,
"userNotFound": userNotFound,
"isStaff": isStaff,
}))
}
}

View File

@ -0,0 +1,63 @@
package manage
import (
"fmt"
"github.com/TicketsBot/GoPanel/config"
"github.com/TicketsBot/GoPanel/database/table"
"github.com/TicketsBot/GoPanel/utils"
"github.com/TicketsBot/GoPanel/utils/discord/objects"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
"strconv"
)
func BlacklistRemoveHandler(ctx *gin.Context) {
store := sessions.Default(ctx)
if store == nil {
return
}
defer store.Save()
if utils.IsLoggedIn(store) {
userIdStr := store.Get("userid").(string)
userId, err := utils.GetUserId(store)
if err != nil {
ctx.String(500, err.Error())
return
}
// Verify the guild exists
guildIdStr := ctx.Param("id")
guildId, err := strconv.ParseInt(guildIdStr, 10, 64)
if err != nil {
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 404 Page
return
}
// Get object for selected guild
var guild objects.Guild
for _, g := range table.GetGuilds(userIdStr) {
if g.Id == guildIdStr {
guild = g
break
}
}
// Verify the user has permissions to be here
if !guild.Owner && !table.IsAdmin(guildId, userId) {
ctx.Redirect(302, config.Conf.Server.BaseUrl) // TODO: 403 Page
return
}
if ctx.Query("c") == store.Get("csrf").(string) {
targetIdStr := ctx.Param("user")
targetId, err := strconv.ParseInt(targetIdStr, 10, 64)
if err == nil { // If it's a real ID
table.RemoveBlacklist(guildId, targetId)
}
}
ctx.Redirect(302, fmt.Sprintf("/manage/%s/blacklist", guildIdStr))
}
}

View File

@ -59,6 +59,9 @@ func StartServer() {
// /manage/:id/blacklist
router.GET("/manage/:id/blacklist", manage.BlacklistHandler)
// /manage/:id/blacklist/remove/:user
router.GET("/manage/:id/blacklist/remove/:user", manage.BlacklistRemoveHandler)
if err := router.Run(config.Conf.Server.Host); err != nil {
panic(err)
}

View File

@ -25,8 +25,20 @@ func GetAdminGuilds(userId int64) []int64 {
return ids
}
func IsSupport(guildId int64, userId int64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsSupport
}
func IsAdmin(guildId int64, userId int64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsAdmin
}
func IsStaff(guildId int64, userId int64) bool {
var node PermissionNode
database.Database.Where(&PermissionNode{GuildId: guildId, UserId: userId}).Take(&node)
return node.IsAdmin || node.IsSupport
}

View File

@ -0,0 +1,40 @@
package table
import (
"encoding/base64"
"github.com/TicketsBot/GoPanel/database"
)
type UsernameNode struct {
Id int64 `gorm:"column:USERID;primary_key"`
Name string `gorm:"column:USERNAME;type:text"` // Base 64 encoded
}
func (UsernameNode) TableName() string {
return "usernames"
}
func GetUsername(id int64) string {
node := UsernameNode{Name: "Unknown"}
database.Database.Where(&UsernameNode{Id: id}).First(&node)
return base64Decode(node.Name)
}
func GetUsernames(ids []int64) map[int64]string {
var nodes []UsernameNode
database.Database.Where(ids).Find(&nodes)
m := make(map[int64]string)
for _, node := range nodes {
m[node.Id] = base64Decode(node.Name)
}
return m
}
func base64Decode(s string) string {
b, err := base64.StdEncoding.DecodeString(s); if err != nil {
return ""
}
return string(b)
}

6
public/static/css/animate.min.css vendored Normal file

File diff suppressed because one or more lines are too long

5
public/static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="white">
<circle transform="translate(8 0)" cx="0" cy="16" r="0">
<animate attributeName="r" values="0; 4; 0; 0" dur="1.2s" repeatCount="indefinite" begin="0"
keytimes="0;0.2;0.7;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="translate(16 0)" cx="0" cy="16" r="0">
<animate attributeName="r" values="0; 4; 0; 0" dur="1.2s" repeatCount="indefinite" begin="0.3"
keytimes="0;0.2;0.7;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" calcMode="spline" />
</circle>
<circle transform="translate(24 0)" cx="0" cy="16" r="0">
<animate attributeName="r" values="0; 4; 0; 0" dur="1.2s" repeatCount="indefinite" begin="0.6"
keytimes="0;0.2;0.7;1" keySplines="0.2 0.2 0.4 0.8;0.2 0.6 0.4 0.8;0.2 0.6 0.4 0.8" calcMode="spline" />
</circle>
</svg>

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

404
public/static/js/bootstrap-notify.js vendored Normal file
View File

@ -0,0 +1,404 @@
/*
Creative Tim Modifications
Lines: 239, 240 was changed from top: 5px to top: 50% and we added margin-top: -13px. In this way the close button will be aligned vertically
Line:242 - modified when the icon is set, we add the class "alert-with-icon", so there will be enough space for the icon.
*/
/*
* Project: Bootstrap Notify = v3.1.5
* Description: Turns standard Bootstrap alerts into "Growl-like" notifications.
* Author: Mouse0270 aka Robert McIntosh
* License: MIT License
* Website: https://github.com/mouse0270/bootstrap-growl
*/
/* global define:false, require: false, jQuery:false */
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS
factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
// Create the defaults once
var defaults = {
element: 'body',
position: null,
type: "info",
allow_dismiss: true,
allow_duplicates: true,
newest_on_top: false,
showProgressbar: false,
placement: {
from: "top",
align: "right"
},
offset: 20,
spacing: 10,
z_index: 1031,
delay: 5000,
timer: 1000,
url_target: '_blank',
mouse_over: null,
animate: {
enter: 'animated fadeInDown',
exit: 'animated fadeOutUp'
},
onShow: null,
onShown: null,
onClose: null,
onClosed: null,
icon_type: 'class',
template: '<div data-notify="container" class="col-xs-11 col-sm-4 alert alert-{0}" role="alert"><button type="button" aria-hidden="true" class="close" data-notify="dismiss">&times;</button><span data-notify="icon"></span> <span data-notify="title">{1}</span> <span data-notify="message">{2}</span><div class="progress" data-notify="progressbar"><div class="progress-bar progress-bar-{0}" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div></div><a href="{3}" target="{4}" data-notify="url"></a></div>'
};
String.format = function () {
var str = arguments[0];
for (var i = 1; i < arguments.length; i++) {
str = str.replace(RegExp("\\{" + (i - 1) + "\\}", "gm"), arguments[i]);
}
return str;
};
function isDuplicateNotification(notification) {
var isDupe = false;
$('[data-notify="container"]').each(function (i, el) {
var $el = $(el);
var title = $el.find('[data-notify="title"]').text().trim();
var message = $el.find('[data-notify="message"]').html().trim();
// The input string might be different than the actual parsed HTML string!
// (<br> vs <br /> for example)
// So we have to force-parse this as HTML here!
var isSameTitle = title === $("<div>" + notification.settings.content.title + "</div>").html().trim();
var isSameMsg = message === $("<div>" + notification.settings.content.message + "</div>").html().trim();
var isSameType = $el.hasClass('alert-' + notification.settings.type);
if (isSameTitle && isSameMsg && isSameType) {
//we found the dupe. Set the var and stop checking.
isDupe = true;
}
return !isDupe;
});
return isDupe;
}
function Notify(element, content, options) {
// Setup Content of Notify
var contentObj = {
content: {
message: typeof content === 'object' ? content.message : content,
title: content.title ? content.title : '',
icon: content.icon ? content.icon : '',
url: content.url ? content.url : '#',
target: content.target ? content.target : '-'
}
};
options = $.extend(true, {}, contentObj, options);
this.settings = $.extend(true, {}, defaults, options);
this._defaults = defaults;
if (this.settings.content.target === "-") {
this.settings.content.target = this.settings.url_target;
}
this.animations = {
start: 'webkitAnimationStart oanimationstart MSAnimationStart animationstart',
end: 'webkitAnimationEnd oanimationend MSAnimationEnd animationend'
};
if (typeof this.settings.offset === 'number') {
this.settings.offset = {
x: this.settings.offset,
y: this.settings.offset
};
}
//if duplicate messages are not allowed, then only continue if this new message is not a duplicate of one that it already showing
if (this.settings.allow_duplicates || (!this.settings.allow_duplicates && !isDuplicateNotification(this))) {
this.init();
}
}
$.extend(Notify.prototype, {
init: function () {
var self = this;
this.buildNotify();
if (this.settings.content.icon) {
this.setIcon();
}
if (this.settings.content.url != "#") {
this.styleURL();
}
this.styleDismiss();
this.placement();
this.bind();
this.notify = {
$ele: this.$ele,
update: function (command, update) {
var commands = {};
if (typeof command === "string") {
commands[command] = update;
} else {
commands = command;
}
for (var cmd in commands) {
switch (cmd) {
case "type":
this.$ele.removeClass('alert-' + self.settings.type);
this.$ele.find('[data-notify="progressbar"] > .progress-bar').removeClass('progress-bar-' + self.settings.type);
self.settings.type = commands[cmd];
this.$ele.addClass('alert-' + commands[cmd]).find('[data-notify="progressbar"] > .progress-bar').addClass('progress-bar-' + commands[cmd]);
break;
case "icon":
var $icon = this.$ele.find('[data-notify="icon"]');
if (self.settings.icon_type.toLowerCase() === 'class') {
$icon.removeClass(self.settings.content.icon).addClass(commands[cmd]);
} else {
if (!$icon.is('img')) {
$icon.find('img');
}
$icon.attr('src', commands[cmd]);
}
break;
case "progress":
var newDelay = self.settings.delay - (self.settings.delay * (commands[cmd] / 100));
this.$ele.data('notify-delay', newDelay);
this.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', commands[cmd]).css('width', commands[cmd] + '%');
break;
case "url":
this.$ele.find('[data-notify="url"]').attr('href', commands[cmd]);
break;
case "target":
this.$ele.find('[data-notify="url"]').attr('target', commands[cmd]);
break;
default:
this.$ele.find('[data-notify="' + cmd + '"]').html(commands[cmd]);
}
}
var posX = this.$ele.outerHeight() + parseInt(self.settings.spacing) + parseInt(self.settings.offset.y);
self.reposition(posX);
},
close: function () {
self.close();
}
};
},
buildNotify: function () {
var content = this.settings.content;
this.$ele = $(String.format(this.settings.template, this.settings.type, content.title, content.message, content.url, content.target));
this.$ele.attr('data-notify-position', this.settings.placement.from + '-' + this.settings.placement.align);
if (!this.settings.allow_dismiss) {
this.$ele.find('[data-notify="dismiss"]').css('display', 'none');
}
if ((this.settings.delay <= 0 && !this.settings.showProgressbar) || !this.settings.showProgressbar) {
this.$ele.find('[data-notify="progressbar"]').remove();
}
},
setIcon: function () {
this.$ele.addClass('alert-with-icon');
if (this.settings.icon_type.toLowerCase() === 'class') {
this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon);
} else {
if (this.$ele.find('[data-notify="icon"]').is('img')) {
this.$ele.find('[data-notify="icon"]').attr('src', this.settings.content.icon);
} else {
this.$ele.find('[data-notify="icon"]').append('<img src="' + this.settings.content.icon + '" alt="Notify Icon" />');
}
}
},
styleDismiss: function () {
this.$ele.find('[data-notify="dismiss"]').css({
position: 'absolute',
right: '10px',
top: '50%',
marginTop: '-13px',
zIndex: this.settings.z_index + 2
});
},
styleURL: function () {
this.$ele.find('[data-notify="url"]').css({
backgroundImage: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)',
height: '100%',
left: 0,
position: 'absolute',
top: 0,
width: '100%',
zIndex: this.settings.z_index + 1
});
},
placement: function () {
var self = this,
offsetAmt = this.settings.offset.y,
css = {
display: 'inline-block',
margin: '0px auto',
position: this.settings.position ? this.settings.position : (this.settings.element === 'body' ? 'fixed' : 'absolute'),
transition: 'all .5s ease-in-out',
zIndex: this.settings.z_index
},
hasAnimation = false,
settings = this.settings;
$('[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])').each(function () {
offsetAmt = Math.max(offsetAmt, parseInt($(this).css(settings.placement.from)) + parseInt($(this).outerHeight()) + parseInt(settings.spacing));
});
if (this.settings.newest_on_top === true) {
offsetAmt = this.settings.offset.y;
}
css[this.settings.placement.from] = offsetAmt + 'px';
switch (this.settings.placement.align) {
case "left":
case "right":
css[this.settings.placement.align] = this.settings.offset.x + 'px';
break;
case "center":
css.left = 0;
css.right = 0;
break;
}
this.$ele.css(css).addClass(this.settings.animate.enter);
$.each(Array('webkit-', 'moz-', 'o-', 'ms-', ''), function (index, prefix) {
self.$ele[0].style[prefix + 'AnimationIterationCount'] = 1;
});
$(this.settings.element).append(this.$ele);
if (this.settings.newest_on_top === true) {
offsetAmt = (parseInt(offsetAmt) + parseInt(this.settings.spacing)) + this.$ele.outerHeight();
this.reposition(offsetAmt);
}
if ($.isFunction(self.settings.onShow)) {
self.settings.onShow.call(this.$ele);
}
this.$ele.one(this.animations.start, function () {
hasAnimation = true;
}).one(this.animations.end, function () {
if ($.isFunction(self.settings.onShown)) {
self.settings.onShown.call(this);
}
});
setTimeout(function () {
if (!hasAnimation) {
if ($.isFunction(self.settings.onShown)) {
self.settings.onShown.call(this);
}
}
}, 600);
},
bind: function () {
var self = this;
this.$ele.find('[data-notify="dismiss"]').on('click', function () {
self.close();
});
this.$ele.mouseover(function () {
$(this).data('data-hover', "true");
}).mouseout(function () {
$(this).data('data-hover', "false");
});
this.$ele.data('data-hover', "false");
if (this.settings.delay > 0) {
self.$ele.data('notify-delay', self.settings.delay);
var timer = setInterval(function () {
var delay = parseInt(self.$ele.data('notify-delay')) - self.settings.timer;
if ((self.$ele.data('data-hover') === 'false' && self.settings.mouse_over === "pause") || self.settings.mouse_over != "pause") {
var percent = ((self.settings.delay - delay) / self.settings.delay) * 100;
self.$ele.data('notify-delay', delay);
self.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', percent).css('width', percent + '%');
}
if (delay <= -(self.settings.timer)) {
clearInterval(timer);
self.close();
}
}, self.settings.timer);
}
},
close: function () {
var self = this,
posX = parseInt(this.$ele.css(this.settings.placement.from)),
hasAnimation = false;
this.$ele.data('closing', 'true').addClass(this.settings.animate.exit);
self.reposition(posX);
if ($.isFunction(self.settings.onClose)) {
self.settings.onClose.call(this.$ele);
}
this.$ele.one(this.animations.start, function () {
hasAnimation = true;
}).one(this.animations.end, function () {
$(this).remove();
if ($.isFunction(self.settings.onClosed)) {
self.settings.onClosed.call(this);
}
});
setTimeout(function () {
if (!hasAnimation) {
self.$ele.remove();
if (self.settings.onClosed) {
self.settings.onClosed(self.$ele);
}
}
}, 600);
},
reposition: function (posX) {
var self = this,
notifies = '[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])',
$elements = this.$ele.nextAll(notifies);
if (this.settings.newest_on_top === true) {
$elements = this.$ele.prevAll(notifies);
}
$elements.each(function () {
$(this).css(self.settings.placement.from, posX);
posX = (parseInt(posX) + parseInt(self.settings.spacing)) + $(this).outerHeight();
});
}
});
$.notify = function (content, options) {
var plugin = new Notify(this, content, options);
return plugin.notify;
};
$.notifyDefaults = function (options) {
defaults = $.extend(true, {}, defaults, options);
return defaults;
};
$.notifyClose = function (command) {
if (typeof command === "undefined" || command === "all") {
$('[data-notify]').find('[data-notify="dismiss"]').trigger('click');
} else {
$('[data-notify-position="' + command + '"]').find('[data-notify="dismiss"]').trigger('click');
}
};
}));

438
public/static/js/bootstrap-select.js vendored Normal file
View File

@ -0,0 +1,438 @@
!function($) {
var Selectpicker = function(element, options, e) {
if (e ) {
e.stopPropagation();
e.preventDefault();
}
this.$element = $(element);
this.$newElement = null;
this.button = null;
//Merge defaults, options and data-attributes to make our options
this.options = $.extend({}, $.fn.selectpicker.defaults, this.$element.data(), typeof options == 'object' && options);
//If we have no title yet, check the attribute 'title' (this is missed by jq as its not a data-attribute
if(this.options.title==null)
this.options.title = this.$element.attr('title');
//Expose public methods
this.val = Selectpicker.prototype.val;
this.render = Selectpicker.prototype.render;
this.init();
};
Selectpicker.prototype = {
constructor: Selectpicker,
init: function (e) {
var _this = this;
this.$element.hide();
this.multiple = this.$element.prop('multiple');
var classList = this.$element.attr('class') !== undefined ? this.$element.attr('class').split(/\s+/) : '';
var id = this.$element.attr('id');
this.$element.after( this.createView() );
this.$newElement = this.$element.next('.select');
var select = this.$newElement;
var menu = this.$newElement.find('.dropdown-menu');
var menuArrow = this.$newElement.find('.dropdown-arrow');
var menuA = menu.find('li > a');
var liHeight = select.addClass('open').find('.dropdown-menu li > a').outerHeight();
select.removeClass('open');
var divHeight = menu.find('li .divider').outerHeight(true);
var selectOffset_top = this.$newElement.offset().top;
var size = 0;
var menuHeight = 0;
var selectHeight = this.$newElement.outerHeight();
this.button = this.$newElement.find('> button');
if (id !== undefined) {
this.button.attr('id', id);
$('label[for="' + id + '"]').click(function(){ select.find('button#'+id).focus(); })
}
for (var i = 0; i < classList.length; i++) {
if(classList[i] != 'selectpicker') {
this.$newElement.addClass(classList[i]);
}
}
//If we are multiple, then add the show-tick class by default
if(this.multiple) {
this.$newElement.addClass('select-multiple');
}
this.button.addClass(this.options.style);
menu.addClass(this.options.menuStyle);
menuArrow.addClass(function() {
if (_this.options.menuStyle) {
return _this.options.menuStyle.replace('dropdown-', 'dropdown-arrow-');
}
});
this.checkDisabled();
this.checkTabIndex();
this.clickListener();
var menuPadding = parseInt(menu.css('padding-top')) + parseInt(menu.css('padding-bottom')) + parseInt(menu.css('border-top-width')) + parseInt(menu.css('border-bottom-width'));
if (this.options.size == 'auto') {
// Creative Tim Changes: We changed the regular function made in bootstrap-select with this function so the getSize() will not be triggered one million times per second while you scroll.
var getSize = debounce(function() {
var selectOffset_top_scroll = selectOffset_top - $(window).scrollTop();
var windowHeight = $(window).innerHeight();
var menuExtras = menuPadding + parseInt(menu.css('margin-top')) + parseInt(menu.css('margin-bottom')) + 2;
var selectOffset_bot = windowHeight - selectOffset_top_scroll - selectHeight - menuExtras;
menuHeight = selectOffset_bot;
if (select.hasClass('dropup')) {
menuHeight = selectOffset_top_scroll - menuExtras;
}
//limit menuHeight to 300px to have a smooth transition with cubic bezier on dropdown
if(menuHeight >= 300){
menuHeight = 300;
}
menu.css({'max-height' : menuHeight + 'px', 'overflow-y' : 'auto', 'min-height' : liHeight * 3 + 'px'});
}, 50);
getSize;
$(window).on('scroll', getSize);
$(window).on('resize', getSize);
if (window.MutationObserver) {
new MutationObserver(getSize).observe(this.$element.get(0), {
childList: true
});
} else {
this.$element.bind('DOMNodeInserted', getSize);
}
} else if (this.options.size && this.options.size != 'auto' && menu.find('li').length > this.options.size) {
var optIndex = menu.find("li > *").filter(':not(.divider)').slice(0,this.options.size).last().parent().index();
var divLength = menu.find("li").slice(0,optIndex + 1).find('.divider').length;
menuHeight = liHeight*this.options.size + divLength*divHeight + menuPadding;
menu.css({'max-height' : menuHeight + 'px', 'overflow-y' : 'scroll'});
//console.log('sunt in if');
}
// Listen for updates to the DOM and re render... (Use Mutation Observer when availiable)
if (window.MutationObserver) {
new MutationObserver($.proxy(this.reloadLi, this)).observe(this.$element.get(0), {
childList: true
});
} else {
this.$element.bind('DOMNodeInserted', $.proxy(this.reloadLi, this));
}
this.render();
},
createDropdown: function() {
var drop =
"<div class='btn-group select'>" +
"<button class='btn dropdown-toggle clearfix' data-toggle='dropdown'>" +
"<span class='filter-option'></span>&nbsp;" +
"<span class='caret'></span>" +
"</button>" +
"<span class='dropdown-arrow'></span>" +
"<ul class='dropdown-menu' role='menu'>" +
"</ul>" +
"</div>";
return $(drop);
},
createView: function() {
var $drop = this.createDropdown();
var $li = this.createLi();
$drop.find('ul').append($li);
return $drop;
},
reloadLi: function() {
//Remove all children.
this.destroyLi();
//Re build
$li = this.createLi();
this.$newElement.find('ul').append( $li );
//render view
this.render();
},
destroyLi:function() {
this.$newElement.find('li').remove();
},
createLi: function() {
var _this = this;
var _li = [];
var _liA = [];
var _liHtml = '';
this.$element.find('option').each(function(){
_li.push($(this).text());
});
this.$element.find('option').each(function(index) {
//Get the class and text for the option
var optionClass = $(this).attr("class") !== undefined ? $(this).attr("class") : '';
var text = $(this).text();
var subtext = $(this).data('subtext') !== undefined ? '<small class="muted">'+$(this).data('subtext')+'</small>' : '';
//Append any subtext to the main text.
text+=subtext;
if ($(this).parent().is('optgroup') && $(this).data('divider') != true) {
if ($(this).index() == 0) {
//Get the opt group label
var label = $(this).parent().attr('label');
var labelSubtext = $(this).parent().data('subtext') !== undefined ? '<small class="muted">'+$(this).parent().data('subtext')+'</small>' : '';
label += labelSubtext;
if ($(this)[0].index != 0) {
_liA.push(
'<div class="divider"></div>'+
'<dt>'+label+'</dt>'+
_this.createA(text, "opt " + optionClass )
);
} else {
_liA.push(
'<dt>'+label+'</dt>'+
_this.createA(text, "opt " + optionClass ));
}
} else {
_liA.push( _this.createA(text, "opt " + optionClass ) );
}
} else if ($(this).data('divider') == true) {
_liA.push('<div class="divider"></div>');
} else if ($(this).data('hidden') == true) {
_liA.push('');
} else {
_liA.push( _this.createA(text, optionClass ) );
}
});
if (_li.length > 0) {
for (var i = 0; i < _li.length; i++) {
var $option = this.$element.find('option').eq(i);
_liHtml += "<li rel=" + i + ">" + _liA[i] + "</li>";
}
}
//If we dont have a selected item, and we dont have a title, select the first element so something is set in the button
if(this.$element.find('option:selected').length==0 && !_this.options.title) {
this.$element.find('option').eq(0).prop('selected', true).attr('selected', 'selected');
}
return $(_liHtml);
},
createA:function(test, classes) {
return '<a tabindex="-1" href="#" class="'+classes+'">' +
'<span class="">' + test + '</span>' +
'</a>';
},
render:function() {
var _this = this;
//Set width of select
if (this.options.width == 'auto') {
var ulWidth = this.$newElement.find('.dropdown-menu').css('width');
this.$newElement.css('width',ulWidth);
} else if (this.options.width && this.options.width != 'auto') {
this.$newElement.css('width',this.options.width);
}
//Update the LI to match the SELECT
this.$element.find('option').each(function(index) {
_this.setDisabled(index, $(this).is(':disabled') || $(this).parent().is(':disabled') );
_this.setSelected(index, $(this).is(':selected') );
});
var selectedItems = this.$element.find('option:selected').map(function(index,value) {
if($(this).attr('title')!=undefined) {
return $(this).attr('title');
} else {
return $(this).text();
}
}).toArray();
//Convert all the values into a comma delimited string
var title = selectedItems.join(", ");
//If this is multi select, and the selectText type is count, the show 1 of 2 selected etc..
if(_this.multiple && _this.options.selectedTextFormat.indexOf('count') > -1) {
var max = _this.options.selectedTextFormat.split(">");
if( (max.length>1 && selectedItems.length > max[1]) || (max.length==1 && selectedItems.length>=2)) {
title = selectedItems.length +' of ' + this.$element.find('option').length + ' selected';
}
}
//If we dont have a title, then use the default, or if nothing is set at all, use the not selected text
if(!title) {
title = _this.options.title != undefined ? _this.options.title : _this.options.noneSelectedText;
}
this.$element.next('.select').find('.filter-option').html( title );
},
setSelected:function(index, selected) {
if(selected) {
this.$newElement.find('li').eq(index).addClass('selected');
} else {
this.$newElement.find('li').eq(index).removeClass('selected');
}
},
setDisabled:function(index, disabled) {
if(disabled) {
this.$newElement.find('li').eq(index).addClass('disabled');
} else {
this.$newElement.find('li').eq(index).removeClass('disabled');
}
},
checkDisabled: function() {
if (this.$element.is(':disabled')) {
this.button.addClass('disabled');
this.button.click(function(e) {
e.preventDefault();
});
}
},
checkTabIndex: function() {
if (this.$element.is('[tabindex]')) {
var tabindex = this.$element.attr("tabindex");
this.button.attr('tabindex', tabindex);
}
},
clickListener: function() {
var _this = this;
$('body').on('touchstart.dropdown', '.dropdown-menu', function (e) { e.stopPropagation(); });
this.$newElement.on('click', 'li a', function(e){
var clickedIndex = $(this).parent().index(),
$this = $(this).parent(),
$select = $this.parents('.select');
//Dont close on multi choice menu
if(_this.multiple) {
e.stopPropagation();
}
e.preventDefault();
//Dont run if we have been disabled
if ($select.prev('select').not(':disabled') && !$(this).parent().hasClass('disabled')){
//Deselect all others if not multi select box
if (!_this.multiple) {
$select.prev('select').find('option').removeAttr('selected');
$select.prev('select').find('option').eq(clickedIndex).prop('selected', true).attr('selected', 'selected');
}
//Else toggle the one we have chosen if we are multi selet.
else {
var selected = $select.prev('select').find('option').eq(clickedIndex).prop('selected');
if(selected) {
$select.prev('select').find('option').eq(clickedIndex).removeAttr('selected');
} else {
$select.prev('select').find('option').eq(clickedIndex).prop('selected', true).attr('selected', 'selected');
}
}
$select.find('.filter-option').html($this.text());
$select.find('button').focus();
// Trigger select 'change'
$select.prev('select').trigger('change');
}
});
this.$newElement.on('click', 'li.disabled a, li dt, li .divider', function(e) {
e.preventDefault();
e.stopPropagation();
$select = $(this).parent().parents('.select');
$select.find('button').focus();
});
this.$element.on('change', function(e) {
_this.render();
});
},
val:function(value) {
if(value!=undefined) {
this.$element.val( value );
this.$element.trigger('change');
return this.$element;
} else {
return this.$element.val();
}
}
};
$.fn.selectpicker = function(option, event) {
//get the args of the outer function..
var args = arguments;
var value;
var chain = this.each(function () {
var $this = $(this),
data = $this.data('selectpicker'),
options = typeof option == 'object' && option;
if (!data) {
$this.data('selectpicker', (data = new Selectpicker(this, options, event)));
} else {
for(var i in option) {
data[i]=option[i];
}
}
if (typeof option == 'string') {
//Copy the value of option, as once we shift the arguments
//it also shifts the value of option.
property = option;
if(data[property] instanceof Function) {
[].shift.apply(args);
value = data[property].apply(data, args);
} else {
value = data[property];
}
}
});
if(value!=undefined) {
return value;
} else {
return chain;
}
};
$.fn.selectpicker.defaults = {
style: null,
size: 'auto',
title: null,
selectedTextFormat : 'values',
noneSelectedText : 'Nothing selected',
width: null,
menuStyle: null,
toggleSize: null
}
}(window.jQuery);

7
public/static/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

9
public/static/js/chartist.min.js vendored Normal file

File diff suppressed because one or more lines are too long

263
public/static/js/demo.js Normal file
View File

@ -0,0 +1,263 @@
type = ['','info','success','warning','danger'];
demo = {
initPickColor: function(){
$('.pick-class-label').click(function(){
var new_class = $(this).attr('new-class');
var old_class = $('#display-buttons').attr('data-class');
var display_div = $('#display-buttons');
if(display_div.length) {
var display_buttons = display_div.find('.btn');
display_buttons.removeClass(old_class);
display_buttons.addClass(new_class);
display_div.attr('data-class', new_class);
}
});
},
checkScrollForTransparentNavbar: debounce(function() {
$navbar = $('.navbar[color-on-scroll]');
scroll_distance = $navbar.attr('color-on-scroll') || 500;
if($(document).scrollTop() > scroll_distance ) {
if(transparent) {
transparent = false;
$('.navbar[color-on-scroll]').removeClass('navbar-transparent');
$('.navbar[color-on-scroll]').addClass('navbar-default');
}
} else {
if( !transparent ) {
transparent = true;
$('.navbar[color-on-scroll]').addClass('navbar-transparent');
$('.navbar[color-on-scroll]').removeClass('navbar-default');
}
}
}, 17),
initDocChartist: function(){
var dataSales = {
labels: ['9:00AM', '12:00AM', '3:00PM', '6:00PM', '9:00PM', '12:00PM', '3:00AM', '6:00AM'],
series: [
[287, 385, 490, 492, 554, 586, 698, 695, 752, 788, 846, 944],
[67, 152, 143, 240, 287, 335, 435, 437, 539, 542, 544, 647],
[23, 113, 67, 108, 190, 239, 307, 308, 439, 410, 410, 509]
]
};
var optionsSales = {
lineSmooth: false,
low: 0,
high: 800,
showArea: true,
height: "245px",
axisX: {
showGrid: false,
},
lineSmooth: Chartist.Interpolation.simple({
divisor: 3
}),
showLine: false,
showPoint: false,
};
var responsiveSales = [
['screen and (max-width: 640px)', {
axisX: {
labelInterpolationFnc: function (value) {
return value[0];
}
}
}]
];
Chartist.Line('#chartHours', dataSales, optionsSales, responsiveSales);
var data = {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
series: [
[542, 443, 320, 780, 553, 453, 326, 434, 568, 610, 756, 895],
[412, 243, 280, 580, 453, 353, 300, 364, 368, 410, 636, 695]
]
};
var options = {
seriesBarDistance: 10,
axisX: {
showGrid: false
},
height: "245px"
};
var responsiveOptions = [
['screen and (max-width: 640px)', {
seriesBarDistance: 5,
axisX: {
labelInterpolationFnc: function (value) {
return value[0];
}
}
}]
];
Chartist.Bar('#chartActivity', data, options, responsiveOptions);
var dataPreferences = {
series: [
[25, 30, 20, 25]
]
};
var optionsPreferences = {
donut: true,
donutWidth: 40,
startAngle: 0,
total: 100,
showLabel: false,
axisX: {
showGrid: false
}
};
Chartist.Pie('#chartPreferences', dataPreferences, optionsPreferences);
Chartist.Pie('#chartPreferences', {
labels: ['62%','32%','6%'],
series: [62, 32, 6]
});
},
initChartist: function(){
var dataSales = {
labels: ['9:00AM', '12:00AM', '3:00PM', '6:00PM', '9:00PM', '12:00PM', '3:00AM', '6:00AM'],
series: [
[287, 385, 490, 492, 554, 586, 698, 695, 752, 788, 846, 944],
[67, 152, 143, 240, 287, 335, 435, 437, 539, 542, 544, 647],
[23, 113, 67, 108, 190, 239, 307, 308, 439, 410, 410, 509]
]
};
var optionsSales = {
lineSmooth: false,
low: 0,
high: 800,
showArea: true,
height: "245px",
axisX: {
showGrid: false,
},
lineSmooth: Chartist.Interpolation.simple({
divisor: 3
}),
showLine: false,
showPoint: false,
};
var responsiveSales = [
['screen and (max-width: 640px)', {
axisX: {
labelInterpolationFnc: function (value) {
return value[0];
}
}
}]
];
Chartist.Line('#chartHours', dataSales, optionsSales, responsiveSales);
var data = {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
series: [
[542, 443, 320, 780, 553, 453, 326, 434, 568, 610, 756, 895],
[412, 243, 280, 580, 453, 353, 300, 364, 368, 410, 636, 695]
]
};
var options = {
seriesBarDistance: 10,
axisX: {
showGrid: false
},
height: "245px"
};
var responsiveOptions = [
['screen and (max-width: 640px)', {
seriesBarDistance: 5,
axisX: {
labelInterpolationFnc: function (value) {
return value[0];
}
}
}]
];
Chartist.Bar('#chartActivity', data, options, responsiveOptions);
var dataPreferences = {
series: [
[25, 30, 20, 25]
]
};
var optionsPreferences = {
donut: true,
donutWidth: 40,
startAngle: 0,
total: 100,
showLabel: false,
axisX: {
showGrid: false
}
};
Chartist.Pie('#chartPreferences', dataPreferences, optionsPreferences);
Chartist.Pie('#chartPreferences', {
labels: ['62%','32%','6%'],
series: [62, 32, 6]
});
},
initGoogleMaps: function(){
var myLatlng = new google.maps.LatLng(40.748817, -73.985428);
var mapOptions = {
zoom: 13,
center: myLatlng,
scrollwheel: false, //we disable de scroll over the map, it is a really annoing when you scroll through page
styles: [{"featureType":"water","stylers":[{"saturation":43},{"lightness":-11},{"hue":"#0088ff"}]},{"featureType":"road","elementType":"geometry.fill","stylers":[{"hue":"#ff0000"},{"saturation":-100},{"lightness":99}]},{"featureType":"road","elementType":"geometry.stroke","stylers":[{"color":"#808080"},{"lightness":54}]},{"featureType":"landscape.man_made","elementType":"geometry.fill","stylers":[{"color":"#ece2d9"}]},{"featureType":"poi.park","elementType":"geometry.fill","stylers":[{"color":"#ccdca1"}]},{"featureType":"road","elementType":"labels.text.fill","stylers":[{"color":"#767676"}]},{"featureType":"road","elementType":"labels.text.stroke","stylers":[{"color":"#ffffff"}]},{"featureType":"poi","stylers":[{"visibility":"off"}]},{"featureType":"landscape.natural","elementType":"geometry.fill","stylers":[{"visibility":"on"},{"color":"#b8cb93"}]},{"featureType":"poi.park","stylers":[{"visibility":"on"}]},{"featureType":"poi.sports_complex","stylers":[{"visibility":"on"}]},{"featureType":"poi.medical","stylers":[{"visibility":"on"}]},{"featureType":"poi.business","stylers":[{"visibility":"simplified"}]}]
}
var map = new google.maps.Map(document.getElementById("map"), mapOptions);
var marker = new google.maps.Marker({
position: myLatlng,
title:"Hello World!"
});
// To add the marker to the map, call setMap();
marker.setMap(map);
},
showNotification: function(from, align){
color = Math.floor((Math.random() * 4) + 1);
$.notify({
icon: "pe-7s-gift",
message: "Welcome to <b>Light Bootstrap Dashboard</b> - a beautiful freebie for every web developer."
},{
type: type[color],
timer: 4000,
placement: {
from: from,
align: align
}
});
}
}

4
public/static/js/jquery.3.2.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,154 @@
/*!
=========================================================
* Light Bootstrap Dashboard - v1.4.0
=========================================================
* Product Page: http://www.creative-tim.com/product/light-bootstrap-dashboard
* Copyright 2017 Creative Tim (http://www.creative-tim.com)
* Licensed under MIT (https://github.com/creativetimofficial/light-bootstrap-dashboard/blob/master/LICENSE.md)
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
var searchVisible = 0;
var transparent = true;
var transparentDemo = true;
var fixedTop = false;
var navbar_initialized = false;
$(document).ready(function(){
window_width = $(window).width();
// check if there is an image set for the sidebar's background
lbd.checkSidebarImage();
// Init navigation toggle for small screens
lbd.initRightMenu();
// Activate the tooltips
$('[rel="tooltip"]').tooltip();
$('.form-control').on("focus", function(){
$(this).parent('.input-group').addClass("input-group-focus");
}).on("blur", function(){
$(this).parent(".input-group").removeClass("input-group-focus");
});
// Fixes sub-nav not working as expected on IOS
$('body').on('touchstart.dropdown', '.dropdown-menu', function (e) { e.stopPropagation(); });
});
$(document).on('click', '.navbar-toggle', function(){
$toggle = $(this);
if(lbd.misc.navbar_menu_visible == 1) {
$('html').removeClass('nav-open');
lbd.misc.navbar_menu_visible = 0;
$('#bodyClick').remove();
setTimeout(function(){
$toggle.removeClass('toggled');
}, 550);
} else {
setTimeout(function(){
$toggle.addClass('toggled');
}, 580);
div = '<div id="bodyClick"></div>';
$(div).appendTo('body').click(function() {
$('html').removeClass('nav-open');
lbd.misc.navbar_menu_visible = 0;
setTimeout(function(){
$toggle.removeClass('toggled');
$('#bodyClick').remove();
}, 550);
});
$('html').addClass('nav-open');
lbd.misc.navbar_menu_visible = 1;
}
});
$(window).on('resize', function(){
if(navbar_initialized){
lbd.initRightMenu();
navbar_initialized = true;
}
});
lbd = {
misc:{
navbar_menu_visible: 0
},
checkSidebarImage: function(){
$sidebar = $('.sidebar');
image_src = $sidebar.data('image');
if(image_src !== undefined){
sidebar_container = '<div class="sidebar-background" style="background-image: url(' + image_src + ') "/>'
$sidebar.append(sidebar_container);
}
},
initRightMenu: debounce(function(){
if(!navbar_initialized){
$sidebar_wrapper = $('.sidebar-wrapper');
$navbar = $('nav').find('.navbar-collapse').html();
mobile_menu_content = '';
nav_content = $navbar;
nav_content = '<ul class="nav nav-mobile-menu">' + nav_content + '</ul>';
// navbar_form = $('nav').find('.navbar-form').get(0).outerHTML;
$sidebar_nav = $sidebar_wrapper.find(' > .nav');
// insert the navbar form before the sidebar list
$nav_content = $(nav_content);
// $navbar_form = $(navbar_form);
$nav_content.insertBefore($sidebar_nav);
// $navbar_form.insertBefore($nav_content);
$(".sidebar-wrapper .dropdown .dropdown-menu > li > a").click(function(event) {
event.stopPropagation();
});
mobile_menu_initialized = true;
} else {
if($(window).width() > 991){
// reset all the additions that we made for the sidebar wrapper only if the screen is bigger than 991px
// $sidebar_wrapper.find('.navbar-form').remove();
$sidebar_wrapper.find('.nav-mobile-menu').remove();
mobile_menu_initialized = false;
}
}
},200)
}
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
};

View File

@ -8,22 +8,59 @@
</div>
<div class="card-body">
<div class="card-body table-responsive">
<div id="accordion">
<div class="card">
<div class="card-header collapsed filterCard" id="addBlacklistHeader" data-toggle="collapse" data-target="#addBlacklist" aria-expanded="false" aria-controls="addBlacklist">
<span class="align-middle" data-toggle="collapse" data-target="#addBlacklist" aria-expanded="false" aria-controls="addBlacklist">
<i class="fas fa-plus"></i> Add New User
</span>
</div>
<div id="addBlacklist" class="collapse" aria-labelledby="addBlacklistHeader" data-parent="#accordion">
<div class="card-body">
<form>
<div class="row">
<div class="col-md-8 pr-1">
<div class="form-group">
<label>User ID</label>
<input name="userid" type="text" class="form-control" placeholder="User ID">
</div>
</div>
<div class="col-md-4 px-1">
<label></label>
<div class="alert alert-success" role="alert">
Click <a href="https://support.discordapp.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-">here</a> for a guide on how to retreive user IDs.
</div>
</div>
</div>
<input name="csrf" type="hidden" value="{{csrf}}">
<div class="row">
<div class="col-md-2">
<div class="form-group">
<button type="submit" class="btn btn-primary mx-auto"><i class="fas fa-paper-plane"></i> Submit</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Ticket ID</th>
<th>Username</th>
<th>User ID</th>
<th>Log URL</th>
<th>Username</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{{#blacklisted}}
<tr>
<td>{{ticketid}}</td>
<td>{{userId}}</td>
<td>{{username}}</td>
<td>{{userid}}</td>
<td><a href="{{baseUrl}}/manage/{{guildId}}/logs/view/{{uuid}}">{{uuid}}</a></td>
<td><a href="{{baseUrl}}/manage/{{guildId}}/blacklist/remove/{{userId}}?c={{csrf}}">Remove</a></td>
</tr>
{{/blacklisted}}
</tbody>
@ -34,4 +71,40 @@
</div>
</div>
</div>
<div aria-live="polite" aria-atomic="true" style="position: relative; min-height: 200px;">
<div style="position: absolute; right: 10px; min-width: 300px">
{{#userNotFound}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
The user you specified couldn't be found
</div>
</div>
{{/userNotFound}}
{{#isStaff}}
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-autohide="false">
<div class="toast-header">
<strong class="mr-auto">Warning</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
You cannot blacklist a staff member
</div>
</div>
{{/isStaff}}
</div>
</div>
<script>
$('.toast').toast('show');
</script>
</div>

View File

@ -33,7 +33,7 @@
</div>
</div>
<div class="row">
<div class="col-md-1">
<div class="col-md-2">
<div class="form-group">
<button type="submit" class="btn btn-primary mx-auto"><i class="fas fa-paper-plane"></i> Filter</button>
</div>