Add permissions editor; fixes for bans

This commit is contained in:
calzoneman 2013-05-22 15:38:16 -04:00
parent 12c1f4acf8
commit 4e364f45a6
8 changed files with 360 additions and 249 deletions

View File

@ -41,14 +41,31 @@ var Channel = function(name) {
this.openqueue = false; this.openqueue = false;
this.poll = false; this.poll = false;
this.voteskip = false; this.voteskip = false;
this.permissions = {
oplaylistadd: -1,
oplaylistnext: 1.5,
oplaylistmove: 1.5,
oplaylistdelete: 2,
oplaylistjump: 1.5,
playlistadd: 1.5,
playlistnext: 1.5,
playlistmove: 1.5,
playlistdelete: 2,
playlistjump: 1.5,
addnontemp: 2,
settemp: 2,
playlistgeturl: 1.5,
playlistshuffle: 2,
playlistclear: 2,
pollctl: 1.5,
pollvote: -1,
kick: 1.5,
ban: 2,
motdedit: 3,
filteredit: 3,
drink: 1.5
};
this.opts = { this.opts = {
qopen_allow_anon: false,
qopen_allow_guest: true,
qopen_allow_qnext: false,
qopen_allow_move: false,
qopen_allow_playnext: false,
qopen_allow_delete: false,
qopen_temp: true,
allow_voteskip: true, allow_voteskip: true,
voteskip_ratio: 0.5, voteskip_ratio: 0.5,
pagetitle: this.name, pagetitle: this.name,
@ -61,6 +78,7 @@ var Channel = function(name) {
new Filter("monospace", "`([^`]+)`", "g", "<code>$1</code>"), new Filter("monospace", "`([^`]+)`", "g", "<code>$1</code>"),
new Filter("bold", "(.)\\*([^\\*]+)\\*", "g", "$1<strong>$2</strong>"), new Filter("bold", "(.)\\*([^\\*]+)\\*", "g", "$1<strong>$2</strong>"),
new Filter("italic", "(^| )_([^_]+)_", "g", "$1<em>$2</em>"), new Filter("italic", "(^| )_([^_]+)_", "g", "$1<em>$2</em>"),
new Filter("strikethrough", "~~([^~]+)~~", "g", "<s>$1</s>"),
new Filter("inline spoiler", "\\[spoiler\\](.*)\\[\\/spoiler\\]", "ig", "<span class=\"spoiler\">$1</span>"), new Filter("inline spoiler", "\\[spoiler\\](.*)\\[\\/spoiler\\]", "ig", "<span class=\"spoiler\">$1</span>"),
]; ];
this.motd = { this.motd = {
@ -92,6 +110,23 @@ var Channel = function(name) {
} }
} }
/* REGION Permissions */
Channel.prototype.hasPermission = function(user, key) {
if(key.indexOf("playlist") == 0 && this.openqueue) {
var key2 = "o" + key;
var v = this.permissions[key2];
if(typeof v != "number") {
return false;
}
return user.rank >= v;
}
var v = this.permissions[key];
if(typeof v != "number") {
return false;
}
return user.rank >= v;
}
/* REGION Channel data */ /* REGION Channel data */
Channel.prototype.loadDump = function() { Channel.prototype.loadDump = function() {
fs.readFile("chandump/" + this.name, function(err, data) { fs.readFile("chandump/" + this.name, function(err, data) {
@ -132,6 +167,9 @@ Channel.prototype.loadDump = function() {
for(var key in data.opts) { for(var key in data.opts) {
this.opts[key] = data.opts[key]; this.opts[key] = data.opts[key];
} }
for(var key in data.permissions) {
this.permissions[key] = data.permissions[key];
}
this.broadcastOpts(); this.broadcastOpts();
if(data.filters) { if(data.filters) {
for(var i = 0; i < data.filters.length; i++) { for(var i = 0; i < data.filters.length; i++) {
@ -185,6 +223,7 @@ Channel.prototype.saveDump = function() {
currentTime: this.media ? this.media.currentTime : 0, currentTime: this.media ? this.media.currentTime : 0,
queue: this.queue, queue: this.queue,
opts: this.opts, opts: this.opts,
permissions: this.permissions,
filters: filts, filters: filts,
motd: this.motd, motd: this.motd,
logins: this.logins, logins: this.logins,
@ -297,7 +336,7 @@ Channel.prototype.cacheMedia = function(media) {
} }
Channel.prototype.banName = function(actor, name) { Channel.prototype.banName = function(actor, name) {
if(!Rank.hasPermission(actor, "ban")) { if(!this.hasPermission(actor, "ban")) {
return false; return false;
} }
@ -334,7 +373,7 @@ Channel.prototype.banName = function(actor, name) {
} }
Channel.prototype.unbanName = function(actor, name) { Channel.prototype.unbanName = function(actor, name) {
if(!Rank.hasPermission(actor, "ban")) { if(!this.hasPermission(actor, "ban")) {
return false; return false;
} }
@ -347,7 +386,7 @@ Channel.prototype.unbanName = function(actor, name) {
} }
Channel.prototype.tryIPBan = function(actor, data) { Channel.prototype.tryIPBan = function(actor, data) {
if(!Rank.hasPermission(actor, "ipban")) { if(!this.hasPermission(actor, "ban")) {
return false; return false;
} }
if(typeof data.id != "string" || data.id.length != 15) { if(typeof data.id != "string" || data.id.length != 15) {
@ -393,7 +432,7 @@ Channel.prototype.tryIPBan = function(actor, data) {
} }
Channel.prototype.banIP = function(actor, receiver) { Channel.prototype.banIP = function(actor, receiver) {
if(!Rank.hasPermission(actor, "ipban")) if(!this.hasPermission(actor, "ban"))
return false; return false;
this.ipbans[receiver.ip] = [receiver.name, actor.name]; this.ipbans[receiver.ip] = [receiver.name, actor.name];
@ -414,13 +453,9 @@ Channel.prototype.banIP = function(actor, receiver) {
} }
Channel.prototype.unbanIP = function(actor, ip) { Channel.prototype.unbanIP = function(actor, ip) {
if(!Rank.hasPermission(actor, "ipban")) if(!this.hasPermission(actor, "ban"))
return false; return false;
if(this.getIPRank(ip) >= actor.rank) {
return false;
}
this.ipbans[ip] = null; this.ipbans[ip] = null;
if(!this.registered) if(!this.registered)
@ -513,7 +548,7 @@ Channel.prototype.userJoin = function(user) {
// If the channel is empty and isn't registered, the first person // If the channel is empty and isn't registered, the first person
// gets ownership of the channel (temporarily) // gets ownership of the channel (temporarily)
if(this.users.length == 0 && !this.registered) { if(this.users.length == 0 && !this.registered) {
user.rank = (user.rank < Rank.Owner) ? Rank.Owner + 7 : user.rank; user.rank = (user.rank < Rank.Owner) ? 10 : user.rank;
user.socket.emit("channelNotRegistered"); user.socket.emit("channelNotRegistered");
} }
this.users.push(user); this.users.push(user);
@ -534,6 +569,7 @@ Channel.prototype.userJoin = function(user) {
user.socket.emit("newPoll", this.poll.packUpdate()); user.socket.emit("newPoll", this.poll.packUpdate());
} }
user.socket.emit("channelOpts", this.opts); user.socket.emit("channelOpts", this.opts);
user.socket.emit("setPermissions", this.permissions);
user.socket.emit("updateMotd", this.motd); user.socket.emit("updateMotd", this.motd);
user.socket.emit("drinkCount", {count: this.drinks}); user.socket.emit("drinkCount", {count: this.drinks});
@ -609,7 +645,7 @@ Channel.prototype.hideIP = function(ip) {
} }
Channel.prototype.sendRankStuff = function(user) { Channel.prototype.sendRankStuff = function(user) {
if(Rank.hasPermission(user, "ipban")) { if(this.hasPermission(user, "ban")) {
var ents = []; var ents = [];
for(var ip in this.ipbans) { for(var ip in this.ipbans) {
if(this.ipbans[ip] != null) { if(this.ipbans[ip] != null) {
@ -651,16 +687,19 @@ Channel.prototype.sendRankStuff = function(user) {
if(user.rank < Rank.Siteadmin) { if(user.rank < Rank.Siteadmin) {
disp = "(Hidden)"; disp = "(Hidden)";
} }
var banned = (ip in this.ipbans && this.ipbans[ip] != null);
var range = ip.replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)/, "$1.$2.$3");
banned = banned || (range in this.ipbans && this.ipbans[range] != null);
ents.push({ ents.push({
ip: disp, ip: disp,
id: this.hideIP(ip), id: this.hideIP(ip),
names: this.logins[ip], names: this.logins[ip],
banned: (ip in this.ipbans && this.ipbans[ip] != null) banned: banned
}); });
} }
user.socket.emit("seenlogins", {entries: ents}); user.socket.emit("seenlogins", {entries: ents});
} }
if(Rank.hasPermission(user, "chatFilter")) { if(this.hasPermission(user, "filteredit")) {
var filts = new Array(this.filters.length); var filts = new Array(this.filters.length);
for(var i = 0; i < this.filters.length; i++) { for(var i = 0; i < this.filters.length; i++) {
filts[i] = this.filters[i].pack(); filts[i] = this.filters[i].pack();
@ -833,7 +872,7 @@ Channel.prototype.broadcastBanlist = function() {
} }
} }
for(var i = 0; i < this.users.length; i++) { for(var i = 0; i < this.users.length; i++) {
if(Rank.hasPermission(this.users[i], "ipban")) { if(this.hasPermission(this.users[i], "ban")) {
if(this.users[i].rank >= Rank.Siteadmin) { if(this.users[i].rank >= Rank.Siteadmin) {
this.users[i].socket.emit("banlist", {entries: adminents}); this.users[i].socket.emit("banlist", {entries: adminents});
} }
@ -857,7 +896,7 @@ Channel.prototype.broadcastChatFilters = function() {
filts[i] = this.filters[i].pack(); filts[i] = this.filters[i].pack();
} }
for(var i = 0; i < this.users.length; i++) { for(var i = 0; i < this.users.length; i++) {
if(Rank.hasPermission(this.users[i], "chatFilter")) { if(this.hasPermission(this.users[i], "filteredit")) {
this.users[i].socket.emit("chatFilters", {filters: filts}); this.users[i].socket.emit("chatFilters", {filters: filts});
} }
} }
@ -938,7 +977,7 @@ Channel.prototype.autoTemp = function(media, user) {
if(isLive(media.type)) { if(isLive(media.type)) {
media.temp = true; media.temp = true;
} }
if(user.rank < Rank.Moderator && this.opts.qopen_temp) { if(!this.hasPermission(user, "addnontemp")) {
media.temp = true; media.temp = true;
} }
} }
@ -1019,14 +1058,7 @@ Channel.prototype.enqueue = function(data, user) {
} }
Channel.prototype.tryQueue = function(user, data) { Channel.prototype.tryQueue = function(user, data) {
var anon = (user.name == ""); if(!this.hasPermission(user, "playlistadd")) {
var guest = (user.name != "" && !user.loggedIn);
var canqueue = (anon && this.opts.qopen_allow_anon) ||
(guest && this.opts.qopen_allow_guest) ||
(!anon && !guest);
canqueue = canqueue && this.openqueue;
canqueue = canqueue || this.leader == user || Rank.hasPermission(user, "queue");
if(!canqueue) {
return; return;
} }
if(data.pos == undefined || data.id == undefined) { if(data.pos == undefined || data.id == undefined) {
@ -1036,14 +1068,12 @@ Channel.prototype.tryQueue = function(user, data) {
return; return;
} }
if(data.pos == "next" && !Rank.hasPermission(user, "queue") && if(data.pos == "next" && !this.hasPermission(user, "playlistnext")) {
this.leader != user &&
!this.opts.qopen_allow_qnext) {
return; return;
} }
if(user.rank < Rank.Moderator if(user.rank < Rank.Moderator
&& this.leader != user && this.leader != user
&& user.noflood("queue", 1.5)) { && user.noflood("queue", 1.5)) {
return; return;
} }
@ -1065,7 +1095,7 @@ Channel.prototype.setTemp = function(idx, temp) {
} }
Channel.prototype.trySetTemp = function(user, data) { Channel.prototype.trySetTemp = function(user, data) {
if(!Rank.hasPermission(user, "settemp")) { if(!this.hasPermission(user, "settemp")) {
return; return;
} }
if(typeof data.idx != "number" || typeof data.temp != "boolean") { if(typeof data.idx != "number" || typeof data.temp != "boolean") {
@ -1103,14 +1133,11 @@ Channel.prototype.dequeue = function(data) {
} }
Channel.prototype.tryDequeue = function(user, data) { Channel.prototype.tryDequeue = function(user, data) {
if(!Rank.hasPermission(user, "queue") && if(!this.hasPermission(user, "playlistdelete")) {
this.leader != user &&
(!this.openqueue ||
this.openqueue && !this.opts.qopen_allow_delete)) {
return; return;
} }
if(data.pos == undefined) { if(data.pos === undefined) {
return; return;
} }
@ -1135,10 +1162,7 @@ Channel.prototype.playNext = function() {
} }
Channel.prototype.tryPlayNext = function(user) { Channel.prototype.tryPlayNext = function(user) {
if(!Rank.hasPermission(user, "queue") && if(!this.hasPermission(user, "playlistjump")) {
this.leader != user &&
(!this.openqueue ||
this.openqueue && !this.opts.qopen_allow_playnext)) {
return; return;
} }
@ -1192,14 +1216,11 @@ Channel.prototype.jumpTo = function(pos) {
} }
Channel.prototype.tryJumpTo = function(user, data) { Channel.prototype.tryJumpTo = function(user, data) {
if(!Rank.hasPermission(user, "queue") && if(!this.hasPermission(user, "playlistjump")) {
this.leader != user &&
(!this.openqueue ||
this.openqueue && !this.opts.qopen_allow_playnext)) {
return; return;
} }
if(data.pos == undefined) { if(data.pos === undefined) {
return; return;
} }
@ -1215,7 +1236,7 @@ Channel.prototype.clearqueue = function() {
} }
Channel.prototype.tryClearqueue = function(user) { Channel.prototype.tryClearqueue = function(user) {
if(!Rank.hasPermission(user, "queue")) { if(!this.hasPermission(user, "playlistclear")) {
return; return;
} }
this.clearqueue(); this.clearqueue();
@ -1240,7 +1261,7 @@ Channel.prototype.shufflequeue = function() {
} }
Channel.prototype.tryShufflequeue = function(user) { Channel.prototype.tryShufflequeue = function(user) {
if(!Rank.hasPermission(user, "queue")) { if(!this.hasPermission(user, "playlistshuffle")) {
return; return;
} }
this.shufflequeue(); this.shufflequeue();
@ -1305,12 +1326,9 @@ Channel.prototype.move = function(data) {
} }
Channel.prototype.tryMove = function(user, data) { Channel.prototype.tryMove = function(user, data) {
if(!Rank.hasPermission(user, "queue") && if(!this.hasPermission(user, "playlistmove")) {
this.leader != user &&
(!this.openqueue ||
this.openqueue && !this.opts.qopen_allow_move)) {
return; return;
} }
if(data.src == undefined || data.dest == undefined) { if(data.src == undefined || data.dest == undefined) {
return; return;
@ -1322,7 +1340,7 @@ Channel.prototype.tryMove = function(user, data) {
/* REGION Polls */ /* REGION Polls */
Channel.prototype.tryOpenPoll = function(user, data) { Channel.prototype.tryOpenPoll = function(user, data) {
if(!Rank.hasPermission(user, "poll") && this.leader != user) { if(!this.hasPermission(user, "pollctl") && this.leader != user) {
return; return;
} }
@ -1337,7 +1355,7 @@ Channel.prototype.tryOpenPoll = function(user, data) {
} }
Channel.prototype.tryClosePoll = function(user) { Channel.prototype.tryClosePoll = function(user) {
if(!Rank.hasPermission(user, "poll") && this.leader != user) { if(!this.hasPermission(user, "pollctl")) {
return; return;
} }
@ -1348,6 +1366,10 @@ Channel.prototype.tryClosePoll = function(user) {
} }
Channel.prototype.tryVote = function(user, data) { Channel.prototype.tryVote = function(user, data) {
if(!this.hasPermission(user, "pollvote")) {
return;
}
if(data.option == undefined) { if(data.option == undefined) {
return; return;
} }
@ -1419,7 +1441,7 @@ Channel.prototype.removeFilter = function(name, source) {
} }
Channel.prototype.tryChangeFilter = function(user, data) { Channel.prototype.tryChangeFilter = function(user, data) {
if(!Rank.hasPermission(user, "chatFilter")) { if(!this.hasPermission(user, "filteredit")) {
return; return;
} }
@ -1448,6 +1470,16 @@ Channel.prototype.tryChangeFilter = function(user, data) {
} }
} }
Channel.prototype.tryUpdatePermissions = function(user, perms) {
if(!Rank.hasPermission(user, "channelperms")) {
return;
}
for(var key in perms) {
this.permissions[key] = perms[key];
}
this.sendAll("setPermissions", this.permissions);
}
Channel.prototype.tryUpdateOptions = function(user, data) { Channel.prototype.tryUpdateOptions = function(user, data) {
if(!Rank.hasPermission(user, "channelOpts")) { if(!Rank.hasPermission(user, "channelOpts")) {
return; return;
@ -1515,7 +1547,7 @@ Channel.prototype.updateMotd = function(motd) {
} }
Channel.prototype.tryUpdateMotd = function(user, data) { Channel.prototype.tryUpdateMotd = function(user, data) {
if(!Rank.hasPermission(user, "updateMotd")) { if(!this.hasPermission(user, "motdedit")) {
return; return;
} }
@ -1683,6 +1715,9 @@ Channel.prototype.changeLeader = function(name) {
if(this.leader != null) { if(this.leader != null) {
var old = this.leader; var old = this.leader;
this.leader = null; this.leader = null;
if(old.rank == 1.5) {
old.rank = old.oldrank;
}
this.broadcastUserUpdate(old); this.broadcastUserUpdate(old);
} }
if(name == "") { if(name == "") {
@ -1698,6 +1733,10 @@ Channel.prototype.changeLeader = function(name) {
if(this.users[i].name == name) { if(this.users[i].name == name) {
this.logger.log("*** Assigned leader: " + name); this.logger.log("*** Assigned leader: " + name);
this.leader = this.users[i]; this.leader = this.users[i];
if(this.users[i].rank < 1.5) {
this.users[i].oldrank = this.users[i].rank;
this.users[i].rank = 1.5;
}
this.broadcastUserUpdate(this.leader); this.broadcastUserUpdate(this.leader);
} }
} }

View File

@ -38,6 +38,9 @@ function handle(chan, user, msg, data) {
else if(msg.indexOf("/ban ") == 0) { else if(msg.indexOf("/ban ") == 0) {
handleBan(chan, user, msg.substring(5).split(" ")); handleBan(chan, user, msg.substring(5).split(" "));
} }
else if(msg.indexOf("/ipban ") == 0) {
handleIPBan(chan, user, msg.substring(5).split(" "));
}
else if(msg.indexOf("/unban ") == 0) { else if(msg.indexOf("/unban ") == 0) {
handleUnban(chan, user, msg.substring(7).split(" ")); handleUnban(chan, user, msg.substring(7).split(" "));
} }
@ -51,7 +54,7 @@ function handle(chan, user, msg, data) {
} }
function handleKick(chan, user, args) { function handleKick(chan, user, args) {
if(Rank.hasPermission(user, "kick") && args.length > 0) { if(chan.hasPermission(user, "kick") && args.length > 0) {
args[0] = args[0].toLowerCase(); args[0] = args[0].toLowerCase();
var kickee; var kickee;
for(var i = 0; i < chan.users.length; i++) { for(var i = 0; i < chan.users.length; i++) {
@ -69,8 +72,8 @@ function handleKick(chan, user, args) {
} }
} }
function handleBan(chan, user, args) { function handleIPBan(chan, user, args) {
if(Rank.hasPermission(user, "ipban") && args.length > 0) { if(chan.hasPermission(user, "ban") && args.length > 0) {
args[0] = args[0].toLowerCase(); args[0] = args[0].toLowerCase();
var kickee; var kickee;
for(var i = 0; i < chan.users.length; i++) { for(var i = 0; i < chan.users.length; i++) {
@ -89,15 +92,27 @@ function handleBan(chan, user, args) {
} }
} }
function handleBan(chan, user, args) {
if(chan.hasPermission(user, "ban") && args.length > 0) {
args[0] = args[0].toLowerCase();
chan.banName(user, args[0]);
}
}
function handleUnban(chan, user, args) { function handleUnban(chan, user, args) {
if(Rank.hasPermission(user, "ipban") && args.length > 0) { if(chan.hasPermission(user, "ban") && args.length > 0) {
chan.logger.log("*** " + user.name + " unbanned " + args[0]); chan.logger.log("*** " + user.name + " unbanned " + args[0]);
chan.unbanIP(user, args[0]); if(args[0].match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/)) {
chan.unbanIP(user, args[0]);
}
else {
chan.unbanName(user, args[0]);
}
} }
} }
function handlePoll(chan, user, msg) { function handlePoll(chan, user, msg) {
if(Rank.hasPermission(user, "poll") || chan.leader == user) { if(chan.hasPermission(user, "poll")) {
var args = msg.split(","); var args = msg.split(",");
var title = args[0]; var title = args[0];
args.splice(0, 1); args.splice(0, 1);
@ -109,7 +124,7 @@ function handlePoll(chan, user, msg) {
} }
function handleDrink(chan, user, msg, data) { function handleDrink(chan, user, msg, data) {
if(!Rank.hasPermission(user, "drink") && chan.leader != user) { if(!chan.hasPermission(user, "drink")) {
return; return;
} }

View File

@ -9,6 +9,7 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
exports.Anonymous = -1;
exports.Guest = 0; exports.Guest = 0;
exports.Member = 1; exports.Member = 1;
exports.Moderator = 2; exports.Moderator = 2;
@ -23,6 +24,7 @@ var permissions = {
chatFilter : exports.Owner, chatFilter : exports.Owner,
setcss : exports.Owner, setcss : exports.Owner,
setjs : exports.Owner, setjs : exports.Owner,
channelperms : exports.Owner,
queue : exports.Moderator, queue : exports.Moderator,
assignLeader : exports.Moderator, assignLeader : exports.Moderator,
kick : exports.Moderator, kick : exports.Moderator,

View File

@ -22,7 +22,7 @@ var User = function(socket, ip) {
this.ip = ip; this.ip = ip;
this.socket = socket; this.socket = socket;
this.loggedIn = false; this.loggedIn = false;
this.rank = Rank.Guest; this.rank = Rank.Anonymous;
this.channel = null; this.channel = null;
this.name = ""; this.name = "";
this.meta = { this.meta = {
@ -346,6 +346,12 @@ User.prototype.initCallbacks = function() {
} }
}.bind(this)); }.bind(this));
this.socket.on("setPermissions", function(data) {
if(this.channel != null) {
this.channel.tryUpdatePermissions(this, data);
}
}.bind(this));
this.socket.on("setChannelCSS", function(data) { this.socket.on("setChannelCSS", function(data) {
if(this.channel != null) { if(this.channel != null) {
this.channel.trySetCSS(this, data); this.channel.trySetCSS(this, data);
@ -474,6 +480,7 @@ User.prototype.login = function(name, pw, session) {
} }
else { else {
lastguestlogin[this.ip] = Date.now(); lastguestlogin[this.ip] = Date.now();
this.rank = Rank.Guest;
Logger.syslog.log(this.ip + " signed in as " + name); Logger.syslog.log(this.ip + " signed in as " + name);
this.name = name; this.name = name;
this.loggedIn = false; this.loggedIn = false;

View File

@ -232,7 +232,13 @@ Callbacks = {
$("#voteskip").attr("disabled", false); $("#voteskip").attr("disabled", false);
else else
$("#voteskip").attr("disabled", true); $("#voteskip").attr("disabled", true);
handleRankChange(); handlePermissionChange();
},
setPermissions: function(perms) {
CHANPERMS = perms;
genPermissionsEditor();
handlePermissionChange();
}, },
channelCSSJS: function(data) { channelCSSJS: function(data) {
@ -428,7 +434,7 @@ Callbacks = {
rank: function(data) { rank: function(data) {
RANK = data.rank; RANK = data.rank;
handleRankChange(); handlePermissionChange();
}, },
register: function(data) { register: function(data) {
@ -502,7 +508,7 @@ Callbacks = {
PROFILE.image = data.profile.image; PROFILE.image = data.profile.image;
LEADER = data.leader; LEADER = data.leader;
RANK = data.rank; RANK = data.rank;
handleRankChange(); handlePermissionChange();
if(LEADER) { if(LEADER) {
// I'm a leader! Set up sync function // I'm a leader! Set up sync function
sendVideoUpdate = function() { sendVideoUpdate = function() {

View File

@ -14,9 +14,10 @@ var LEADER = false;
var PLAYER = new Media(); var PLAYER = new Media();
var MEDIATYPE = "null"; var MEDIATYPE = "null";
var POSITION = -1; var POSITION = -1;
var RANK = 0; var RANK = -1;
var OPENQUEUE = false; var OPENQUEUE = false;
var CHANNELOPTS = {}; var CHANNELOPTS = {};
var CHANPERMS = {};
var GRABBEDLI = null; var GRABBEDLI = null;
var OLDINDEX = -1; var OLDINDEX = -1;
var CHATHIST = []; var CHATHIST = [];
@ -346,13 +347,6 @@ $("#opt_submit").click(function() {
var css = $("#opt_customcss").val(); var css = $("#opt_customcss").val();
var ratio = +$("#opt_voteskip_ratio").val() || 0.5; var ratio = +$("#opt_voteskip_ratio").val() || 0.5;
opts = { opts = {
qopen_allow_anon: $("#opt_qopen_allow_anon").prop("checked"),
qopen_allow_guest: $("#opt_qopen_allow_guest").prop("checked"),
qopen_allow_qnext: $("#opt_qopen_allow_qnext").prop("checked"),
qopen_allow_move: $("#opt_qopen_allow_move").prop("checked"),
qopen_allow_delete: $("#opt_qopen_allow_delete").prop("checked"),
qopen_allow_playnext: $("#opt_qopen_allow_playnext").prop("checked"),
qopen_temp: $("#opt_qopen_temp").prop("checked"),
allow_voteskip: $("#opt_allow_voteskip").prop("checked"), allow_voteskip: $("#opt_allow_voteskip").prop("checked"),
voteskip_ratio: ratio, voteskip_ratio: ratio,
pagetitle: ptitle, pagetitle: ptitle,
@ -380,6 +374,15 @@ $("#show_chancontrols").click(function() {
$("#chancontrols").show(); $("#chancontrols").show();
}); });
$("#show_chanperms").click(function() {
$("#modnav li").each(function() {
$(this).removeClass("active");
});
$(".modonly").hide();
$("#show_chanperms").parent().addClass("active");
$("#chanperms").show();
});
$("#show_banlist").click(function() { $("#show_banlist").click(function() {
$("#modnav li").each(function() { $("#modnav li").each(function() {
$(this).removeClass("active"); $(this).removeClass("active");
@ -439,17 +442,12 @@ $("#updatejs").click(function() {
}); });
$("#show_filtereditor").click(function() { $("#show_filtereditor").click(function() {
if(RANK >= Rank.Owner) { $("#modnav li").each(function() {
$("#modnav li").each(function() { $(this).removeClass("active");
$(this).removeClass("active"); });
}); $(".modonly").hide();
$(".modonly").hide(); $("#show_filtereditor").parent().addClass("active");
$("#show_filtereditor").parent().addClass("active"); $("#filtereditor").show();
$("#filtereditor").show();
}
else {
alert("Only channel administrators can change filters");
}
}); });
$("#show_acl").click(function() { $("#show_acl").click(function() {

View File

@ -93,6 +93,17 @@ function addUserDropdown(entry, name) {
} }
}.bind(a)); }.bind(a));
if(hasPermission("kick")) {
var kick = $("<li />").appendTo(ul);
var a = $("<a />").attr("tabindex", "-1").attr("href", "javascript:void(0);").appendTo(kick);
a.text("Kick");
a.click(function() {
socket.emit("chatMsg", {
msg: "/kick " + name
});
});
}
if(RANK >= Rank.Moderator) { if(RANK >= Rank.Moderator) {
$("<li />").addClass("divider").appendTo(ul); $("<li />").addClass("divider").appendTo(ul);
@ -114,15 +125,6 @@ function addUserDropdown(entry, name) {
}); });
}); });
var kick = $("<li />").appendTo(ul);
var a = $("<a />").attr("tabindex", "-1").attr("href", "javascript:void(0);").appendTo(kick);
a.text("Kick");
a.click(function() {
socket.emit("chatMsg", {
msg: "/kick " + name
});
});
var ban = $("<li />").appendTo(ul); var ban = $("<li />").appendTo(ul);
var a = $("<a />").attr("tabindex", "-1").attr("href", "javascript:void(0);").appendTo(ban); var a = $("<a />").attr("tabindex", "-1").attr("href", "javascript:void(0);").appendTo(ban);
a.text("IP Ban"); a.text("IP Ban");
@ -295,19 +297,9 @@ function makeQueueEntry(video) {
// Add buttons to a queue list entry // Add buttons to a queue list entry
function addQueueButtons(li) { function addQueueButtons(li) {
if(RANK < Rank.Moderator && !LEADER) {
if(!CHANNELOPTS.qopen_allow_delete
&& !CHANNELOPTS.qopen_allow_move
&& !CHANNELOPTS.qopen_allow_qnext) {
return;
}
}
var fullperms = LEADER || RANK >= Rank.Moderator;
var btnstrip = $("<div />").attr("class", "btn-group qe_buttons").prependTo(li); var btnstrip = $("<div />").attr("class", "btn-group qe_buttons").prependTo(li);
if(CHANNELOPTS.qopen_allow_move || fullperms) { if(hasPermission("playlistmove")) {
var btnMove = $("<button />").addClass("btn qe_btn").appendTo(btnstrip); var btnMove = $("<button />").addClass("btn qe_btn").appendTo(btnstrip);
$("<i />").addClass("icon-resize-vertical").appendTo(btnMove); $("<i />").addClass("icon-resize-vertical").appendTo(btnMove);
// Callback time // Callback time
@ -326,7 +318,7 @@ function addQueueButtons(li) {
}); });
} }
if(CHANNELOPTS.qopen_allow_delete || fullperms) { if(hasPermission("playlistdelete")) {
var btnRemove = $("<button />").attr("class", "btn btn-danger qe_btn").appendTo(btnstrip); var btnRemove = $("<button />").attr("class", "btn btn-danger qe_btn").appendTo(btnstrip);
$("<i />").attr("class", "icon-remove").appendTo(btnRemove); $("<i />").attr("class", "icon-remove").appendTo(btnRemove);
$(btnRemove).click(function() { $(btnRemove).click(function() {
@ -336,7 +328,7 @@ function addQueueButtons(li) {
}); });
} }
if(CHANNELOPTS.qopen_allow_playnext || fullperms) { if(hasPermission("playlistjump")) {
var btnPlay = $("<button />").attr("class", "btn btn-success qe_btn").appendTo(btnstrip); var btnPlay = $("<button />").attr("class", "btn btn-success qe_btn").appendTo(btnstrip);
$("<i />").attr("class", "icon-play").appendTo(btnPlay); $("<i />").attr("class", "icon-play").appendTo(btnPlay);
$(btnPlay).click(function() { $(btnPlay).click(function() {
@ -347,7 +339,7 @@ function addQueueButtons(li) {
}); });
} }
if(CHANNELOPTS.qopen_allow_qnext || fullperms) { if(hasPermission("playlistnext")) {
var btnNext = $("<button />").attr("class", "btn qe_btn").appendTo(btnstrip); var btnNext = $("<button />").attr("class", "btn qe_btn").appendTo(btnstrip);
$("<i/>").addClass("icon-share-alt").appendTo(btnNext); $("<i/>").addClass("icon-share-alt").appendTo(btnNext);
$(btnNext).click(function() { $(btnNext).click(function() {
@ -360,7 +352,7 @@ function addQueueButtons(li) {
}); });
} }
if(RANK >= Rank.Moderator) { if(hasPermission("settemp")) {
var btnTemp = $("<button />").attr("class", "btn qe_btn").appendTo(btnstrip); var btnTemp = $("<button />").attr("class", "btn qe_btn").appendTo(btnstrip);
var temp = $(li).hasClass("alert-error"); var temp = $(li).hasClass("alert-error");
$("<i/>").addClass("icon-flag").appendTo(btnTemp); $("<i/>").addClass("icon-flag").appendTo(btnTemp);
@ -396,8 +388,7 @@ function addQueueButtons(li) {
function rebuildPlaylist() { function rebuildPlaylist() {
$("#queue li").each(function() { $("#queue li").each(function() {
$(this).find(".btn-group").remove(); $(this).find(".btn-group").remove();
if(RANK >= Rank.Moderator || LEADER || canQueue()) addQueueButtons(this);
addQueueButtons(this);
}); });
} }
@ -405,7 +396,7 @@ function rebuildPlaylist() {
function addLibraryButtons(li, id, yt) { function addLibraryButtons(li, id, yt) {
var btnstrip = $("<div />").attr("class", "btn-group qe_buttons").prependTo(li); var btnstrip = $("<div />").attr("class", "btn-group qe_buttons").prependTo(li);
if(RANK >= Rank.Moderator || LEADER || (OPENQUEUE && CHANNELOPTS.qopen_allow_qnext)) { if(hasPermission("playlistnext")) {
var btnNext = $("<button />").addClass("btn qe_btn") var btnNext = $("<button />").addClass("btn qe_btn")
.text("Next") .text("Next")
.appendTo(btnstrip); .appendTo(btnstrip);
@ -427,20 +418,6 @@ function addLibraryButtons(li, id, yt) {
} }
var btnEnd = $("<button />").addClass("btn qe_btn").text("End").appendTo(btnstrip); var btnEnd = $("<button />").addClass("btn qe_btn").text("End").appendTo(btnstrip);
if(RANK >= Rank.Moderator) {
var btnDelete = $("<button/>").addClass("btn qe_btn btn-danger").appendTo(btnstrip);
$("<i/>").addClass("icon-remove").appendTo(btnDelete);
btnDelete.click(function() {
socket.emit("uncache", {
id: id
});
$(li).hide("blind", function() {
$(li).remove();
});
});
}
btnEnd.click(function() { btnEnd.click(function() {
if(yt) { if(yt) {
socket.emit("queue", { socket.emit("queue", {
@ -457,6 +434,18 @@ function addLibraryButtons(li, id, yt) {
} }
}); });
if(RANK >= Rank.Moderator) {
var btnDelete = $("<button/>").addClass("btn qe_btn btn-danger").appendTo(btnstrip);
$("<i/>").addClass("icon-remove").appendTo(btnDelete);
btnDelete.click(function() {
socket.emit("uncache", {
id: id
});
$(li).hide("blind", function() {
$(li).remove();
});
});
}
} }
// Rearranges the queue // Rearranges the queue
@ -618,85 +607,59 @@ function canQueue() {
return canqueue; return canqueue;
} }
function handleRankChange() { function handlePermissionChange() {
rebuildPlaylist(); function setVisible(selector, bool) {
if(RANK >= 10) { var disp = bool ? "" : "none";
$("#drop_channel").parent().css("display", ""); $(selector).css("display", disp);
} }
if(RANK >= Rank.Owner) {
$("#show_jseditor").parent().css("display", "");
$("#show_csseditor").parent().css("display", "");
}
else {
$("#show_jseditor").parent().css("display", "none");
$("#show_csseditor").parent().css("display", "none");
}
if(RANK >= Rank.Moderator) {
$("#qlockbtn").css("display", "block");
var users = $("#userlist").children();
for(var i = 0; i < users.length; i++) {
addUserDropdown(users[i], users[i].children[1].innerHTML);
}
$("#getplaylist").css("width", "34%");
$("#clearplaylist").css("display", "");
$("#shuffleplaylist").css("display", "");
$("#modnav").show();
$("#chancontrols").show();
var val = false;
if(RANK < Rank.Owner) {
val = "disabled";
}
$("#opt_pagetitle").attr("disabled", val);
$("#opt_customcss").attr("disabled", val);
$("#opt_customjs").attr("disabled", val);
$("#opt_show_public").attr("disabled", val);
$("#show_filtereditor").attr("disabled", val);
$("#show_acl").attr("disabled", val);
}
else {
if(!LEADER) {
if(canQueue()) {
$("#playlist_controls").css("display", "");
if(CHANNELOPTS.qopen_allow_qnext)
$("#queue_next").attr("disabled", false);
else
$("#queue_next").attr("disabled", true);
if(CHANNELOPTS.qopen_allow_playnext)
$("#play_next").attr("disabled", false);
else
$("#play_next").attr("disabled", true);
}
else {
$("#playlist_controls").css("display", "none");
}
$("#pollcontainer .active").each(function() { if(RANK < 2) {
$(this).find(".btn-danger").remove(); $(".modonly").hide();
}); }
setVisible("#playlist_controls", hasPermission("playlistadd"));
$("#queue_next").attr("disabled", !hasPermission("playlistnext"));
setVisible("#qlockbtn", RANK >= 2);
setVisible("#getplaylist", hasPermission("playlistgeturl"));
setVisible("#clearplaylist", hasPermission("playlistclear"));
setVisible("#shuffleplaylist", hasPermission("playlistshuffle"));
setVisible("#modnav", RANK >= 2);
setVisible("#chanperms_tab", RANK >= 3);
setVisible("#banlist_tab", hasPermission("ban"));
setVisible("#motdeditor_tab", hasPermission("motdedit"));
setVisible("#csseditor_tab", RANK >= 3);
setVisible("#jseditor_tab", RANK >= 3);
setVisible("#filtereditor_tab", hasPermission("filteredit"));
setVisible("#acl_tab", RANK >= 3);
setVisible("#dropchannel_tab", RANK >= 10);
$("#pollcontainer .active").find(".btn-danger").remove();
if(hasPermission("pollctl")) {
var poll = $("#pollcontainer .active");
if(poll.length > 0) {
$("<button/>").addClass("btn btn-danger pull-right")
.text("End Poll")
.insertAfter(poll.find(".close"))
.click(function() {
socket.emit("closePoll");
});
} }
$("#getplaylist").css("width", "100%");
$("#clearplaylist").css("display", "none");
$("#shuffleplaylist").css("display", "none");
} }
if(RANK >= Rank.Moderator || LEADER) { var poll = $("#pollcontainer .active");
$("#playlist_controls").css("display", "block"); if(poll.length > 0) {
$("#playlist_controls button").each(function() { poll.find(".btn").attr("disabled", hasPermission("pollvote"));
$(this).attr("disabled", false);
});
$("#pollcontainer .active").each(function() {
var btns = $(this).find(".btn-danger");
if(btns.length == 0) {
$("<button/>").addClass("btn btn-danger pull-right")
.text("End Poll")
.insertAfter($(this).find(".close"))
.click(function() {
socket.emit("closePoll")
});
}
});
} }
var users = $("#userlist").children();
for(var i = 0; i < users.length; i++) {
addUserDropdown(users[i], users[i].children[1].innerHTML);
}
rebuildPlaylist();
} }
function onWindowFocus() { function onWindowFocus() {
clearInterval(TITLE_BLINK); clearInterval(TITLE_BLINK);
TITLE_BLINK = false; TITLE_BLINK = false;
@ -1093,3 +1056,112 @@ function idToURL(data) {
} }
return entry; return entry;
} }
function genPermissionsEditor() {
$("#chanperms").html("");
var form = $("<form/>").addClass("form-horizontal")
.attr("action", "javascript:void(0)")
.appendTo($("#chanperms"));
var fs = $("<fieldset/>").appendTo(form);
function makeOption(text, key, permset, defval) {
var group = $("<div/>").addClass("control-group")
.appendTo(fs);
$("<label/>").addClass("control-label")
.text(text)
.appendTo(group);
var controls = $("<div/>").addClass("controls")
.appendTo(group);
var select = $("<select/>").appendTo(controls);
select.data("key", key);
for(var i = 0; i < permset.length; i++) {
$("<option/>").attr("value", permset[i][1])
.text(permset[i][0])
.attr("selected", defval == permset[i][1])
.appendTo(select);
}
}
function addDivider(text) {
$("<hr/>").appendTo(fs);
$("<h3/>").text(text).appendTo(fs);
}
var standard = [
["Anonymous" , "-1"],
["Guest" , "0"],
["Registered" , "1"],
["Leader" , "1.5"],
["Moderator" , "2"],
["Channel Admin", "3"]
];
var modleader = [
["Leader" , "1.5"],
["Moderator" , "2"],
["Channel Admin", "3"]
];
var modplus = [
["Moderator" , "2"],
["Channel Admin", "3"]
];
addDivider("Open playlist permissions");
makeOption("Add to playlist", "oplaylistadd", standard, CHANPERMS.oplaylistadd+"");
makeOption("Add/move to next", "oplaylistnext", standard, CHANPERMS.oplaylistnext+"");
makeOption("Move playlist items", "oplaylistmove", standard, CHANPERMS.oplaylistmove+"");
makeOption("Delete playlist items", "oplaylistdelete", standard, CHANPERMS.oplaylistdelete+"");
makeOption("Jump to video", "oplaylistjump", standard, CHANPERMS.oplaylistjump+"");
addDivider("General playlist permissions");
makeOption("Add to playlist", "playlistadd", standard, CHANPERMS.playlistadd+"");
makeOption("Add/move to next", "playlistnext", standard, CHANPERMS.playlistnext+"");
makeOption("Move playlist items", "playlistmove", standard, CHANPERMS.playlistmove+"");
makeOption("Delete playlist items", "playlistdelete", standard, CHANPERMS.playlistdelete+"");
makeOption("Jump to video", "playlistjump", standard, CHANPERMS.playlistjump+"");
makeOption("Add nontemporary media", "addnontemp", standard, CHANPERMS.addnontemp+"");
makeOption("Temp/untemp playlist item", "settemp", standard, CHANPERMS.settemp+"");
makeOption("Retrieve playlist URLs", "playlistgeturl", standard, CHANPERMS.playlistgeturl+"");
makeOption("Shuffle playlist", "playlistshuffle", standard, CHANPERMS.playlistshuffle+"");
makeOption("Clear playlist", "playlistclear", standard, CHANPERMS.playlistclear+"");
addDivider("Polls");
makeOption("Open/Close poll", "pollctl", modleader, CHANPERMS.pollctl+"");
makeOption("Vote", "pollvote", standard, CHANPERMS.pollvote+"");
addDivider("Moderation");
makeOption("Kick users", "kick", modleader, CHANPERMS.kick+"");
makeOption("Ban users", "ban", modplus, CHANPERMS.ban+"");
makeOption("Edit MOTD", "motdedit", modplus, CHANPERMS.motdedit+"");
makeOption("Edit chat filters", "filteredit", modplus, CHANPERMS.filteredit+"");
addDivider("Misc");
makeOption("Drink calls", "drink", modleader, CHANPERMS.drink+"");
var submit = $("<button/>").addClass("btn btn-primary").appendTo(fs);
submit.text("Save");
submit.click(function() {
var perms = {};
fs.find("select").each(function() {
perms[$(this).data("key")] = parseFloat($(this).val());
});
socket.emit("setPermissions", perms);
});
}
function hasPermission(key) {
if(key.indexOf("playlist") == 0 && OPENQUEUE) {
var key2 = "o" + key;
var v = CHANPERMS[key2];
if(typeof v != "number") {
return false;
}
return RANK >= v;
}
var v = CHANPERMS[key];
if(typeof v != "number") {
return false;
}
return RANK >= v;
}

View File

@ -113,42 +113,43 @@
<div class="clear: both;"></div> <div class="clear: both;"></div>
</div> </div>
<button class="btn btn-danger btn-block" id="qlockbtn" style="display:none;">Unlock Queue</button> <button class="btn btn-danger btn-block" id="qlockbtn" style="display:none;">Unlock Queue</button>
<div class="span12 btn-group" style="margin: 0"> <button class="btn btn-block" id="getplaylist" style="display: none;">Get Playlist URLs</button>
<button class="btn" id="getplaylist" style="width: 100%">Get Playlist URLs</button> <button class="btn btn-block" id="clearplaylist" style="display: none;">Clear Playlist</button>
<button class="btn" id="clearplaylist" style="width: 33%; display: none;">Clear Playlist</button> <button class="btn btn-block" id="shuffleplaylist" style="display: none;">Shuffle Playlist</button>
<button class="btn" id="shuffleplaylist" style="width: 33%; display: none;">Shuffle Playlist</button>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="row" style="display: none;" id="modnav"> <div class="row" style="display: none;" id="modnav">
<div class="span12" id="modtabs"> <div class="span12" id="modtabs">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="active"> <li class="active" id="chancontrols_tab">
<a href="javascript:void(0)" id="show_chancontrols">Channel Controls</a> <a href="javascript:void(0)" id="show_chancontrols">Channel Controls</a>
</li> </li>
<li> <li id="chanperms_tab">
<a href="javascript:void(0)" id="show_chanperms">Channel Permissions</a>
</li>
<li id="banlist_tab">
<a href="javascript:void(0)" id="show_banlist">Ban List</a> <a href="javascript:void(0)" id="show_banlist">Ban List</a>
</li> </li>
<li> <li id="loginlog_tab">
<a href="javascript:void(0)" id="show_loginlog">Connection Log</a> <a href="javascript:void(0)" id="show_loginlog">Connection Log</a>
</li> </li>
<li> <li id="motdeditor_tab">
<a href="javascript:void(0)" id="show_motdeditor">MOTD</a> <a href="javascript:void(0)" id="show_motdeditor">MOTD</a>
</li> </li>
<li style="display: none"> <li style="display: none" id="csseditor_tab">
<a href="javascript:void(0)" id="show_csseditor">CSS Editor</a> <a href="javascript:void(0)" id="show_csseditor">CSS Editor</a>
</li> </li>
<li style="display: none"> <li style="display: none" id="jseditor_tab">
<a href="javascript:void(0)" id="show_jseditor">JS Editor</a> <a href="javascript:void(0)" id="show_jseditor">JS Editor</a>
</li> </li>
<li> <li id="filtereditor_tab">
<a href="javascript:void(0)" id="show_filtereditor">Chat Filters</a> <a href="javascript:void(0)" id="show_filtereditor">Chat Filters</a>
</li> </li>
<li> <li id="acl_tab">
<a href="javascript:void(0)" id="show_acl">Channel Ranks</a> <a href="javascript:void(0)" id="show_acl">Channel Ranks</a>
</li> </li>
<li style="display: none"> <li style="display: none" id="dropchannel_tab">
<a href="javascript:void(0)" id="drop_channel">Unregister Channel</a> <a href="javascript:void(0)" id="drop_channel">Unregister Channel</a>
</li> </li>
</ul> </ul>
@ -158,37 +159,6 @@
<div class="span12"> <div class="span12">
<form action="javascript:void(0)"> <form action="javascript:void(0)">
<fieldset> <fieldset>
<div class="span5">
<label>When the queue is open:</label>
<label class="checkbox">
<input type="checkbox" id="opt_qopen_allow_anon">
Allow anonymous users to add videos
</label>
<label class="checkbox">
<input type="checkbox" id="opt_qopen_allow_guest">
Allow guest users to add videos
</label>
<label class="checkbox">
<input type="checkbox" id="opt_qopen_allow_qnext">
Allow anyone to Queue Next
</label>
<label class="checkbox">
<input type="checkbox" id="opt_qopen_allow_move">
Allow anyone to move videos
</label>
<label class="checkbox">
<input type="checkbox" id="opt_qopen_allow_delete">
Allow anyone to delete videos
</label>
<label class="checkbox">
<input type="checkbox" id="opt_qopen_allow_playnext">
Allow anyone to jump to a video
</label>
<label class="checkbox">
<input type="checkbox" id="opt_qopen_temp">
Videos added by guests are temporary
</label>
</div>
<div class="span5"> <div class="span5">
<label>Page Title <label>Page Title
<input type="text" id="opt_pagetitle" placeholder="CyTube" class="pull-right"> <input type="text" id="opt_pagetitle" placeholder="CyTube" class="pull-right">
@ -229,6 +199,8 @@
</form> </form>
</div> </div>
</div> </div>
<div class="row modonly" id="chanperms" style="display: none;">
</div>
<div class="row modonly" id="banlist" style="display: none;"> <div class="row modonly" id="banlist" style="display: none;">
<div class="span12"> <div class="span12">
<table class="table table-striped"> <table class="table table-striped">