express-ads
Version:
Ads distribution and management module for Express
1,609 lines (1,509 loc) • 62.8 kB
JavaScript
/*
* Copyright (c) 2015 Michel Gutierrez
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
angular.module('EASApp', ["ui.select"]);
angular.module('EASApp').controller('EASCtrl',
['$scope', '$http',
function ($scope,$http) {
$scope.data = {};
$scope.context = {
inventory: null,
campaign: null,
banner: null,
addImageError: null,
addImageUrl: "",
imageFiles: [],
startType: "now",
endType: "never",
start: Date.now(),
end: new Date(Date.now()+7*24*60*60*1000).getTime(),
selected: {},
selToggle: true,
invStyleProp: null,
invStyleValue: null,
creatingBanner: false,
bannerType: "image",
bannerText: null,
bannerTextId: null,
addons: {},
}
$scope.campaignUsage = {};
$scope.bannerUsage = {};
$scope.inventoryUsage = {};
$scope.local = {
filterInventoryActive: true,
filterInventoryUsed: false,
filterInventoryCampaign: null,
filterInventoryBanner: null,
filterCampaignActive: true,
filterCampaignUsed: false,
filterCampaignInventory: null,
filterCampaignBanner: null,
filterBannerActive: true,
filterBannerUsed: false,
filterBannerInventory: null,
filterBannerCampaign: null,
tab: "campaign",
}
if(window.localStorage) {
var local = window.localStorage.getItem("eas");
if(local)
try {
var stored = JSON.parse(local);
angular.merge($scope.local,stored);
} catch(e) {}
}
$scope.sizes = [];
$scope.campTypes = [
{value: "background", label: "Background", vLabel: "Weight" },
{value: "click", label: "Clicks", vLabel: "Total clicks" },
{value: "impr", label: "Imprs", vLabel: "Total imprs" },
{value: "clickperminute", label: "Clicks per minute", vLabel: "Clicks per minute" },
{value: "clickperhour", label: "Clicks per hour", vLabel: "Clicks per hour" },
{value: "clickperday", label: "Clicks per day", vLabel: "Clicks per day" },
{value: "clickperweek", label: "Clicks per week", vLabel: "Clicks per week" },
{value: "clickpermonth", label: "Clicks per month", vLabel: "Clicks per month" },
{value: "imprperminute", label: "Imprs per minute", vLabel: "Imprs per minute" },
{value: "imprperhour", label: "Imprs per hour", vLabel: "Imprs per hour" },
{value: "imprperday", label: "Imprs per day", vLabel: "Imprs per day" },
{value: "imprperweek", label: "Imprs per week", vLabel: "Imprs per week" },
{value: "imprpermonth", label: "Imprs per month", vLabel: "Imprs per month" },
];
var baseBannerTypes = {
image: "Images",
text: "Texts",
};
$scope.nobanners = [{value:'hide',label:"Hide area"},{value:'blank',label:"Blank area"}];
$scope.startTypes = [
{value: "now", label: "Starts now" },
{value: "date", label: "Starts at" },
];
$scope.endTypes = [
{value: "never", label: "Never ends" },
{value: "date", label: "Ends at" },
];
$scope.countryTypes = [
{value: "any", label: "Any country" },
{value: "in", label: "Selected countries" },
{value: "out", label: "All but countries..." },
];
$scope.browserTypes = [
{value: "any", label: "Any browser" },
{value: "in", label: "Selected browsers" },
{value: "out", label: "All but browsers..." },
];
$scope.osTypes = [
{value: "any", label: "Any OS" },
{value: "in", label: "Selected OSes" },
{value: "out", label: "All but OSes..." },
];
$scope.browserFamilies = {
"bingbot": "bingbot",
"firefox": "Firefox",
"android": "Android",
"chrome": "Chrome",
"pale moon": "Pale Moon",
"slurp": "Slurp",
"yandexbot": "YandexBot",
"mobile safari": "Mobile Safari",
"ie": "IE",
"phantomjs": "PhantomJS",
"yandex browser": "Yandex Browser",
"maxthon": "Maxthon",
"other": "Other",
"safari": "Safari",
"applemail": "AppleMail",
"chrome mobile": "Chrome Mobile",
"ie mobile": "IE Mobile",
"opera": "Opera",
"firefox mobile": "Firefox Mobile",
"iceweasel": "Iceweasel",
"chromium": "Chromium",
"googlebot": "Googlebot",
"firefox beta": "Firefox Beta",
"uc browser": "UC Browser",
"qq browser mobile": "QQ Browser Mobile",
"firefox alpha": "Firefox Alpha",
"opera mini": "Opera Mini",
"twitterbot": "TwitterBot",
"opera mobile": "Opera Mobile",
"iron": "Iron",
"seamonkey": "SeaMonkey",
"k-meleon": "K-Meleon",
"up.browser": "UP.Browser",
"avant": "Avant",
"dolfin": "Dolfin",
"chrome mobile ios": "Chrome Mobile iOS",
"konqueror": "Konqueror",
"camino": "Camino",
"facebookbot": "FacebookBot"
}
$scope.osFamilies = {
"other": "Other",
"windows 7": "Windows 7",
"windows": "Windows",
"windows 8.1": "Windows 8.1",
"windows xp": "Windows XP",
"android": "Android",
"windows 8": "Windows 8",
"ubuntu": "Ubuntu",
"mac os x": "Mac OS X",
"linux": "Linux",
"windows vista": "Windows Vista",
"ios": "iOS",
"linux mint": "Linux Mint",
"windows phone": "Windows Phone",
"fedora": "Fedora",
"windows ce": "Windows CE",
"windows rt 8.1": "Windows RT 8.1",
"chrome os": "Chrome OS",
"windows rt": "Windows RT",
"windows 98": "Windows 98",
"windows 95": "Windows 95",
"windows 2000": "Windows 2000",
"netbsd": "NetBSD",
"debian": "Debian"
}
const periodTypes = {
'click': { type: 'click' },
'impr': { type: 'impr' },
'clickperminute': { type:'click', duration:60*1000 },
'clickperhour': { type:'click', duration:60*60*1000 },
'clickperday': { type:'click', duration:24*60*60*1000 },
'clickperweek': { type: 'click', duration: 7*24*60*60*1000 },
'clickpermonth': { type: 'click', duration : 30*24*60*60*1000 },
'imprperminute': { type: 'impr', duration: 60*1000 },
'imprperhour': { type: 'impr', duration: 60*60*1000 },
'imprperday': { type: 'impr', duration: 24*60*60*1000 },
'imprperweek': { type: 'impr', duration: 7*24*60*60*1000 },
'imprpermonth': { type: 'impr', duration: 30*24*60*60*1000 }
};
function UpdateSizes(addons) {
var sizes = {text: "Text"};
if($scope.data.sizes)
$scope.data.sizes.forEach(function(size) {
sizes[size] = size;
});
for(var a in (addons || {}))
addons[a].sizes.forEach(function(size) {
size = size.toLowerCase()
if(!sizes[size])
sizes[size] = size[0].toUpperCase()+size.substring(1).toLowerCase();
});
Object.keys(sizes).sort().forEach(function(size) {
sizes[size.toLowerCase()] = size[0].toUpperCase()+size.substring(1).toLowerCase();
$scope.sizes.push({value: size, label: sizes[size]});
});
}
function UpdateBannerTypes(addons) {
$scope.bannerTypes = [];
var types = angular.merge({},baseBannerTypes);
for(var a in (addons || {}))
if(!types[a])
types[a] = addons[a].title;
Object.keys(types).sort().forEach(function(type) {
$scope.bannerTypes.push({value: type, label: types[type]});
});
}
UpdateBannerTypes({});
function Call(apiUrl,apiParams,options,callback) {
$scope.loading = true;
if(!callback) {
callback = options;
options = undefined;
}
$http.post(easAPI + apiUrl,apiParams,options).then(function(data) {
$scope.loading = false;
if(!data.data.status)
return callback(new Error(data.data.error));
else
return callback(null,data.data.result || null);
},function(err) {
$scope.loading = false;
console.info("Error",err);
if(err.status==403)
alert("Logged out ?");
else if(err.status==-1)
alert("Server down ?");
callback(err);
});
}
$scope.campTypeValueLabel = function(type) {
for(var i=0;i<$scope.campTypes.length;i++)
if($scope.campTypes[i].value==type)
return $scope.campTypes[i].vLabel;
return "";
}
$scope.bannerTypeValueLabel = function(type) {
for(var i=0;i<$scope.bannerTypes.length;i++)
if($scope.bannerTypes[i].value==type)
return $scope.bannerTypes[i].label;
return "";
}
$scope.campTypeLabel = function(type) {
for(var i=0;i<$scope.campTypes.length;i++)
if($scope.campTypes[i].value==type)
return $scope.campTypes[i].label;
return "";
}
$scope.sizeLabel = function(size) {
for(var s in $scope.sizes)
if($scope.sizes[s].value==size)
return $scope.sizes[s].label;
return "";
}
$scope.campRemains = function(cid) {
var campaign = $scope.data.ads.campaign[cid];
if(!campaign)
return "-";
var periodType = periodTypes[campaign.type];
if(periodType) {
var eventsCount = $scope.data.stats.period[periodType.type][cid] || 0;
if(periodType.duration) {
var timeRemaining = Math.max(0,($scope.data.stats.periodTime[cid]+periodType.duration)-$scope.data.now);
return Math.max(0,campaign.value-eventsCount) + " " + periodType.type+"s in "+TimeToText(timeRemaining);
} else {
var timeRemaining = Math.max(0,campaign.end-$scope.data.now);
return Math.max(0,campaign.value-eventsCount) + " " + periodType.type+"s in "+TimeToText(timeRemaining);
}
}
}
$scope.$watch('context.addImageUrl',function() {
$scope.context.addImageError = null;
});
$scope.$watch('local.tab',function() {
$scope.context.selected = {};
$scope.context.selToggle = true;
$(".tooltip").remove();
});
$scope.$watch('local',function() {
if(window.localStorage)
window.localStorage.setItem("eas",JSON.stringify($scope.local));
},true);
function UpdateSchedule() {
var campaign = $scope.context.campaign;
var context = $scope.context;
if(campaign) {
if(context.startType=='date')
campaign.start = new Date(context.start).getTime();
else if(context.startType=='now')
campaign.start = 0;
if(context.endType=='date')
campaign.end = new Date(context.end).getTime();
else if(context.endType=='never')
campaign.end = 0;
}
}
$scope.$watch('context.startType+context.start+context.endType+context.end',UpdateSchedule);
$scope.selectionToggle = function() {
if(['campaign','banner','inventory'].indexOf($scope.local.tab)>=0) {
for(var id in $scope.data.ads[$scope.local.tab])
$scope.context.selected[id] = $scope.context.selToggle;
$scope.context.selToggle = !$scope.context.selToggle;
}
}
$scope.selectedCount = function() {
var count = 0;
for(var id in $scope.context.selected)
if($scope.context.selected[id])
count++;
return count;
}
$scope.activeSelected = function(active) {
if(['campaign','banner','inventory'].indexOf($scope.local.tab)>=0) {
Call('/active-group',{type: $scope.local.tab, active: active, ids: $scope.context.selected},function(err,data) {
$scope.getAds();
});
}
}
$scope.removeSelected = function() {
if(['campaign','banner','inventory'].indexOf($scope.local.tab)>=0) {
var count = 0;
for(var id in $scope.context.selected)
if($scope.context.selected[id])
count++;
if(confirm("Do you really want to remove "+count+" item"+(count>1?"s":"")+" ?")) {
Call('/remove-group',{type: $scope.local.tab, ids: $scope.context.selected},function(err,data) {
$scope.getAds();
});
}
}
}
$scope.mustHaveEnd = function() {
return $scope.context.campaign && (
$scope.context.campaign.type=='click' || $scope.context.campaign.type=='impr'
);
}
$scope.$watch('context.campaign.type',function() {
if($scope.mustHaveEnd()) {
$scope.context.endType = 'date';
$scope.context.end = $scope.context.campaign.end || new Date(Date.now()+7*24*60*60*1000);
}
});
$scope.getAds = function(callback) {
Call('/',{},function(err,data) {
$(".tooltip").hide();
$("[vdh-tooltip]").tooltip('hide');
if(!err) {
$scope.data = data;
$scope.context.addons = angular.merge({},data.ads.addons);
angular.extend($scope.osFamilies,data.osFamilies);
angular.extend($scope.browserFamilies,data.browserFamilies);
UpdateSizes(data.addons);
UpdateBannerTypes(data.addons);
$scope.context.selected = {}
$scope.selToggle = true;
$scope.calcInventoryUsage();
$scope.calcCampaignUsage();
$scope.calcBannerUsage();
if(callback)
callback();
}
});
}
$scope.getAds();
$scope.floor = function(val) {
return Math.floor(val);
}
$scope.prettyAds = function() {
return JSON.stringify($scope.data,null,4);
}
$scope.pretty = function(data) {
return JSON.stringify(data,null,4);
}
/* inventory */
$scope.newInventory = function() {
$scope.context.inventory = {
hid: "",
active: true,
size: "300x250",
description: "",
nobanner: "hide",
classes: "",
styles: {},
}
}
$scope.cancelInventory = function() {
$scope.context.inventory = null;
}
$scope.saveInventory = function() {
Call('/set-inventory',{inventory: $scope.context.inventory},function(err,data) {
$scope.getAds();
});
$scope.context.inventory = null;
}
$scope.removeInventory = function(inv) {
if(confirm("Are you sure you want to remove the area ?")) {
$scope.context.inventory = null;
Call('/remove-inventory',{iid: inv.id},function(err,data) {
$scope.getAds();
});
}
}
$scope.changedInventory = function() {
if(!$scope.context.inventory)
return false;
return !angular.equals($scope.context.inventory,$scope.data.ads.inventory[$scope.context.inventory.id]);
}
$scope.selectInventory = function(inv) {
$scope.context.inventory = angular.copy(inv);
ScrollTop();
}
$scope.inventoryCount = function() {
if(!$scope.data.ads)
return 0;
var count = 0;
for(var i in $scope.data.ads.inventory)
count++;
return count;
}
$scope.inventoryHasStyle = function() {
if(!$scope.context.inventory)
return false;
for(var i in $scope.context.inventory.styles)
return true;
return false;
}
$scope.addInventoryStyle = function() {
$scope.context.inventory.styles[$scope.context.invStyleProp.trim()] = $scope.context.invStyleValue.trim();
$scope.context.invStyleProp = null;
$scope.context.invStyleValue = null;
}
$scope.newInventoryStyle = function() {
$scope.context.invStyleProp = "";
$scope.context.invStyleValue = "";
}
$scope.removeInventoryStyle = function(prop) {
delete $scope.context.inventory.styles[prop];
}
/* campaign */
$scope.newCampaign = function() {
$scope.context.campaign = {
hid: "",
active: true,
type: "background",
value: 1,
cap: 0,
pagecap: 0,
paused: false,
banners: [],
description: "",
start: 0,
end: 0,
}
}
$scope.cancelCampaign = function() {
$scope.context.campaign = null;
}
$scope.saveCampaign = function() {
Call('/set-campaign',{campaign: $scope.context.campaign},function(err,data) {
$scope.getAds();
});
$scope.context.campaign = null;
}
$scope.removeCampaign = function(inv) {
if(confirm("Are you sure you want to remove the campaign ?")) {
$scope.context.campaign = null;
Call('/remove-campaign',{iid: inv.id},function(err,data) {
$scope.getAds();
});
}
}
$scope.changedCampaign = function() {
if(!$scope.context.campaign)
return false;
return !angular.equals($scope.context.campaign,$scope.data.ads.campaign[$scope.context.campaign.id]);
}
$scope.selectCampaign = function(inv) {
$scope.context.campaign = angular.copy(inv);
$scope.context.startType = $scope.context.campaign.start ? "date" : "now";
$scope.context.endType = $scope.context.campaign.end ? "date" : "never";
ScrollTop();
}
$scope.campaignCount = function() {
if(!$scope.data.ads)
return 0;
var count = 0;
for(var i in $scope.data.ads.campaign)
count++;
return count;
}
$scope.campaignStatus = function(campaign) {
if(!campaign.active)
return {
title: "Paused",
clazz: "fa-pause",
}
if(campaign.start && campaign.start>$scope.data.now) {
return {
title: "Waiting "+TimeToText(campaign.start-$scope.data.now),
clazz: "fa-clock-o",
}
}
if(campaign.end && campaign.end<$scope.data.now)
return {
title: "Complete",
clazz: "fa-times",
}
if(campaign.active) {
var usage = $scope.campaignUsage[campaign.id];
if(usage.bannersCount==0 || usage.inventoryCount==0)
return {
title: "Unused",
clazz: "fa-play-circle-o",
}
else
return {
title: "Running ",
clazz: "fa-play",
}
}
}
/* banner */
$scope.createBanner = function() {
$scope.context.banner = {
hid: "",
active: true,
type: $scope.context.bannerType,
link: "",
alt: "",
cap: 0,
pagecap: 0,
description: "",
images: {},
inventory: [],
countryType: "any",
countries: [],
browserType: "any",
browsers: [],
osType: "any",
oss: [],
adbUsed: true,
adbUnsure: true,
adbUnused: true,
}
$scope.context.imageFiles = [];
if($scope.context.bannerType=='image')
$scope.context.banner.images = {}
else
$scope.context.banner.texts = {}
Call('/set-banner',{banner: $scope.context.banner},function(err,data) {
$scope.context.creatingBanner = false;
if(data.banner)
$scope.context.banner = data.banner;
$scope.getAds();
});
}
$scope.newBanner = function() {
$scope.context.creatingBanner = true;
}
$scope.cancelBanner = function() {
$scope.context.banner = null;
}
$scope.saveBanner = function() {
Call('/set-banner',{banner: $scope.context.banner},function(err,data) {
$scope.getAds();
});
$scope.context.banner = null;
}
$scope.removeBanner = function(inv) {
if(confirm("Are you sure you want to remove the banner ?")) {
$scope.context.banner = null;
Call('/remove-banner',{iid: inv.id},function(err,data) {
$scope.getAds();
});
}
}
$scope.changedBanner = function() {
if(!$scope.context.banner)
return false;
return !angular.equals($scope.context.banner,$scope.data.ads.banner[$scope.context.banner.id]);
}
$scope.selectBanner = function(ban) {
$scope.context.banner = angular.copy(ban);
$scope.context.imageFiles = [];
ScrollTop();
}
$scope.bannerCount = function() {
if(!$scope.data.ads)
return 0;
var count = 0;
for(var i in $scope.data.ads.banner)
count++;
return count;
}
$scope.addBannerImage = function() {
var imageUrl = ($scope.context.addImageUrl || "").trim();
if(!imageUrl)
return;
var bid = $scope.context.banner.id;
Call('/add-banner-image',{bid: bid,url:imageUrl},function(err,data) {
if(err)
$scope.context.addImageError = err.data.error;
else if($scope.context.banner) {
$scope.context.banner.images = $scope.context.banner.images || {};
$scope.context.banner.images[data.image.id] = data.image;
}
});
}
$scope.$watch('context.imageFiles',function() {
if($scope.context.imageFiles.length==0)
return;
var imageFiles = $scope.context.imageFiles;
$scope.context.imageFiles = [];
var fd = new FormData();
fd.append('bid', $scope.context.banner.id);
for(var i=0;i<imageFiles.length;i++)
fd.append('files[]', imageFiles[i]);
Call('/upload-banner-images',fd,{
transformRequest: angular.identity,
headers: {
'Content-Type': undefined
}
}, function(err,data) {
if(err)
alert("Could not add file:",err);
else if($scope.context.banner) {
if(data.errors.length>0)
alert(data.errors.join("\n"));
$scope.context.banner.images = $scope.context.banner.images || {};
data.images.forEach(function(image) {
$scope.context.banner.images[image.id] = image;
});
}
});
});
$scope.newBannerText = function() {
Call('/make-id',{},function(err,data) {
if(!err) {
$scope.context.bannerTextId = data;
$scope.context.bannerText = '';
}
});
}
$scope.selectBannerText = function(txt) {
$scope.context.bannerTextId = txt.id;
$scope.context.bannerText = txt.text;
}
$scope.addBannerText = function() {
$scope.context.banner.texts[$scope.context.bannerTextId] = {
id: $scope.context.bannerTextId,
text: $scope.context.bannerText,
}
$scope.context.bannerText = null;
$scope.context.bannerTextId = null;
}
$scope.addBannerImageUrl = function() {
var imageUrl = ($scope.context.addImageUrl || "").trim();
if(!imageUrl)
return;
var bid = $scope.context.banner.id;
Call('/add-banner-image-url',{bid: bid,url:imageUrl},function(err,data) {
if(err)
$scope.context.addImageError = err.data.error;
else
$scope.getAds(function() {
$scope.selectBanner($scope.data.ads.banner[bid]);
});
});
}
$scope.removeBannerImage = function(img) {
if(confirm("Are you sure you want to remove the banner image ?"))
delete $scope.context.banner.images[img.id];
}
$scope.removeBannerText = function(txt) {
if(confirm("Are you sure you want to remove this text ?")) {
delete $scope.context.banner.texts[txt.id];
if(txt.id==$scope.context.bannerTextId) {
$scope.context.bannerText = null;
$scope.context.bannerTextId = null;
}
}
}
$scope.bannerContentCount = function(type) {
if(!$scope.context.banner)
return 0;
if(!$scope.context.banner[type])
return 0;
var count=0;
for(var cont in $scope.context.banner[type])
count++;
return count;
}
$scope.bannerImagesCount = function() {
return $scope.bannerContentCount('images');
}
$scope.bannerTextsCount = function() {
return $scope.bannerContentCount('texts');
}
$scope.bannersArray = function() {
if(!$scope.data || !$scope.data.ads || !$scope.data.ads.banner)
return [];
var arr=[];
for(var id in $scope.data.ads.banner)
arr.push($scope.data.ads.banner[id]);
return arr;
}
$scope.bannerStatus = function(banner) {
if(!banner.active)
return {
title: "Paused",
clazz: "fa-pause",
}
else {
var usage = $scope.bannerUsage[banner.id];
if(usage.campaignsCount==0 || usage.inventoryCount==0)
return {
title: "Unused",
clazz: "fa-play-circle-o",
}
else
return {
title: "Running ",
clazz: "fa-play",
}
}
}
$scope.bannerType = function(banner) {
if(banner.type=='image')
return {
clazz: 'fa-picture-o',
title: 'Image',
}
if(banner.type=='text')
return {
clazz: 'fa-align-justify',
title: 'Text',
}
return {
clazz: 'fa-puzzle-piece',
title: $scope.data.addons[banner.type]?$scope.data.addons[banner.type].title:"",
}
}
$scope.inventoryArray = function() {
if(!$scope.data || !$scope.data.ads || !$scope.data.ads.inventory)
return [];
var arr=[];
for(var id in $scope.data.ads.inventory)
arr.push($scope.data.ads.inventory[id]);
return arr;
}
$scope.calcCTR = function(object,id) {
if(!object || !object.click || !object.impr)
return "-";
if(!object.click[id])
return 0;
if(object.impr[id])
return Math.round(object.click[id]*10000/object.impr[id])/100;
else
return "-";
}
function TimeToText(value) {
value=Math.floor(value/1000);
var days=Math.floor(value/86400);
value%=86400;
var hours=Math.floor(value/3600);
value%=3600;
var minutes=Math.floor(value/60);
var seconds=value%60;
var str="";
if(days>3)
str+=days+'d';
else if(days>0) {
if(hours>0)
str+=days+"d"+hours+"h"
else
str+=days+"d";
} else if(hours>3)
str+=hours+"h";
else if(hours>0) {
if(minutes>0)
str+=hours+"h"+minutes+"m";
else
str+=hours+"h";
} else {
str+=minutes+':';
if(seconds<10)
str+='0';
str+=seconds;
}
return str;
}
$scope.imageUrl = function(img) {
return img.url || '/eas/images/'+img.id+'.png';
}
$scope.capitalize = function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
$scope.clearStats = function(type,id,which) {
Call('/clear-stats',{type: type, id: id, which:which},function(err,data) {
$scope.getAds();
});
}
$scope.bannerImageTooltip = function(img) {
return '<div><img style=\'max-width:400px;max-height:200px\' src=\''+$scope.imageUrl(img)+'\'/></div>';
}
$scope.filterCampaign = function(cam) {
if($scope.local.filterCampaignActive && !cam.active)
return false;
if($scope.local.filterCampaignUsed && ($scope.campaignUsage[cam.id].bannersCount==0 || $scope.campaignUsage[cam.id].inventoryCount==0))
return false;
if($scope.data.ads && $scope.data.ads.inventory[$scope.local.filterCampaignInventory] &&
!$scope.campaignUsage[cam.id].inventory[$scope.local.filterCampaignInventory])
return false;
if($scope.data.ads && $scope.data.ads.banner[$scope.local.filterCampaignBanner] &&
!$scope.campaignUsage[cam.id].banners[$scope.local.filterCampaignBanner])
return false;
return true;
}
$scope.calcCampaignUsage = function() {
if(!$scope.data || !$scope.data.ads || !$scope.data.ads.campaign)
return;
$scope.campaignUsage = {};
for(var camId in $scope.data.ads.campaign) {
var cam = $scope.data.ads.campaign[camId];
var banners = {};
var inventory = {};
if(cam.active) {
cam.banners.forEach(function(bid) {
if(!$scope.data.ads.banner[bid])
return;
if(!$scope.data.ads.banner[bid].active)
return;
banners[bid] = 1;
});
for(var bid in banners) {
var banner = $scope.data.ads.banner[bid];
banner.inventory.forEach(function(iid) {
var inv = $scope.data.ads.inventory[iid];
if(!inv)
return;
if(!inv.active)
return;
if(banner.type=='text') {
if(inv.size=='text')
inventory[iid] = 1;
} else if(banner.type=='image') {
for(var imid in banner.images) {
var image = banner.images[imid];
if(!image)
continue;
if(image.size==inv.size)
inventory[iid] = 1;
}
} else if($scope.data.addons[banner.type]) {
if($scope.data.addons[banner.type].sizes.indexOf(inv.size)>=0)
inventory[iid] = 1;
}
});
}
}
var inventoryCount = 0;
for(var iid in inventory)
if($scope.data.ads.inventory[iid].active)
inventoryCount++;
var bannersCount = 0;
for(var bid in banners)
if($scope.data.ads.banner[bid].active)
bannersCount++;
$scope.campaignUsage[camId] = {
banners: banners,
bannersCount: bannersCount,
inventory: inventory,
inventoryCount: inventoryCount,
}
}
var campaignInv = {};
var campaignCount = {};
for(var invId in $scope.data.stats.roll) {
if(!$scope.data.ads.inventory[invId])
continue;
var roll = $scope.data.stats.roll[invId];
for(var cid in roll.current) {
if(!(cid in $scope.campaignUsage))
continue;
campaignInv[cid] = campaignInv[cid] || {};
campaignInv[cid][invId] = (campaignInv[cid][invId] || 0) + roll.current[cid];
campaignCount[cid] = (campaignCount[cid] || 0) + roll.current[cid];
}
for(var cid in roll.last) {
if(!(cid in $scope.campaignUsage))
continue;
campaignInv[cid] = campaignInv[cid] || {};
campaignInv[cid][invId] = (campaignInv[cid][invId] || 0) + roll.last[cid];
campaignCount[cid] = (campaignCount[cid] || 0) + roll.last[cid];
}
}
for(var cid in campaignInv) {
$scope.campaignUsage[cid].inventoryUsagePercent = {};
for(var iid in campaignInv[cid])
$scope.campaignUsage[cid].inventoryUsagePercent[iid] = Math.round(campaignInv[cid][iid]*100/campaignCount[cid]);
}
}
$scope.filterBanner = function(ban) {
if($scope.local.filterBannerActive && !ban.active)
return false;
if($scope.local.filterBannerUsed && ($scope.bannerUsage[ban.id].campaignsCount==0 || $scope.bannerUsage[ban.id].inventoryCount==0))
return false;
if($scope.data.ads && $scope.data.ads.inventory[$scope.local.filterBannerInventory] &&
!$scope.bannerUsage[ban.id].inventory[$scope.local.filterBannerInventory])
return false;
if($scope.data.ads && $scope.data.ads.campaign[$scope.local.filterBannerCampaign] &&
!$scope.bannerUsage[ban.id].campaigns[$scope.local.filterBannerCampaign])
return false;
return true;
}
$scope.calcBannerUsage = function() {
if(!$scope.data || !$scope.data.ads || !$scope.data.ads.banner)
return;
$scope.bannerUsage = {};
for(var banId in $scope.data.ads.banner) {
var banner = $scope.data.ads.banner[banId];
var campaigns = {};
var inventory = {};
if(banner.active) {
banner.inventory.forEach(function(iid) {
var inv = $scope.data.ads.inventory[iid];
if(!inv)
return;
if(!inv.active)
return;
if(banner.type=='image') {
for(var imid in banner.images) {
var image = banner.images[imid];
if(!image)
continue;
if(image.size==inv.size)
inventory[iid] = 1;
}
} else if(banner.type=='text') {
if(inv.size=='text')
inventory[iid] = 1;
} else if($scope.data.addons[banner.type]) {
if($scope.data.addons[banner.type].sizes.indexOf(inv.size)>=0)
inventory[iid] = 1;
}
});
for(var cid in $scope.data.ads.campaign) {
var campaign = $scope.data.ads.campaign[cid];
if(!campaign)
continue;
if(!campaign.active)
continue;
if(campaign.banners.indexOf(banner.id)>=0)
campaigns[cid] = 1;
}
}
var inventoryCount = 0;
for(var iid in inventory)
if($scope.data.ads.inventory[iid].active)
inventoryCount++;
var campaignsCount = 0;
for(var cid in campaigns)
if($scope.data.ads.campaign[cid].active)
campaignsCount++;
$scope.bannerUsage[banId] = {
campaigns: campaigns,
campaignsCount: campaignsCount,
inventory: inventory,
inventoryCount: inventoryCount,
}
}
}
$scope.filterActiveInventory = function(inv) {
return inv.active;
}
$scope.filterTypedInventory = function(type) {
return function(inv) {
if(type=='text')
return inv.size=='text';
if(type=='image')
return inv.size!='text';
return $scope.data.addons[type] && $scope.data.addons[type].sizes.indexOf(inv.size)>=0;
}
}
$scope.filterInventory = function(inv) {
if($scope.local.filterInventoryActive && !inv.active)
return false;
if($scope.local.filterInventoryUsed && ($scope.inventoryUsage[inv.id].campaignsCount==0 || $scope.inventoryUsage[inv.id].bannersCount==0))
return false;
if($scope.data.ads && $scope.data.ads.campaign[$scope.local.filterInventoryCampaign] &&
!$scope.inventoryUsage[inv.id].campaigns[$scope.local.filterInventoryCampaign])
return false;
if($scope.data.ads && $scope.data.ads.banner[$scope.local.filterInventoryBanner] &&
!$scope.inventoryUsage[inv.id].banners[$scope.local.filterInventoryBanner])
return false;
return true;
}
$scope.calcInventoryUsage = function() {
if(!$scope.data || !$scope.data.ads || !$scope.data.ads.inventory)
return;
var now = $scope.data.now;
$scope.inventoryUsage = {};
for(var invId in $scope.data.ads.inventory) {
var inv = $scope.data.ads.inventory[invId];
var uroll = {
total: 0,
campaignPercent: {},
};
var roll = $scope.data.stats.roll[invId];
if(roll) {
var total = roll.currentCount + roll.lastCount;
if(total>0) {
uroll.total = total;
uroll.perday = Math.round(total * ( 24*60*60*1000 / (now-roll.lastStart)));
var campaigns0 = {}
for(var cid in roll.current) {
if(cid[0]=='_')
continue;
campaigns0[cid] = roll.current[cid];
}
for(var cid in roll.last) {
if(cid[0]=='_')
continue;
campaigns0[cid] = (campaigns0[cid] || 0) + roll.last[cid];
}
var background = 0;
var scheduled = 0;
for(var cid in campaigns0) {
var campaign = $scope.data.ads.campaign[cid];
uroll.campaignPercent[cid] = Math.round(campaigns0[cid]*100/total);
if(campaign) {
if(campaign.type=='background')
background += campaigns0[cid];
else
scheduled += campaigns0[cid];
}
}
uroll.backgroundPercent = Math.round(background*100/total);
uroll.scheduledPercent = Math.round(scheduled*100/total)
var missedBanner = (roll.current['_noBanner'] || 0) + (roll.last['_noBanner'] || 0);
uroll.missedBannerPercent = Math.round(missedBanner*100/total)
var missedCampaign = (roll.current['_noCampaign'] || 0) + (roll.last['_noCampaign'] || 0);
uroll.missedCampaignPercent = Math.round(missedCampaign*100/total)
}
}
var banners = {};
var campaigns = {};
if(inv.active) {
for(var bid in $scope.data.ads.banner) {
var banner = $scope.data.ads.banner[bid];
if(!banner)
continue;
if(!banner.active)
continue;
if(banner.inventory.indexOf(inv.id)>=0) {
if(banner.type=='text') {
if(inv.size=='text')
banners[bid] = 1;
} else if(banner.type=='image') {
for(var iid in banner.images) {
var image = banner.images[iid];
if(!image)
continue;
if(image.size==inv.size)
banners[bid] = 1;
}
} else if($scope.data.addons[banner.type]) {
if($scope.data.addons[banner.type].sizes.indexOf(inv.size)>=0)
banners[bid] = 1;
}
}
}
for(var cid in $scope.data.ads.campaign) {
var campaign = $scope.data.ads.campaign[cid];
if(!campaign)
continue;
if(!campaign.active)
continue;
for(var bid in banners)
if(campaign.banners.indexOf(bid)>=0)
campaigns[cid] = 1;
}
}
var bannersCount = 0;
for(var bid in banners)
if($scope.data.ads.banner[bid].active)
bannersCount++;
var campaignsCount = 0;
for(var cid in campaigns)
if($scope.data.ads.campaign[cid].active)
campaignsCount++;
$scope.inventoryUsage[invId] = {
banners: banners,
bannersCount: bannersCount,
campaigns: campaigns,
campaignsCount: campaignsCount,
roll: uroll,
};
}
}
function CountProps(object) {
var count = 0;
for(var i in object)
count++;
return count;
}
$scope.campaignTooltip = function(cam) {
var tooltip = '<div style="width: 400px;text-align:left">';
var usage = $scope.campaignUsage[cam.id];
var inventoryCount = CountProps(usage.inventory);
var bannersCount = CountProps(usage.banners);
if(bannersCount>0) {
tooltip += '<div class="eas-tooltip-label">Banners:</div><div>';
var banners = [];
for(var bid in usage.banners)
banners.push($scope.data.ads.banner[bid].hid);
tooltip += banners.join(", ");
tooltip += '</div>';
}
if(inventoryCount>0) {
tooltip += '<div class="eas-tooltip-label">Inventory:</div><div>';
var inventory = [];
for(var iid in usage.inventory) {
var str = $scope.data.ads.inventory[iid].hid;
if($scope.campaignUsage[cam.id].inventoryUsagePercent)
str += " ("+($scope.campaignUsage[cam.id].inventoryUsagePercent[iid] || 0)+"%)";
inventory.push(str);
}
tooltip += inventory.join(", ");
tooltip += '</div>';
}
if(bannersCount==0 && inventoryCount==0)
tooltip += '<div class="eas-tooltip-label">Not used</div>';
tooltip += '</div>';
return tooltip;
}
$scope.bannerTooltip = function(cam) {
var tooltip = '<div style="width: 400px;text-align:left">';
var usage = $scope.bannerUsage[cam.id];
var inventoryCount = CountProps(usage.inventory);
var campaignsCount = CountProps(usage.campaigns);
if(campaignsCount>0) {
tooltip += '<div class="eas-tooltip-label">Campaigns:</div><div>';
var campaigns = [];
for(var cid in usage.campaigns)
campaigns.push($scope.data.ads.campaign[cid].hid);
tooltip += campaigns.join(", ");
tooltip += '</div>';
}
if(inventoryCount>0) {
tooltip += '<div class="eas-tooltip-label">Inventory:</div><div>';
var inventory = [];
for(var iid in usage.inventory)
inventory.push($scope.data.ads.inventory[iid].hid);
tooltip += inventory.join(", ");
tooltip += '</div>';
}
if(campaignsCount==0 && inventoryCount==0)
tooltip += '<div class="eas-tooltip-label">Not used</div>';
tooltip += '</div>';
return tooltip;
}
$scope.inventoryStatus = function(inventory) {
if(!inventory.active)
return {
title: "Paused",
clazz: "fa-pause",
}
else {
var usage = $scope.inventoryUsage[inventory.id];
if(usage.campaignsCount==0 || usage.bannersCount==0)
return {
title: "Unused",
clazz: "fa-play-circle-o",
}
else
return {
title: "Running ",
clazz: "fa-play",
}
}
}
$scope.inventoryTooltip = function(inv) {
var tooltip = '<div style="width: 400px;text-align:left">';
var usage = $scope.inventoryUsage[inv.id];
var bannersCount = CountProps(usage.banners);
var campaignsCount = CountProps(usage.campaigns);
if(campaignsCount>0) {
tooltip += '<div class="eas-tooltip-label">Campaigns:</div><div>';
var campaigns = [];
for(var cid in usage.campaigns) {
var campStr = $scope.data.ads.campaign[cid].hid;
if(usage.roll.campaignPercent[cid])
campStr += ' ('+ usage.roll.campaignPercent[cid] + '%)';
campaigns.push(campStr);
}
tooltip += campaigns.join(", ");
tooltip += '</div>';
}
if(bannersCount>0) {
tooltip += '<div class="eas-tooltip-label">Banners:</div><div>';
var banners = [];
for(var bid in usage.banners)
banners.push($scope.data.ads.banner[bid].hid);
tooltip += banners.join(", ");
tooltip += '</div>';
}
if(usage.roll.total>0) {
tooltip += '<div class="eas-tooltip-label">Usage:</div>';
tooltip += '<div>'+usage.roll.perday + " imprs per day</div>";
tooltip += '<div>Scheduled '+usage.roll.scheduledPercent+'%</div>';
tooltip += '<div>Background '+usage.roll.backgroundPercent+'%</div>';
tooltip += '<div>Missed no banner '+usage.roll.missedBannerPercent+'%</div>';
tooltip += '<div>Missed no campaign '+usage.roll.missedCampaignPercent+'%</div>';
}
if(bannersCount==0 && campaignsCount==0 && usage.roll.total==0)
tooltip += '<div class="eas-tooltip-label">Not used</div>';
tooltip += '</div>';
return tooltip;
}
$scope.instantStats = function(what,id,period) {
if(!$scope.data.stats || !$scope.data.stats[period])
return {string:""};
var duration = $scope.data.stats[period].duration;
var now = $scope.data.now;
var instant = $scope.data.stats[period][what][id];
if(!instant)
return {string:""};
var currentDuration = now - instant.lastEnd;
var missingDuration = Math.max(0,duration - currentDuration);
var lastDuration = instant.lastEnd - instant.lastStart;
var imprs = instant.current.impr;
var clicks = instant.current.click;
if(lastDuration>0) {
imprs += Math.round(instant.last.impr * (missingDuration/lastDuration));
clicks += Math.round(instant.last.click * (missingDuration/lastDuration));
}
if(currentDuration>duration) {
imprs = 0;
clicks = 0;
}
var str = "";
var ctr = "";
if(imprs>0 || clicks>0) {
str = clicks + "/" + imprs;
if(imprs) {
ctr = (Math.round(clicks*10000/imprs)/100)+"%";
str += " ("+ctr+")";
}
}
return {
imprs: imprs,
clicks: clicks,
ctr: ctr,
string: str,
}
}
$scope.totalStats = function(what,id) {
if(!$scope.data.stats || !$scope.data.stats.total)
return {string:""};
var totalStats = $scope.data.stats.total;
var total = totalStats[what];
var imprs = total.impr[id] || 0;
var clicks = total.click[id] || 0;
var str = clicks + "/" + imprs;
if(clicks==0 && imprs==0)
str = "";
var ctr = "";
if(imprs) {
ctr = Math.round(clicks*10000/imprs)/100+"%";
str += " ("+ctr+")";
}
return {
imprs: imprs,
clicks: clicks,
ctr: ctr,
string: str,
};
}
function ScrollTop() {
window.scroll(0,0);
}
$scope.sorter = function(name,what) {
$scope.local.sorter = $scope.local.sorter || {};
$scope.local.sorter[name] = $scope.local.sorter[name] || {};
return function(item) {
var sorter = $scope.local.sorter[name];
var type = sorter.type || 'normal';
if(type=='normal') {
var field = sorter.field || 'hid';
return item[field] || 0;
} else if(type='stats') {
if(!$scope.data.stats || !$scope.data.stats[sorter.field])
return 0;
var total = $scope.data.stats[sorter.field][what];
if(!total)
return 0;
if(sorter.statsType==1) {
switch(sorter.index%3) {
case 0: return total.click[item.id] || 0;
case 1: return total.impr[item.id] || 0;
case 2: return total.impr[item.id] ? (total.click[item.id] || 0)/total.impr[item.id] : 0;
}
} else if(sorter.statsType==2) {
var duration = $scope.data.stats[sorter.field].duration;
var now = $scope.data.now;
var instant = total[item.id];
if(!instant)
return 0;
var currentDuration = now - instant.lastEnd;
var missingDuration = Math.max(0,duration - currentDuration);
var lastDuration = instant.lastEnd - instant.lastStart;
var imprs = instant.current.impr;
var clicks = instant.current.click;
if(lastDuration>0) {
imprs += Math.round(instant.last.impr * (missingDuration/lastDuration));
clicks += Math.round(instant.last.click * (missingDuration/lastDuration));
}
if(currentDuration>duration) {
imprs = 0;
clicks = 0;
}
switch(sorter.index%3) {
case 0: return clicks || 0;
case 1: return imprs || 0;
case 2: return imprs ? (clicks || 0)/imprs : 0;
}
}
}
}
}
$scope.sorterDir = function(name) {
$scope.local.sorter = $scope.local.sorter || {};
$scope.local.sorter[name] = $scope.local.sorter[name] || {};
var sorter = $scope.local.sorter[name];
var type = sorter.type || 'normal';
if(type=='normal')
return !!sorter.direction;
else if(type=='stats')
return sorter.index<3;
}
$scope.sorterField = function(name,field) {
$scope.local.sorter = $scope.local.sorter || {};
$scope.local.sorter[name] = $scope.local.sorter[name] || {};
var sorter = $scope.local.sorter[name];
sorter.type = 'normal';
if(sorter.field===field)
sorter.direction = !(sorter.direction);
else {
sorter.field = field;
sorter.direction = false;
}
}
$scope.sorterStat = function(name,field,statsType) {
statsType = statsType || 1;
$scope.local.sorter = $scope.local.sorter || {};
$scope.local.sorter[name] = $scope.local.sorter[name] || {};
var sorter = $scope.local.sorter[name];
sorter.type = 'stats';
if(sorter.field===field) {
sorter.direction = !(sorter.direction);
sorter.index = ((sorter.index || 0) + 1) % 6;
} else {
delete sorter.direction;
sorter.statsType = statsType;
sorter.index = 0;
sorter.field = field;
}
}
$scope.gotoItem = function(type,id) {
var item = $scope.data.ads[type][id];
if(item) {
$scope.local.tab = type;
$scope["select"+type[0].toUpperCase()+type.substring(1)](item);
}
}
$scope.hasAddons = function() {
if($scope.data.addons)
for(var a in $scope.data.addons)
return true;
return false;
}
$scope.haveAddonSettingsChanged = function() {
try {
return !angular.equals($scope.context.addons,$scope.data.ads.addons);
} catch(e) {
return false;
}
}
$scope.resetAddons = function() {
$scope.context.addons = angular.merge({},$scope.data.ads.addons);
}
$scope.updateAddons = function() {
var backup = $scope.context.addons;
$scope.context.addons = {};
Call('/set-addons',{addons: backup},function(err,data) {
if(err)
$scope.context.addons = backup;
else
$scope.getAds();
});
}
$scope.countries = [ // Taken from https://gist.github.com/unceus/6501985
{name: 'Afghanistan', code: 'AF'},
{name: 'Aland Islands', code: 'AX'},
{name: 'Albania', code: 'AL'},
{name: 'Algeria', code: 'DZ'},
{name: 'American Samoa', code: 'AS'},
{name: 'Andorra', code: 'AD'},
{name: 'Angola', code: 'AO'},
{name: 'Anguilla', code: 'AI'},
{name: 'Antarctica', code: 'AQ'},
{name: 'Antigua and Barbuda', code: 'AG'},
{name: 'Argentina', code: 'AR'},
{name: 'Armenia', code: 'AM'},
{name: 'Aruba', code: 'AW'},
{name: 'Australia', code: 'AU'},
{name: 'Austria', code: 'AT'},
{name: 'Azerbaijan', code: 'AZ'},
{name: 'Bahamas', code: 'BS'},
{name: 'Bahrain', code: 'BH'},
{name: 'Bangladesh', code: 'BD'},
{name: 'Barbados', code: 'BB'},
{name: 'Belarus', code: 'BY'},
{name: 'Belgium', code: 'BE'},
{name: 'Belize', code: 'BZ'},
{name: 'Benin', code: 'BJ'},
{name: 'Bermuda', code: 'BM'},
{name: 'Bhutan', code: 'BT'},
{name: 'Bolivia', code: 'BO'},
{name: 'Bosnia and Herzegovina', code: 'BA'},
{name: 'Botswana', code: 'BW'},
{name: 'Bouvet Island', code: 'BV'},
{name: 'Brazil', code: 'BR'},
{name: 'British Indian Ocean Territory', code: 'IO'},
{name: 'Brunei Darussalam', code: 'BN'},
{name: 'Bulgaria', code: 'BG'},
{name: 'Burkina Faso', code: 'BF'},
{name: 'Burundi', code: 'BI'},
{name: 'Cambodia', code: 'KH'},
{name: 'Cameroon', code: 'CM'},
{name: 'Canada', code: 'CA'},
{name: 'Cape Verde', code: 'CV'},
{name: 'Cayman Islands', code: 'KY'},
{name: 'Central African Republic', code: 'CF'},
{name: 'Chad', code: 'TD'},
{name: 'Chile', code: 'CL'},
{name: 'China', code: 'CN'},
{name: 'Christmas Island', code: 'CX'},
{name: 'Cocos (Keeling) Islands', code: 'CC'},
{name: 'Colombia', code: 'CO'},
{name: 'Comoros', code: 'KM'},
{name: 'Congo', code: 'CG'},
{name: 'Congo, The Democratic Republic of the', code: 'CD'},
{name: 'Cook Islands', code: 'CK'},
{name: 'Costa Rica', code: 'CR'},
{name: 'Cote D\'Ivoire', code: 'CI'},
{name: 'Croatia', code: 'HR'},
{name: 'Cuba', code: 'CU'},
{name: 'Cyprus', code: 'CY'},
{name: 'Czech Republic', code: 'CZ'},
{name: 'Denmark', code: 'DK'},
{name: 'Djibouti', code: 'DJ'},
{name: 'Dominica', code: 'DM'},
{name: 'Dominican Republic', code: 'DO'},
{name: 'Ecuador', code: 'EC'},
{name: 'Egypt', code: 'EG'},
{name: 'El Salvador', code: 'SV'},
{name: 'Equatorial Guinea', code: 'GQ'},
{name: 'Eritrea', code: 'ER'},
{name: 'Estonia', code: 'EE'},
{name: 'Ethiopia', code: 'ET'},
{name: 'Falkland Islands (Malvinas)', code: 'FK'},
{name: 'Faroe Islands', code: 'FO'},
{name: 'Fiji', code: 'FJ'},
{name: 'Finland', code: 'FI'},
{name: 'France', code: 'FR'},
{name: 'French Guiana', code: 'GF'},
{name: 'French Polynesia', code: 'PF'},
{name: 'French Southern Territories', code: 'TF'},
{name: 'Gabon', code: 'GA'},
{name: 'Gambia', code: 'GM'},
{name: 'Georgia', code: 'GE'},
{name: 'Germany', code: 'DE'},
{name: 'Ghana', code: 'GH'},
{name: 'Gibraltar', code: 'GI'},
{name: 'Greece', code: 'GR'},
{name: 'Greenland', code: 'GL'},
{name: 'Grenada', code: 'GD'},
{name: 'Guadeloupe', code: 'GP'},
{name: 'Guam', code: 'GU'},
{name: 'Guatemala', code: 'GT'},
{name: 'Guernsey', code: 'GG'},
{name: 'Guinea', code: 'GN'},
{name: 'Guinea-Bissau', code: 'GW'},
{name: 'Guyana', code: 'GY'},
{name: 'Haiti', code: 'HT'},
{name: 'Heard Island and Mcdonald Islands', code: 'HM'},
{name: 'Holy See (Vatican City State)', code: 'VA'},
{name: 'Honduras', code: 'HN'},
{name: 'Hong Kong', code: 'HK'},
{name: 'Hungary', code: 'HU'},
{name: 'Iceland', code: 'IS'},
{name: 'India', code: 'IN'},
{name: 'Indonesia', code: 'ID'},
{name: 'Iran, Islamic Republic Of', code: 'IR'},