diff --git a/bookmarklet/README.md b/bookmarklet/README.md
new file mode 100644
index 0000000..91bcf3c
--- /dev/null
+++ b/bookmarklet/README.md
@@ -0,0 +1,31 @@
+# CAWS Bookmarklet
+
+This folder contains the javascript code used for the bookmarklet. The
+bookmarklet query the energy usage of your available endpoints within our database and
+will inject HTML code into the
+[Globus web app's Compute page](https://app.globus.org/compute) to display
+energy expenditure information.
+
+
+
+## How to use
+
+Create a bookmark of any name with the content of
+[bookmarklet](https://github.com/AK2000/caws/blob/bookmarklet/bookmarklet/bookmarklet)
+as the URL. Then, proceed to [Globus Compute](https://app.globus.org/compute) and
+click on your newly created bookmark to execute the bookmarklet. The Globus Compute page should then
+be updated with the total energy expenditure of the endpoints.
+
+## Server
+
+By default, the code queries our running server for energy expenditure information. However, if you
+have chosen to host this information elsewhere, we have provided the server code used by our bookmarklet.
+To execute the server, simply define the following environment variables `CAWS_HOST`, `CAWS_USER`,
+`CAWS_PASSWORD` and execute the provided [server.py](https://github.com/AK2000/caws/blob/bookmarklet/bookmarklet/server.py) script.
+
+For example, `CAWS_HOST='127.0.0.1' CAWS_USER='fake_user' CAWS_PASSWORD='fake_password' python server.py`.
+
+## Troubleshooting
+Currently the bookmarklet is not configured to use SSL, and therefore, your browser will likely tell you that the connection is not
+secure. You will need to bypass your browser's default security to have the bookmarklet display.
+
diff --git a/bookmarklet/bookmark.js b/bookmarklet/bookmark.js
new file mode 100644
index 0000000..d610fe0
--- /dev/null
+++ b/bookmarklet/bookmark.js
@@ -0,0 +1,92 @@
+(async () => {
+ var endpoint_links = document.querySelectorAll('div[class="h3 my-0"] a[href*="endpoints"]');
+
+ Array.prototype.forEach.call(endpoint_links, async function (element, index) {
+
+ // step 1: get endpoint id
+ var segments = element.href.split('/');
+ var endpoint = segments.pop() || segments.pop();
+
+ // step 2: query server for endpoint stats
+ let url = `https://129.114.27.161:5001/caws?endpoint=${endpoint}`
+ const response = await fetch(url);
+ const endpoint_json = await response.json();
+
+ // step 3: display results
+ var parentEnergyElement = element.parentNode.parentNode.nextElementSibling.firstElementChild;
+
+ energyContentDiv = parentEnergyElement.querySelector('.caws-energy');
+ consumedContentDiv = parentEnergyElement.querySelector('.caws-consumed');
+
+ if (energyContentDiv != null || consumedContentDiv != null) {
+ // Edit value in existing div
+
+ if (energyContentDiv != null)
+ energyContentDiv.innerHTML = endpoint_json.total_energy;
+
+ if (consumedContentDiv != null)
+ consumedContentDiv.innerHTML = endpoint_json.energy_consumed;
+ }
+ else {
+ // Create parent div
+ var energyDiv = document.createElement('div');
+ energyDiv.classList.add('caws');
+ energyDiv.classList.add('col-lg');
+ energyDiv.classList.add('py-1');
+
+ var energyRowDiv = document.createElement('div');
+ energyRowDiv.classList.add('row');
+
+ // title div
+ var energyTitleDiv = document.createElement('div');
+ energyTitleDiv.classList.add('col-4');
+ energyTitleDiv.classList.add('col-lg-12');
+ energyTitleDiv.classList.add('small');
+ energyTitleDiv.classList.add('text-uppercase');
+ energyTitleDiv.classList.add('text-success');
+ energyTitleDiv.innerHTML = 'Total node energy expenditure (kWh)
';
+ energyRowDiv.appendChild(energyTitleDiv);
+
+ // content div
+ var energyContentDiv = document.createElement('div');
+ energyContentDiv.classList.add('caws-energy')
+ energyContentDiv.classList.add('col-8');
+ energyContentDiv.classList.add('col-lg-12');
+ energyContentDiv.classList.add('small');
+ energyContentDiv.innerHTML = endpoint_json.total_energy;
+ energyRowDiv.appendChild(energyContentDiv);
+ energyDiv.appendChild(energyRowDiv);
+ parentEnergyElement.appendChild(energyDiv);
+
+ var consumedDiv = document.createElement('div');
+ consumedDiv.classList.add('caws');
+ consumedDiv.classList.add('col-lg');
+ consumedDiv.classList.add('py-1');
+
+ var consumedRowDiv = document.createElement('div');
+ consumedRowDiv.classList.add('row');
+
+ var consumedTitleDiv = document.createElement('div');
+ consumedTitleDiv.classList.add('col-4');
+ consumedTitleDiv.classList.add('col-lg-12');
+ consumedTitleDiv.classList.add('small');
+ consumedTitleDiv.classList.add('text-uppercase');
+ consumedTitleDiv.classList.add('text-success');
+ consumedTitleDiv.innerHTML = 'Energy consumed by tasks (kWH)
';
+ consumedRowDiv.appendChild(consumedTitleDiv);
+
+ // content div
+ var consumedContentDiv = document.createElement('div');
+ consumedContentDiv.classList.add('caws-consumed')
+ consumedContentDiv.classList.add('col-8');
+ consumedContentDiv.classList.add('col-lg-12');
+ consumedContentDiv.classList.add('small');
+ consumedContentDiv.innerHTML = endpoint_json.energy_consumed;
+ consumedRowDiv.appendChild(consumedContentDiv);
+ consumedDiv.appendChild(consumedRowDiv);
+ parentEnergyElement.appendChild(consumedDiv);
+ }
+
+ }, endpoint_links);
+
+})();
diff --git a/bookmarklet/bookmark.min.js b/bookmarklet/bookmark.min.js
new file mode 100644
index 0000000..87db46b
--- /dev/null
+++ b/bookmarklet/bookmark.min.js
@@ -0,0 +1 @@
+(async()=>{var s=document.querySelectorAll('div[class="h3 my-0"] a[href*="endpoints"]');Array.prototype.forEach.call(s,(async function(s,e){var a=s.href.split("/");let d=`https://129.114.27.161:5001/caws?endpoint=${a.pop()||a.pop()}`;const l=await fetch(d),t=await l.json();var c=s.parentNode.parentNode.nextElementSibling.firstElementChild;if(o=c.querySelector(".caws-energy"),u=c.querySelector(".caws-consumed"),null!=o||null!=u)null!=o&&(o.innerHTML=t.total_energy),null!=u&&(u.innerHTML=t.energy_consumed);else{var n=document.createElement("div");n.classList.add("caws"),n.classList.add("col-lg"),n.classList.add("py-1");var i=document.createElement("div");i.classList.add("row");var r=document.createElement("div");r.classList.add("col-4"),r.classList.add("col-lg-12"),r.classList.add("small"),r.classList.add("text-uppercase"),r.classList.add("text-success"),r.innerHTML='Total node energy expenditure (kWh)
',i.appendChild(r);var o=document.createElement("div");o.classList.add("caws-energy"),o.classList.add("col-8"),o.classList.add("col-lg-12"),o.classList.add("small"),o.innerHTML=t.total_energy,i.appendChild(o),n.appendChild(i),c.appendChild(n);var p=document.createElement("div");p.classList.add("caws"),p.classList.add("col-lg"),p.classList.add("py-1");var m=document.createElement("div");m.classList.add("row");var L=document.createElement("div");L.classList.add("col-4"),L.classList.add("col-lg-12"),L.classList.add("small"),L.classList.add("text-uppercase"),L.classList.add("text-success"),L.innerHTML='Energy consumed by tasks (kWH)
',m.appendChild(L);var u=document.createElement("div");u.classList.add("caws-consumed"),u.classList.add("col-8"),u.classList.add("col-lg-12"),u.classList.add("small"),u.innerHTML=t.energy_consumed,m.appendChild(u),p.appendChild(m),c.appendChild(p)}}),s)})();
\ No newline at end of file
diff --git a/bookmarklet/bookmarklet b/bookmarklet/bookmarklet
new file mode 100644
index 0000000..8c0f891
--- /dev/null
+++ b/bookmarklet/bookmarklet
@@ -0,0 +1 @@
+javascript:(async()=>{var s=document.querySelectorAll('div[class="h3 my-0"] a[href*="endpoints"]');Array.prototype.forEach.call(s,(async function(s,e){var a=s.href.split("/");let d=`https://129.114.27.161:5001/caws?endpoint=${a.pop()||a.pop()}`;const l=await fetch(d),t=await l.json();var c=s.parentNode.parentNode.nextElementSibling.firstElementChild;if(o=c.querySelector(".caws-energy"),u=c.querySelector(".caws-consumed"),null!=o||null!=u)null!=o&&(o.innerHTML=t.total_energy),null!=u&&(u.innerHTML=t.energy_consumed);else{var n=document.createElement("div");n.classList.add("caws"),n.classList.add("col-lg"),n.classList.add("py-1");var i=document.createElement("div");i.classList.add("row");var r=document.createElement("div");r.classList.add("col-4"),r.classList.add("col-lg-12"),r.classList.add("small"),r.classList.add("text-uppercase"),r.classList.add("text-success"),r.innerHTML='Total node energy expenditure (kWh)
',i.appendChild(r);var o=document.createElement("div");o.classList.add("caws-energy"),o.classList.add("col-8"),o.classList.add("col-lg-12"),o.classList.add("small"),o.innerHTML=t.total_energy,i.appendChild(o),n.appendChild(i),c.appendChild(n);var p=document.createElement("div");p.classList.add("caws"),p.classList.add("col-lg"),p.classList.add("py-1");var m=document.createElement("div");m.classList.add("row");var L=document.createElement("div");L.classList.add("col-4"),L.classList.add("col-lg-12"),L.classList.add("small"),L.classList.add("text-uppercase"),L.classList.add("text-success"),L.innerHTML='Energy consumed by tasks (kWH)
',m.appendChild(L);var u=document.createElement("div");u.classList.add("caws-consumed"),u.classList.add("col-8"),u.classList.add("col-lg-12"),u.classList.add("small"),u.innerHTML=t.energy_consumed,m.appendChild(u),p.appendChild(m),c.appendChild(p)}}),s)})();
\ No newline at end of file
diff --git a/bookmarklet/figures/bookmarklet.png b/bookmarklet/figures/bookmarklet.png
new file mode 100644
index 0000000..b152de0
Binary files /dev/null and b/bookmarklet/figures/bookmarklet.png differ
diff --git a/bookmarklet/server.py b/bookmarklet/server.py
new file mode 100644
index 0000000..8011e63
--- /dev/null
+++ b/bookmarklet/server.py
@@ -0,0 +1,55 @@
+"""Flask server implementation."""
+import pandas as pd
+import psycopg
+import os
+
+from flask import after_this_request
+from flask import Flask
+from flask import jsonify
+from flask import request
+
+app = Flask(__name__)
+user = os.environ['CAWS_USER']
+password = os.environ['CAWS_PASSWORD']
+host = os.environ['CAWS_HOST']
+dbname = 'monitoring'
+
+def get_kwh(microjoules):
+ return round(microjoules * 10**-6 / 3600, 7)
+
+@app.route("/caws", methods=["GET"])
+def get_energy() -> bytes:
+ """REST request to return proxy/ies to the user.
+
+ Returns (bytes):
+ The pickled dictionary of all proxies or a single pickled proxy if \
+ database name to return is provided.
+ """
+ @after_this_request
+ def add_header(response):
+ response.headers.add('Access-Control-Allow-Origin', '*')
+ return response
+
+ endpoint_id = request.args.get('endpoint')
+
+ with psycopg.connect(f'dbname={dbname} user={user} password={password} host={host}', row_factory=psycopg.rows.dict_row) as conn:
+ with conn.cursor() as cur:
+ cur.execute(f'''SELECT *
+ FROM "energy"
+ INNER JOIN "caws_task"
+ ON "energy".run_id = "caws_task".endpoint_id
+ WHERE "energy".run_id = \'{endpoint_id}\' '''
+ ) # FULL JOIN so we can get energy data of endpoint even if one of the table parameters is missing
+ data = cur.fetchall()
+ if len(data) == 0:
+ return jsonify({'run_id': endpoint_id, 'energy_consumed': 'N/A', 'total_energy': 'N/A'})
+
+ df = pd.DataFrame(data)
+ total_energy = df['total_energy'].sum()
+ energy_consumed = df['energy_consumed'].sum()
+
+ return jsonify({'run_id': endpoint_id, 'energy_consumed': get_kwh(energy_consumed), 'total_energy': get_kwh(total_energy)})
+
+
+if __name__ == "__main__":
+ app.run(debug=True, host="0.0.0.0", port=5001, ssl_context='adhoc')