diff --git a/public/ajquery.js b/public/ajquery.js new file mode 100644 index 0000000..c2575e4 --- /dev/null +++ b/public/ajquery.js @@ -0,0 +1,13 @@ +'use strict'; + +var $ = function(sel, el) { + return (el || window.document).querySelector(sel); +}; +$.create = function(html) { + var div = document.createElement('div'); + div.innerHTML = html; + return div; +}; +var $$ = function(sel, el) { + return (el || window.document).querySelectorAll(sel); +}; diff --git a/public/hooks.js b/public/hooks.js new file mode 100644 index 0000000..d5391e0 --- /dev/null +++ b/public/hooks.js @@ -0,0 +1,301 @@ +(function() { + 'use strict'; + + // AJ Query + var $ = window.$; + var $$ = window.$$; + + var state = {}; + + var $grantTpl; + var $devTpl; + var $updateTpl; + var $headerTpl; + var $webhookTpl; + var $webhookHeaderTpl; + + // TODO add offset based on date selected (i.e. MDT -0500) + var tzdb = [ + 'America/New_York', + 'America/Chicago', + 'America/Denver', + 'America/Phoenix', + 'America/Los_Angeles', + 'America/Sao_Paulo', + 'Europe/London', + 'Europe/Berlin', + 'Europe/Moscow', + 'Asia/Dubai', + 'Asia/Kolkata', + 'Asia/Hong_Kong', + 'Asia/Tokyo', + 'Pacific/Auckland', + 'Australia/Sydney' + ]; + + function run() { + $headerTpl = $('.js-new-webhook .js-header').outerHTML; + + $webhookHeaderTpl = $('.js-schedule .js-webhook .js-header').outerHTML; + $('.js-schedule .js-webhooks .js-headers').innerHTML = ''; + $webhookTpl = $('.js-schedule .js-webhook').outerHTML; + $('.js-schedule .js-webhooks').innerHTML = ''; + + // after blanking all inner templates + $devTpl = $('.js-schedule').outerHTML; + + console.log('hello'); + + $('body').addEventListener('click', function(ev) { + if (ev.target.matches('.js-new-header')) { + newWebhookHeader(ev.target); + } else if (ev.target.matches('.js-rm-header')) { + rmWebhookHeader(ev.target); + } else if ( + ev.target.matches('.js-delete') && + ev.target.closest('.js-grant') + ) { + deleteGrant(ev.target.closest('.js-grant')); + } else if ( + ev.target.matches('.js-delete') && + ev.target.closest('.js-webhook') + ) { + deleteWebhook(ev.target.closest('.js-webhook')); + } else { + return; + } + ev.preventDefault(); + ev.stopPropagation(); + }); + + $('body').addEventListener('submit', function(ev) { + if (ev.target.matches('.js-new-schedule')) { + newSchedule(ev.target); + } else { + return; + } + ev.preventDefault(); + ev.stopPropagation(); + }); + } + + function newSchedule() { + var $hook = $('.js-new-webhook'); + var deviceId = $hook.closest('.js-schedule').querySelector('.js-id').value; + var hook = { + comment: $('.js-comment', $hook).value, + method: $('.js-method', $hook).value, + url: $('.js-url', $hook).value, + headers: {} + }; + $$('.js-header', $hook).forEach(function($head) { + var key = $('.js-key', $head).value; + var val = $('.js-value', $head).value; + if (key && val) { + hook.headers[key] = val; + } + }); + hook.body = $('.js-body-template', $hook).value; + // TODO update on template change and show preview + + var opts = { + method: 'POST', + headers: { + Accept: 'application/json', + Authorization: JSON.parse(localStorage.getItem('session')).access_token, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(hook), + cors: true + }; + + /* + state.account.devices + .filter(function(d) { + return d.accessToken == deviceId; + })[0] + .webhooks.push(hook); + + displayAccount(state.account); + return; + */ + + window + .fetch('/api/iot/devices/' + deviceId + '/webhooks', opts) + .then(function(resp) { + return resp + .json() + .then(function(data) { + if (!data.webhook) { + throw new Error('something bad happened'); + return; + } + + state.account.devices + .filter(function(d) { + return d.accessToken == deviceId; + })[0] + .webhooks.push(resp.data.webhook); + + displayAccount(state.account); + }) + .catch(function(e) { + window.alert(e.message); + }); + }); + } + + function newWebhookHeader($newHeader) { + var $hs = $newHeader.closest('.js-headers'); + var $h = $newHeader.closest('.js-header'); + var $div = document.createElement('div'); + $div.innerHTML = $headerTpl; + $hs.append($('.js-header', $div)); + $newHeader.hidden = true; + $('.js-rm-header', $h).hidden = false; + $('.js-key', $h).required = 'required'; + $('.js-value', $h).required = 'required'; + } + + function rmWebhookHeader($rmHeader) { + var $h = $rmHeader.closest('.js-header'); + $h.parentElement.removeChild($h); + } + function deleteWebhook($hook) { + var deviceId = $hook.closest('.js-schedule').querySelector('.js-id').value; + var id = $('.js-id', $hook).innerText; + var opts = { + method: 'DELETE', + headers: { + Accept: 'application/json', + Authorization: JSON.parse(localStorage.getItem('session')).access_token + }, + cors: true + }; + window + .fetch('/api/iot/devices/' + deviceId + '/webhooks/' + id, opts) + .then(function(resp) { + return resp.json().then(function(result) { + if (!result.webhook) { + console.error(result); + window.alert('something went wrong: ' + JSON.stringify(result)); + return; + } + var index = -1; + var dev = state.account.devices.filter(function(d, i) { + return d.accessToken == deviceId; + })[0]; + dev.webhooks.some(function(g, i) { + if (g.id === id) { + index = i; + return true; + } + }); + if (index > -1) { + dev.webhooks.splice(index, 1); + displayAccount(state.account); + } + }); + }); + } + + function displayAccount(data) { + state.account = data; + console.log('[debug] Display Account:'); + console.log(data); + var $devs = $('.js-schedules'); + $devs.innerHTML = ''; + data.devices.forEach(function(d) { + var $dev = $.create($devTpl); + $('.js-ieme', $dev).innerText = d.id; + $('.js-id', $dev).value = d.accessToken; + $('.js-update-url', $dev).innerText = d.updateUrl; + d.webhooks.forEach(function(h) { + var $hook = $.create($webhookTpl); + $('.js-id', $hook).innerText = h.id; + $('.js-comment', $hook).innerText = h.comment; + $('.js-method', $hook).innerText = h.method; + $('.js-url', $hook).innerText = h.url; + Object.keys(h.headers).forEach(function(k) { + var $header = $.create($webhookHeaderTpl); + var v = h.headers[k]; + $('.js-key', $header).innerText = k; + $('.js-value', $header).innerText = v; + $('.js-headers', $hook).innerHTML += $header.innerHTML; + }); + $('.js-body-template', $hook).innerText = h.body; + $('.js-webhooks', $dev).innerHTML += $hook.innerHTML; + }); + d.grants.forEach(function(g) { + var $grant = $.create($grantTpl); + $('.js-id', $grant).innerText = g.id; + $('.js-comment', $grant).innerText = g.comment; + $('.js-token', $grant).innerText = g.token; + //TODO Math.floor(Date.now() / 1000); + var url = + 'https://test.therootcompany.com/api/v1/devices/' + + d.accessToken + + '/updates?since=' + + 0; + $('.js-example-curl .js-example-url', $grant).innerText = url; + $('.js-example-curl .js-example-token', $grant).innerText = g.token; + $('.js-example-js .js-example-url', $grant).innerText = url; + $('.js-example-js .js-example-id', $grant).innerText = d.accessToken; + $('.js-example-js .js-example-token', $grant).innerText = g.token; + $('.js-grants', $dev).innerHTML += $grant.innerHTML; + }); + d.updates.slice(0, 10).forEach(function(u) { + var $update = $.create($updateTpl); + $('.js-update-details', $update).innerText = JSON.stringify(u); + $('.js-updates', $dev).innerHTML += $update.innerHTML; + }); + $devs.innerHTML += $dev.innerHTML; + }); + } + + console.info('[tzdb] requesting'); + window.fetch('./tzdb.json').then(function(resp) { + return resp.json().then(function(tzdb) { + console.info('[tzdb] received'); + var tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + var options = $$('.js-schedule-tz option'); + var valOpt = options[0].outerHTML; // UTC + var spaceOpt = options[1].outerHTML; // ---- + var innerHTML = $('.js-schedule-tz').innerHTML; + innerHTML = + '' + + spaceOpt + + innerHTML.replace(/>UTC/, '>    UTC'); + //$('.js-schedule-tz').innerHTML += spaceOpt; + //$('.js-schedule-tz').innerHTML += valOpt.replace(/UTC/g, 'custom'); + Object.keys(tzdb) + .sort() + .forEach(function(k) { + var parts = k.split(' '); + //var sep = '── ' + parts[0]; + var sep = parts[0]; + if (parts[0] !== parts[1]) { + sep += ' / ' + parts[1] + ' (DST)'; + } + //innerHTML += ''; + innerHTML += ''; + var areas = tzdb[k]; + areas.forEach(function(_tz) { + if (tz !== _tz) { + innerHTML += valOpt.replace(/UTC/g, _tz); + } + }); + innerHTML += ''; + }); + $('.js-schedule-tz').innerHTML = innerHTML; + + console.info('[tzdb] loaded'); + run(); + }); + }); + //window.addEventListener('load', run); +})(); diff --git a/public/index.html b/public/index.html index 600e744..e31ace4 100644 --- a/public/index.html +++ b/public/index.html @@ -4,7 +4,9 @@ Go Again -

Hello, World!

+

Go Again

+

Webhooks, on time!

+
-
 
+ +
 
+ +
+

Schedules

+
+
+ +
+ +
+ + +
+ +
+

Webhook

+
+
+

+ +

+ + + +
+
+
+ + +
+
+
+
+
+
+ +
+ +
+ + +
+
+ + + + +
+
+
+ + +
+
+
+ +
+ +
+
+
+ + + diff --git a/public/tzdb.json b/public/tzdb.json new file mode 100644 index 0000000..9d74ce2 --- /dev/null +++ b/public/tzdb.json @@ -0,0 +1,54 @@ +{"+00:00 +00:00":["Africa/Abidjan","Africa/Accra","Africa/Bissau","Africa/Monrovia","America/Danmarkshavn","Atlantic/Reykjavik"], +"+01:00 +01:00":["Africa/Algiers","Africa/Casablanca","Africa/Lagos","Africa/Ndjamena","Africa/Tunis"], +"+02:00 +02:00":["Africa/Cairo","Africa/Johannesburg","Africa/Khartoum","Africa/Maputo","Africa/Tripoli","Africa/Windhoek","Asia/Famagusta","Europe/Kaliningrad"], +"+01:00 +02:00":["Africa/Ceuta","Europe/Amsterdam","Europe/Andorra","Europe/Belgrade","Europe/Berlin","Europe/Brussels","Europe/Budapest","Europe/Copenhagen","Europe/Gibraltar","Europe/Luxembourg","Europe/Madrid","Europe/Malta","Europe/Monaco","Europe/Oslo","Europe/Paris","Europe/Prague","Europe/Rome","Europe/Stockholm","Europe/Tirane","Europe/Vienna","Europe/Warsaw","Europe/Zurich"], +"+00:00 +01:00":["Africa/El_Aaiun","Atlantic/Canary","Atlantic/Faroe","Atlantic/Madeira","Europe/Dublin","Europe/Lisbon","Europe/London"], +"+03:00 +03:00":["Africa/Juba","Africa/Nairobi","Antarctica/Syowa","Asia/Baghdad","Asia/Qatar","Asia/Riyadh","Europe/Istanbul","Europe/Kirov","Europe/Minsk","Europe/Moscow","Europe/Simferopol"], +"−10:00 −09:00":["America/Adak"], +"−09:00 −08:00":["America/Anchorage","America/Juneau","America/Metlakatla","America/Nome","America/Sitka","America/Yakutat"], +"−03:00 −03:00":["America/Araguaina","America/Argentina/Buenos_Aires","America/Argentina/Catamarca","America/Argentina/Cordoba","America/Argentina/Jujuy","America/Argentina/La_Rioja","America/Argentina/Mendoza","America/Argentina/Rio_Gallegos","America/Argentina/Salta","America/Argentina/San_Juan","America/Argentina/San_Luis","America/Argentina/Tucuman","America/Argentina/Ushuaia","America/Bahia","America/Belem","America/Cayenne","America/Fortaleza","America/Maceio","America/Montevideo","America/Paramaribo","America/Punta_Arenas","America/Recife","America/Santarem","Antarctica/Palmer","Antarctica/Rothera","Atlantic/Stanley"], +"−04:00 −03:00":["America/Asuncion","America/Campo_Grande","America/Cuiaba","America/Glace_Bay","America/Goose_Bay","America/Halifax","America/Moncton","America/Santiago","America/Thule","Atlantic/Bermuda"], +"−05:00 −05:00":["America/Atikokan","America/Bogota","America/Cancun","America/Eirunepe","America/Guayaquil","America/Jamaica","America/Lima","America/Panama","America/Rio_Branco"], +"−06:00 −05:00":["America/Bahia_Banderas","America/Chicago","America/Indiana/Knox","America/Indiana/Tell_City","America/Matamoros","America/Menominee","America/Merida","America/Mexico_City","America/Monterrey","America/North_Dakota/Beulah","America/North_Dakota/Center","America/North_Dakota/New_Salem","America/Rainy_River","America/Rankin_Inlet","America/Resolute","America/Winnipeg","Pacific/Easter"], +"−04:00 −04:00":["America/Barbados","America/Blanc-Sablon","America/Boa_Vista","America/Caracas","America/Curacao","America/Guyana","America/La_Paz","America/Manaus","America/Martinique","America/Port_of_Spain","America/Porto_Velho","America/Puerto_Rico","America/Santo_Domingo"], +"−06:00 −06:00":["America/Belize","America/Costa_Rica","America/El_Salvador","America/Guatemala","America/Managua","America/Regina","America/Swift_Current","America/Tegucigalpa","Pacific/Galapagos"], +"−07:00 −06:00":["America/Boise","America/Cambridge_Bay","America/Chihuahua","America/Denver","America/Edmonton","America/Inuvik","America/Mazatlan","America/Ojinaga","America/Yellowknife"], +"−07:00 −07:00":["America/Creston","America/Dawson_Creek","America/Fort_Nelson","America/Hermosillo","America/Phoenix"], +"−08:00 −07:00":["America/Dawson","America/Los_Angeles","America/Tijuana","America/Vancouver","America/Whitehorse"], +"−05:00 −04:00":["America/Detroit","America/Grand_Turk","America/Havana","America/Indiana/Indianapolis","America/Indiana/Marengo","America/Indiana/Petersburg","America/Indiana/Vevay","America/Indiana/Vincennes","America/Indiana/Winamac","America/Iqaluit","America/Kentucky/Louisville","America/Kentucky/Monticello","America/Nassau","America/New_York","America/Nipigon","America/Pangnirtung","America/Port-au-Prince","America/Thunder_Bay","America/Toronto"], +"−03:00 −02:00":["America/Godthab","America/Miquelon","America/Sao_Paulo"], +"−02:00 −02:00":["America/Noronha","Atlantic/South_Georgia"], +"−01:00 +00:00":["America/Scoresbysund","Atlantic/Azores"], +"−03:30 −02:30":["America/St_Johns"], +"+11:00 +11:00":["Antarctica/Casey","Antarctica/Macquarie","Asia/Magadan","Asia/Sakhalin","Asia/Srednekolymsk","Pacific/Bougainville","Pacific/Efate","Pacific/Guadalcanal","Pacific/Kosrae","Pacific/Norfolk","Pacific/Noumea","Pacific/Pohnpei"], +"+07:00 +07:00":["Antarctica/Davis","Asia/Bangkok","Asia/Barnaul","Asia/Ho_Chi_Minh","Asia/Hovd","Asia/Jakarta","Asia/Krasnoyarsk","Asia/Novokuznetsk","Asia/Novosibirsk","Asia/Pontianak","Asia/Tomsk","Indian/Christmas"], +"+10:00 +10:00":["Antarctica/DumontDUrville","Asia/Ust-Nera","Asia/Vladivostok","Australia/Brisbane","Australia/Lindeman","Pacific/Chuuk","Pacific/Guam","Pacific/Port_Moresby"], +"+05:00 +05:00":["Antarctica/Mawson","Asia/Aqtau","Asia/Aqtobe","Asia/Ashgabat","Asia/Atyrau","Asia/Dushanbe","Asia/Karachi","Asia/Oral","Asia/Qyzylorda","Asia/Samarkand","Asia/Tashkent","Asia/Yekaterinburg","Indian/Kerguelen","Indian/Maldives"], +"+00:00 +02:00":["Antarctica/Troll"], +"+06:00 +06:00":["Antarctica/Vostok","Asia/Almaty","Asia/Bishkek","Asia/Dhaka","Asia/Omsk","Asia/Thimphu","Asia/Urumqi","Indian/Chagos"], +"+02:00 +03:00":["Asia/Amman","Asia/Beirut","Asia/Damascus","Asia/Gaza","Asia/Hebron","Asia/Jerusalem","Europe/Athens","Europe/Bucharest","Europe/Chisinau","Europe/Helsinki","Europe/Kiev","Asia/Nicosia","Europe/Riga","Europe/Sofia","Europe/Tallinn","Europe/Uzhgorod","Europe/Vilnius","Europe/Zaporozhye"], +"+12:00 +12:00":["Asia/Anadyr","Asia/Kamchatka","Pacific/Funafuti","Pacific/Kwajalein","Pacific/Majuro","Pacific/Nauru","Pacific/Tarawa","Pacific/Wake","Pacific/Wallis"], +"+04:00 +04:00":["Asia/Baku","Asia/Dubai","Asia/Tbilisi","Asia/Yerevan","Europe/Astrakhan","Europe/Samara","Europe/Saratov","Europe/Ulyanovsk","Europe/Volgograd","Indian/Mahe","Indian/Mauritius","Indian/Reunion"], +"+08:00 +08:00":["Asia/Brunei","Asia/Choibalsan","Asia/Hong_Kong","Asia/Irkutsk","Asia/Kuala_Lumpur","Asia/Kuching","Asia/Macau","Asia/Makassar","Asia/Manila","Asia/Shanghai","Asia/Singapore","Asia/Taipei","Asia/Ulaanbaatar","Australia/Perth"], +"+09:00 +09:00":["Asia/Chita","Asia/Dili","Asia/Jayapura","Asia/Khandyga","Asia/Pyongyang","Asia/Seoul","Asia/Tokyo","Asia/Yakutsk","Pacific/Palau"], +"+05:30 +05:30":["Asia/Colombo","Asia/Kolkata"], +"+04:30 +04:30":["Asia/Kabul"], +"+05:45 +05:45":["Asia/Kathmandu"], +"+03:30 +04:30":["Asia/Tehran"], +"+06:30 +06:30":["Asia/Yangon","Indian/Cocos"], +"−01:00 −01:00":["Atlantic/Cape_Verde"], +"+09:30 +10:30":["Australia/Adelaide","Australia/Broken_Hill"], +"+10:00 +11:00":["Australia/Currie","Australia/Hobart","Australia/Melbourne","Australia/Sydney"], +"+09:30 +09:30":["Australia/Darwin"], +"+08:45 +08:45":["Australia/Eucla"], +"+10:30 +11:00":["Australia/Lord_Howe"], +"+13:00 +14:00":["Pacific/Apia","Pacific/Tongatapu"], +"+12:00 +13:00":["Pacific/Auckland","Pacific/Fiji"], +"+12:45 +13:45":["Pacific/Chatham"], +"+13:00 +13:00":["Pacific/Enderbury","Pacific/Fakaofo"], +"−09:00 −09:00":["Pacific/Gambier"], +"−10:00 −10:00":["Pacific/Honolulu","Pacific/Rarotonga","Pacific/Tahiti"], +"+14:00 +14:00":["Pacific/Kiritimati"], +"−09:30 −09:30":["Pacific/Marquesas"], +"−11:00 −11:00":["Pacific/Niue","Pacific/Pago_Pago"], +"−08:00 −08:00":["Pacific/Pitcairn"]} diff --git a/public/tzdb/scrape-wikipedia.js b/public/tzdb/scrape-wikipedia.js new file mode 100644 index 0000000..89ea960 --- /dev/null +++ b/public/tzdb/scrape-wikipedia.js @@ -0,0 +1,31 @@ +// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + +var zones = []; +var zoneMap = {}; +var all = document.body + .querySelector('.wikitable.sortable.jquery-tablesorter') + .querySelectorAll('tr'); +all = [].slice.call(all, 1); // remove header + +all.forEach(function(el) { + if (/Alias|Deprecated|Etc\//.test(el.outerText)) { + $(el).remove(); + return; + } + + var fields = [].slice.call(el.querySelectorAll('td')); + var f = fields.map(function(td) { + return td.innerText.trim(); + }); + var id = f[5] + ' ' + f[6]; + if (!zoneMap[id]) { + zones.push([f[2], f[5], f[6]]); + } + zoneMap[id] = zoneMap[id] || []; + zoneMap[id].push(f[2]); +}); + +// console.log(JSON.stringify(zones)); +console.log('Total:', all.length); +console.log('Unique:', Object.keys(zoneMap).length); +console.log(zoneMap);