├── robots.txt
├── readme-assets
└── images
│ ├── WIP_dec-2015.png
│ ├── pixelated_rescompare_v0.1.png
│ ├── pixelated_rescompare_v1.0.png
│ └── pixelated_rescompare_v1.0.3.png
├── humans.txt
├── crossdomain.xml
├── js
├── sortable.number.js
├── main.js
├── csv.js
├── sortable.min.js
├── dom.js
└── aws.js
├── css
├── main.css
├── sortable.css
├── boilerplate.css
└── normalize.css
├── LICENSE
├── 404.html
├── main.css
├── README.md
├── dev.html
├── index.html
└── main.js
/robots.txt:
--------------------------------------------------------------------------------
1 | # www.robotstxt.org/
2 |
3 | # Allow crawling of all content
4 | User-agent: *
5 | Disallow:
6 |
--------------------------------------------------------------------------------
/readme-assets/images/WIP_dec-2015.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckelner/AWSResco/HEAD/readme-assets/images/WIP_dec-2015.png
--------------------------------------------------------------------------------
/readme-assets/images/pixelated_rescompare_v0.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckelner/AWSResco/HEAD/readme-assets/images/pixelated_rescompare_v0.1.png
--------------------------------------------------------------------------------
/readme-assets/images/pixelated_rescompare_v1.0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckelner/AWSResco/HEAD/readme-assets/images/pixelated_rescompare_v1.0.png
--------------------------------------------------------------------------------
/readme-assets/images/pixelated_rescompare_v1.0.3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckelner/AWSResco/HEAD/readme-assets/images/pixelated_rescompare_v1.0.3.png
--------------------------------------------------------------------------------
/humans.txt:
--------------------------------------------------------------------------------
1 | # humanstxt.org/
2 | # The humans responsible & technology colophon
3 |
4 | # TEAM
5 |
6 | Chris Kelner - Twitter: @kelner - GitHub: @ckelner
7 |
8 | # TECHNOLOGY COLOPHON
9 |
10 | CSS3, HTML5
11 | AngularJS, Modernizr, Normalize.css
12 |
--------------------------------------------------------------------------------
/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/js/sortable.number.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | var cleanNumber = function(i) {
3 | return i.replace(/[^\-?0-9.]/g, '');
4 | },
5 |
6 | compareNumber = function(a, b) {
7 | a = parseFloat(a);
8 | b = parseFloat(b);
9 |
10 | a = isNaN(a) ? 0 : a;
11 | b = isNaN(b) ? 0 : b;
12 |
13 | return a - b;
14 | };
15 |
16 | Tablesort.extend('number', function(item) {
17 | return item.match(/^-?[£\x24Û¢´€]?\d+\s*([,\.]\d{0,2})/) || // Prefixed currency
18 | item.match(/^-?\d+\s*([,\.]\d{0,2})?[£\x24Û¢´€]/) || // Suffixed currency
19 | item.match(/^-?(\d)*-?([,\.]){0,1}-?(\d)+([E,e][\-+][\d]+)?%?$/); // Number
20 | }, function(a, b) {
21 | a = cleanNumber(a);
22 | b = cleanNumber(b);
23 |
24 | return compareNumber(b, a);
25 | });
26 | }());
27 |
--------------------------------------------------------------------------------
/css/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 1px !important;
3 | }
4 | .page-header {
5 | margin-top: 1px !important;
6 | margin-bottom: 10px !important;
7 | padding-bottom: 0px !important;
8 | }
9 | .col-padding-top-25 {
10 | margin-top: 25px !important;
11 | }
12 | h1 {
13 | line-height: 0.8 !important;
14 | margin-top: 5px !important;
15 | }
16 | h1 a:hover {
17 | text-decoration: none !important;
18 | }
19 | #awsQueryResults {
20 | display: none;
21 | }
22 | #pleaseWait {
23 | display: none;
24 | font-size: 2em;
25 | }
26 | #tabDataRunNames {
27 | width: 200px;
28 | }
29 | #errorAccessSecretKey {
30 | display: none;
31 | }
32 | #errorCredentials {
33 | display: none;
34 | }
35 | #security {
36 | font-size: .5em;
37 | float: right;
38 | position: relative;
39 | top: 15px;
40 | }
41 | .right .github-fork-ribbon {
42 | background-color: #f80;
43 | }
44 |
--------------------------------------------------------------------------------
/css/sortable.css:
--------------------------------------------------------------------------------
1 | th.sort-header::-moz-selection {
2 | background: transparent;
3 | }
4 | th.sort-header::selection {
5 | background: transparent;
6 | }
7 | th.sort-header {
8 | cursor: pointer;
9 | }
10 | th.sort-up {
11 | background-color: #5bc0de !important;
12 | }
13 | th.sort-down {
14 | background-color: #5bc0de !important;
15 | }
16 | th.sort-header::-moz-selection, th.sort-header::selection {
17 | background: transparent;
18 | }
19 | table th.sort-header:after {
20 | content: '';
21 | float: right;
22 | margin-top: 7px;
23 | border-width: 0 4px 4px;
24 | border-style: solid;
25 | border-color: #404040 transparent;
26 | visibility: hidden;
27 | }
28 | table th.sort-header:hover:after {
29 | visibility: visible;
30 | }
31 | table th.sort-up:after, table th.sort-down:after, table th.sort-down:hover:after {
32 | visibility: visible;
33 | opacity: 0.4;
34 | }
35 | table th.sort-up:after {
36 | border-bottom: none;
37 | border-width: 4px 4px 0;
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Chris Kelner
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/js/main.js:
--------------------------------------------------------------------------------
1 | window.onload = function() {
2 | init();
3 | };
4 |
5 | function init() {
6 | document.getElementById("awsQueryButton").addEventListener("click", function() {
7 | awsQueryButtonAction();
8 | });
9 | }
10 |
11 | function validateKeys(val) {
12 | if (val == undefined || val == null || val == "") {
13 | showAccessSecretErrorDiv();
14 | return null;
15 | }
16 | return val;
17 | }
18 |
19 | function getAccessKeyValue() {
20 | return validateKeys(document.getElementById('awsAccessKey').value);
21 | }
22 |
23 | function getSecretKeyValue() {
24 | return validateKeys(document.getElementById('awsSecretKey').value);
25 | }
26 |
27 | function getTokenValue() {
28 | return document.getElementById('awsToken').value;
29 | }
30 |
31 | function getRegionValue() {
32 | return document.getElementById('regionSelect').value;
33 | }
34 |
35 | function awsQueryButtonAction() {
36 | resetCredChecks();
37 | showPleaseWaitDiv();
38 | hideAccessSecretErrorDiv();
39 | hideCredentialsErrorDiv();
40 | testAWSCredentials(getAccessKeyValue(), getSecretKeyValue(), getTokenValue(), getRegionValue());
41 | waitForCredCheck();
42 | // always return false to avoid page refresh
43 | return false;
44 | }
45 |
46 | // utils
47 | Array.prototype.contains = function(obj) {
48 | var i = this.length;
49 | while (i--) {
50 | if (this[i] === obj) {
51 | return true;
52 | }
53 | }
54 | return false;
55 | }
56 |
--------------------------------------------------------------------------------
/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Page Not Found
6 |
7 |
54 |
55 |
56 |
Page Not Found
57 |
Sorry, but the page you were trying to view does not exist.
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/js/csv.js:
--------------------------------------------------------------------------------
1 | function getTableDataAsCSV() {
2 | var dataTable = document.getElementById("resCoTable");
3 | var csvData = "data:text/csv;charset=utf-8,";
4 | for (var i = 0, row; row = dataTable.rows[i]; i++) {
5 | for (var j = 0, col; col = row.cells[j]; j++) {
6 | if (j > 0) {
7 | csvData += ",";
8 | }
9 | var cellContents = col.innerHTML.toString().trim();
10 | // sanitize contents
11 | if (cellContents.indexOf(',') >= 0 || cellContents.indexOf('\"') >= 0 || cellContents.indexOf('\n') >= 0) {
12 | cellContents = "\"" + cellContents.replace(/\"/g, "\"\"") + "\"";
13 | }
14 | if (cellContents.indexOf(' ') >= 0) {
15 | cellContents = cellContents.replace(/ /g, "");
16 | }
17 | csvData += cellContents;
18 | }
19 | csvData += '\n';
20 | }
21 | var link = document.createElement('a');
22 | link.setAttribute('href', encodeURI(csvData));
23 | link.setAttribute('download', buildCSVFileName());
24 | link.click();
25 | }
26 |
27 | function buildCSVFileName() {
28 | var fileName = "awsresco-export_";
29 | var d = new Date();
30 | fileName += d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate() +
31 | "_" + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
32 | return fileName;
33 | }
34 |
35 | // Polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim
36 | if (!String.prototype.trim) {
37 | String.prototype.trim = function() {
38 | return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/js/sortable.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * tablesort v4.0.0 (2015-12-17)
3 | * Copyright (c) 2015 ; Licensed MIT
4 | */!function(){function a(b,c){if(!(this instanceof a))return new a(b,c);if(!b||"TABLE"!==b.tagName)throw new Error("Element must be a table");this.init(b,c||{})}var b=[],c=function(a){var b;return window.CustomEvent&&"function"==typeof window.CustomEvent?b=new CustomEvent(a):(b=document.createEvent("CustomEvent"),b.initCustomEvent(a,!1,!1,void 0)),b},d=function(a){return a.getAttribute("data-sort")||a.textContent||a.innerText||""},e=function(a,b){return a=a.toLowerCase(),b=b.toLowerCase(),a===b?0:b>a?1:-1},f=function(a,b){return function(c,d){var e=a(c.td,d.td);return 0===e?b?d.index-c.index:c.index-d.index:e}};a.extend=function(a,c,d){if("function"!=typeof c||"function"!=typeof d)throw new Error("Pattern and sort must be a function");b.push({name:a,pattern:c,sort:d})},a.prototype={init:function(a,b){var c,d,e,f,g=this;if(g.table=a,g.thead=!1,g.options=b,a.rows&&a.rows.length>0&&(a.tHead&&a.tHead.rows.length>0?(c=a.tHead.rows[a.tHead.rows.length-1],g.thead=!0):c=a.rows[0]),c){var h=function(){g.current&&g.current!==this&&(g.current.classList.remove("sort-up"),g.current.classList.remove("sort-down")),g.current=this,g.sortTable(this)};for(e=0;e0&&m.push(l),n++;if(!m)return}for(n=0;nn;n++)s[n]?(l=s[n],u++):l=r[n-u].tr,i.table.tBodies[0].appendChild(l);i.table.dispatchEvent(c("afterSort"))}},refresh:function(){void 0!==this.current&&this.sortTable(this.current,!0)}},"undefined"!=typeof module&&module.exports?module.exports=a:window.Tablesort=a}();
5 |
--------------------------------------------------------------------------------
/main.css:
--------------------------------------------------------------------------------
1 | /*! HTML5 Boilerplate v5.2.0 | MIT License | https://html5boilerplate.com/ */html{color:#222;font-size:1em;line-height:1.4}::-moz-selection{background:#b3d4fc;text-shadow:none}::selection{background:#b3d4fc;text-shadow:none}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}.hidden{display:none !important}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:" ";display:table}.clearfix:after{clear:both}
2 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}
3 | th.sort-header::-moz-selection{background:transparent}th.sort-header::selection{background:transparent}th.sort-header{cursor:pointer}th.sort-up{background-color:#5bc0de !important}th.sort-down{background-color:#5bc0de !important}th.sort-header::-moz-selection,th.sort-header::selection{background:transparent}table th.sort-header:after{content:'';float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#404040 transparent;visibility:hidden}table th.sort-header:hover:after{visibility:visible}table th.sort-up:after,table th.sort-down:after,table th.sort-down:hover:after{visibility:visible;opacity:.4}table th.sort-up:after{border-bottom:0;border-width:4px 4px 0}
4 | body{padding-top:1px !important}.page-header{margin-top:1px !important;margin-bottom:10px !important;padding-bottom:0 !important}.col-padding-top-25{margin-top:25px !important}h1{line-height:.8 !important;margin-top:5px !important}h1 a:hover{text-decoration:none !important}#awsQueryResults{display:none}#pleaseWait{display:none;font-size:2em}#tabDataRunNames{width:200px}#errorAccessSecretKey{display:none}#errorCredentials{display:none}#security{font-size:.5em;float:right;position:relative;top:15px}.right .github-fork-ribbon{background-color:#f80}
5 |
--------------------------------------------------------------------------------
/css/boilerplate.css:
--------------------------------------------------------------------------------
1 | /*! HTML5 Boilerplate v5.2.0 | MIT License | https://html5boilerplate.com/ */
2 |
3 | /*
4 | * What follows is the result of much research on cross-browser styling.
5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
6 | * Kroc Camen, and the H5BP dev community and team.
7 | */
8 |
9 | /* ==========================================================================
10 | Base styles: opinionated defaults
11 | ========================================================================== */
12 |
13 | html {
14 | color: #222;
15 | font-size: 1em;
16 | line-height: 1.4;
17 | }
18 |
19 | /*
20 | * Remove text-shadow in selection highlight:
21 | * https://twitter.com/miketaylr/status/12228805301
22 | *
23 | * These selection rule sets have to be separate.
24 | * Customize the background color to match your design.
25 | */
26 |
27 | ::-moz-selection {
28 | background: #b3d4fc;
29 | text-shadow: none;
30 | }
31 |
32 | ::selection {
33 | background: #b3d4fc;
34 | text-shadow: none;
35 | }
36 |
37 | /*
38 | * A better looking default horizontal rule
39 | */
40 |
41 | hr {
42 | display: block;
43 | height: 1px;
44 | border: 0;
45 | border-top: 1px solid #ccc;
46 | margin: 1em 0;
47 | padding: 0;
48 | }
49 |
50 | /*
51 | * Remove the gap between audio, canvas, iframes,
52 | * images, videos and the bottom of their containers:
53 | * https://github.com/h5bp/html5-boilerplate/issues/440
54 | */
55 |
56 | audio,
57 | canvas,
58 | iframe,
59 | img,
60 | svg,
61 | video {
62 | vertical-align: middle;
63 | }
64 |
65 | /*
66 | * Remove default fieldset styles.
67 | */
68 |
69 | fieldset {
70 | border: 0;
71 | margin: 0;
72 | padding: 0;
73 | }
74 |
75 | /*
76 | * Allow only vertical resizing of textareas.
77 | */
78 |
79 | textarea {
80 | resize: vertical;
81 | }
82 |
83 | /* ==========================================================================
84 | Helper classes
85 | ========================================================================== */
86 |
87 | /*
88 | * Hide visually and from screen readers:
89 | */
90 |
91 | .hidden {
92 | display: none !important;
93 | }
94 |
95 | /*
96 | * Hide only visually, but have it available for screen readers:
97 | * http://snook.ca/archives/html_and_css/hiding-content-for-accessibility
98 | */
99 |
100 | .visuallyhidden {
101 | border: 0;
102 | clip: rect(0 0 0 0);
103 | height: 1px;
104 | margin: -1px;
105 | overflow: hidden;
106 | padding: 0;
107 | position: absolute;
108 | width: 1px;
109 | }
110 |
111 | /*
112 | * Extends the .visuallyhidden class to allow the element
113 | * to be focusable when navigated to via the keyboard:
114 | * https://www.drupal.org/node/897638
115 | */
116 |
117 | .visuallyhidden.focusable:active,
118 | .visuallyhidden.focusable:focus {
119 | clip: auto;
120 | height: auto;
121 | margin: 0;
122 | overflow: visible;
123 | position: static;
124 | width: auto;
125 | }
126 |
127 | /*
128 | * Hide visually and from screen readers, but maintain layout
129 | */
130 |
131 | .invisible {
132 | visibility: hidden;
133 | }
134 |
135 | /*
136 | * Clearfix: contain floats
137 | *
138 | * For modern browsers
139 | * 1. The space content is one way to avoid an Opera bug when the
140 | * `contenteditable` attribute is included anywhere else in the document.
141 | * Otherwise it causes space to appear at the top and bottom of elements
142 | * that receive the `clearfix` class.
143 | * 2. The use of `table` rather than `block` is only necessary if using
144 | * `:before` to contain the top-margins of child elements.
145 | */
146 |
147 | .clearfix:before,
148 | .clearfix:after {
149 | content: " "; /* 1 */
150 | display: table; /* 2 */
151 | }
152 |
153 | .clearfix:after {
154 | clear: both;
155 | }
156 |
--------------------------------------------------------------------------------
/js/dom.js:
--------------------------------------------------------------------------------
1 | var g_PleaseWaitIntervalId = null;
2 | var g_ReservationTotal = 0; //shitty hax
3 | var g_RunningTotal = 0; //shitty hax
4 | var g_Zones = [];
5 |
6 | function displayEc2DataTable(data) {
7 | getEc2DataTableBody().innerHTML = "";
8 | getEc2DataTableBody().innerHTML += buildEc2DataTable(data);
9 | getTotalDiv().innerHTML = "Total Reservations: " + g_ReservationTotal +
10 | " --- Total Running Instances: " + g_RunningTotal +
11 | " --- ";
13 | // let it be sortable :)
14 | new Tablesort(document.getElementById('resCoTable'));
15 | document.getElementById("differentialHeader").click();
16 | hidePleaseWaitDiv();
17 | setupDownloadAsCSVButtonClick();
18 | showAwsQueryResults();
19 | }
20 |
21 | function setupDownloadAsCSVButtonClick() {
22 | document.getElementById("downloadAsCSV").addEventListener("click", function() {
23 | getTableDataAsCSV();
24 | });
25 | }
26 |
27 | // Currently not in use
28 | function buildZoneCheckListWithButton() {
29 | var html = "";
37 | return html;
38 | }
39 |
40 | function resetEc2DataTable() {
41 | hideAwsQueryResults();
42 | getEc2DataTableBody().innerHTML = "";
43 | getTotalDiv().innerHTML = "";
44 | g_ReservationTotal = 0;
45 | g_RunningTotal = 0;
46 | }
47 |
48 | function showQueryError(htmlSnippit) {
49 | getEc2DataTableBody().innerHTML = htmlSnippit;
50 | }
51 |
52 | // TODO: Make this prettier :)
53 | function buildEc2DataTable(data) {
54 | var htmlSnippit = "";
55 | var dataLen = data.length;
56 | for (var i = 0; i < dataLen; i++) {
57 | htmlSnippit += "
" +
58 | data[i]["count"] +
59 | "
" + data[i]["running"] +
60 | "
" + data[i]["diff"] +
61 | "
" + data[i]["type"] +
62 | "
" + data[i]["az"] +
63 | "
" + data[i]["windows"].toString() +
64 | "
" + data[i]["vpc"].toString() +
65 | "
" + data[i]["running_ids"].join(", ") +
66 | "
" + data[i]["running_names"].join(", ") +
67 | "
";
68 | g_ReservationTotal += data[i]["count"];
69 | g_RunningTotal += data[i]["running"];
70 | if (!g_Zones.contains(data[i]["az"])) {
71 | g_Zones.push(data[i]["az"]);
72 | }
73 | }
74 | return htmlSnippit;
75 | }
76 |
77 | function setPleaseWaitDivUpdateInterval() {
78 | g_PleaseWaitIntervalId = setInterval(updatePleaseWaitDiv, 1000);
79 | }
80 |
81 | function updatePleaseWaitDiv() {
82 | if (getPleaseWaitDiv().innerHTML.toLowerCase().indexOf("please wait.....") == -1) {
83 | getPleaseWaitDiv().innerHTML += "."
84 | } else {
85 | getPleaseWaitDiv().innerHTML = "Please Wait"
86 | }
87 | }
88 |
89 | function getTotalDiv() {
90 | return document.getElementById("totalData");
91 | }
92 |
93 | function getPleaseWaitDiv() {
94 | return document.getElementById("pleaseWait");
95 | }
96 |
97 | function showPleaseWaitDiv() {
98 | setPleaseWaitDivUpdateInterval();
99 | getPleaseWaitDiv().style.display = "block";
100 | }
101 |
102 | function hidePleaseWaitDiv() {
103 | clearPleaseWaitInterval();
104 | getPleaseWaitDiv().style.display = "none";
105 | }
106 |
107 | function clearPleaseWaitInterval() {
108 | clearInterval(g_PleaseWaitIntervalId);
109 | }
110 |
111 | function getCredentialsErrorDiv() {
112 | return document.getElementById("errorCredentials");
113 | }
114 |
115 | function showCredentialsErrorDiv() {
116 | hidePleaseWaitDiv();
117 | getCredentialsErrorDiv().style.display = "block";
118 | }
119 |
120 | function hideCredentialsErrorDiv() {
121 | getCredentialsErrorDiv().style.display = "none";
122 | }
123 |
124 | function getAccessSecretErrorDiv() {
125 | return document.getElementById("errorAccessSecretKey");
126 | }
127 |
128 | function showAccessSecretErrorDiv() {
129 | hidePleaseWaitDiv();
130 | getAccessSecretErrorDiv().style.display = "block";
131 | }
132 |
133 | function hideAccessSecretErrorDiv() {
134 | getAccessSecretErrorDiv().style.display = "none";
135 | }
136 |
137 | function getEc2DataTableBody() {
138 | return document.getElementById("ec2DataTableBody");
139 | }
140 |
141 | function getAwsQueryResultsDiv() {
142 | return document.getElementById("awsQueryResults");
143 | }
144 |
145 | function showAwsQueryResults() {
146 | getAwsQueryResultsDiv().style.display = "block";
147 | }
148 |
149 | function hideAwsQueryResults() {
150 | getAwsQueryResultsDiv().style.display = "none";
151 | }
152 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AWSResco
2 |
3 | ### Table of Contents
4 | - [What](#what)
5 | - [Screenshot Example](#screenshot-example)
6 | - [Why?](#why)
7 | - [How it works](#how-it-works)
8 | - [Security?](#security)
9 | - [AWS IAM Policy for access](#aws-iam-policy-for-access)
10 | - [Gaps](#gaps)
11 | - [Development](#development)
12 | - [Test](#test)
13 | - [Build](#build)
14 | - [Deploy](#deploy)
15 | - [Build & Deploy Gaps](#build--deploy-gaps)
16 |
17 | * * *
18 |
19 | ## What
20 | AWSResco is a standalone SPA using the client side AWS JS SDK to get instance and reservation info for JIT comparison of running infrastructure. This comparison provides a "Differential" value to the user to see where they are over or under allotted on their reservations.
21 |
22 | The latest version is at: [http://awsresco.s3-website-us-east-1.amazonaws.com/](http://awsresco.s3-website-us-east-1.amazonaws.com/)
23 |
24 | ### Screenshot Example
25 | 
26 |
27 | ## Why?
28 | In my time with working with AWS, the question of "Am I utilizing my reservations correctly?" has come up many times. AWS doesn't provide an interface, tool, or output that I am aware of that can provide this information in an easily consumable way. The closest tool AWS has is [Reservation Utilization Reports](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/usage-reports-ri.html) which works well in some cases, mostly smaller organizations, but lacks granularity for others.
29 |
30 | ## How it works
31 | AWS Reservation Comparison (AWSResco) takes a look at AWS reservations for a given account and compares them against running infrastructure. It combines multiple reservation purchases of the same `Instance Type`, `Availability Zone`, `Platform` (windows or linux), and `VPC` (EC2-Classic or VPC) into a single object, then looks at all running instances and matches them with any reservation based on the same values.
32 |
33 | This information is then presented to the user as:
34 | - The number of reservations of a given type, zone, platform, and vpc: `Reservation Count`
35 | - The number of running instances of a given type, zone, platform, and vpc: `Running Instances`
36 | - A differential, the number of reservations minus the number of running instances: `Differential`
37 | - The hard data on each reservation & running instance sets: `Type`, `Zone`, `Windows`, `VPC`, `Runnings Ids`, and `Running Names`
38 |
39 | ## Security?
40 | While AWSResco itself doesn't use HTTPS from S3 to the client's browser, all communication from the client's browser to AWS is over HTTPS.
41 | This means that your access and secret keys are never sent from your browser over anything other than HTTPS.
42 | This is enabled via [http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#sslEnabled-property](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#sslEnabled-property).
43 |
44 | If you are worried about what the tool does with your keys, please feel free to review the code for any malicious usage. If you find anything concering, feel free to open an issue or pull request.
45 |
46 | ## AWS IAM Policy for access
47 | The policy you use for AWSResco should follow the least privilege access rules. In the case of AWSResco, the only access needed is for the the [describeInstances](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/EC2.html#describeInstances-property) and the [describeReservedInstances](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/EC2.html#describeReservedInstances-property) API calls. The following is an example of the policy to use:
48 |
49 | ```
50 | {
51 | "Version": "2012-10-17",
52 | "Statement": [
53 | {
54 | "Sid": "Stmt1452989493668",
55 | "Action": [
56 | "ec2:DescribeInstances",
57 | "ec2:DescribeReservedInstances"
58 | ],
59 | "Effect": "Allow",
60 | "Resource": "*"
61 | }
62 | ]
63 | }
64 | ```
65 |
66 | If you are using an IAM instance profile you need to use the (access key, secret key, token) tuple to authenticate.
67 | You can retrieve the values from the EC2 metadata API. First, find the name of your instance-profile:
68 |
69 | `curl http://169.254.169.254/latest/meta-data/iam/security-credentials/`
70 |
71 | Then, plug in the name at the end of the URL
72 |
73 | `curl http://169.254.169.254/latest/meta-data/iam/security-credentials/instance-profile`
74 |
75 | The resulting JSON will have the necessary credentials to utilize the site.
76 |
77 | ## Gaps
78 | Currently AWSResco does not take into account `OfferingType`, it assumes that only `Heavy Utilization` is being used as that was the original use case for the tool. There are plans to support all `OfferingType` variations - see [Issue#3](https://github.com/ckelner/AWSResco/issues/3).
79 |
80 | ## Development
81 | - Run with: `python -m SimpleHTTPServer` (or your favorite web server)
82 | - Navigate to: `localhost:8000/dev.html` (or your favorite web server's configuration)
83 |
84 | ## Test
85 | - Manual process to test
86 |
87 | ## Build
88 | - Run `bash build.sh` which will uglify css and javascript
89 |
90 | ## Deploy
91 | - Manual process of pushing to S3
92 |
93 | ## Build & Deploy Gaps
94 | - Changes to `dev.html` need to be copied to `index.html` at this time.
95 | - No publish scripts to get new version to S3.
96 |
--------------------------------------------------------------------------------
/dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | AWSResco
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |