Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: ESTCP plots and iframe #4602

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions config/settings/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@
# COMPRESS_OFFLINE = True

# Make sure to disable secure cooking and csrf when using Cloudflare
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# Allow SEED within iframes
SESSION_COOKIE_SAMESITE = 'None'
CSRF_COOKIE_SAMESITE = 'None'

ALLOWED_HOSTS = ['*']

Expand Down
48 changes: 24 additions & 24 deletions docker/nginx-seed.conf
Original file line number Diff line number Diff line change
Expand Up @@ -102,30 +102,6 @@ http {
# Includes virtual hosts configs.
# include /etc/nginx/http.d/*.conf;

# https://gist.github.com/plentz/6737338
# config to disallow the browser to render the page inside a frame or iframe
# and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking
# if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
# https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
add_header X-Frame-Options SAMEORIGIN;

# when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
# to disable content-type sniffing on some browsers.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
# currently supported in IE > 8 http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx
# https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/gg622941(v=vs.85)
# 'soon' on Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=471020
add_header X-Content-Type-Options nosniff;

# This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
# It's usually enabled by default anyway, so the role of this header is to re-enable the filter for
# this particular website if it was disabled by the user.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
add_header X-XSS-Protection "1; mode=block";

# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# the upstream component nginx needs to connect to
upstream seed_upstream {
server unix:///tmp/uwsgi-seed.sock;
Expand Down Expand Up @@ -192,6 +168,30 @@ http {

add_header Content-Security-Policy "${DEFAULT}; ${SCRIPT}; ${STYLE}; ${FONT}; ${FRAME}; ${IMG}; ${OBJECT}";

# https://gist.github.com/plentz/6737338
# config to disallow the browser to render the page inside a frame or iframe
# and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking
# if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
# https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
# add_header X-Frame-Options SAMEORIGIN;

# when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
# to disable content-type sniffing on some browsers.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
# currently supported in IE > 8 http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx
# https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/compatibility/gg622941(v=vs.85)
# 'soon' on Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=471020
add_header X-Content-Type-Options nosniff;

# This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
# It's usually enabled by default anyway, so the role of this header is to re-enable the filter for
# this particular website if it was disabled by the user.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
add_header X-XSS-Protection "1; mode=block";

# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# configure maintenance page redirect
if (-f /seed/collected_static/maintenance.html) {
return 503;
Expand Down
307 changes: 307 additions & 0 deletions seed/static/seed/js/controllers/inventory_plots_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
/**
* :copyright (c) 2014 - 2022, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Department of Energy) and contributors. All rights reserved.
* :author
*/
angular.module('BE.seed.controller.inventory_plots', [])
.controller('inventory_plots_controller', [
'$scope',
'$filter',
'$window',
'$uibModal',
'$sce',
'$state',
'$stateParams',
'$q',
'inventory_service',
'label_service',
'data_quality_service',
'geocode_service',
'user_service',
'derived_columns_service',
'Notification',
'cycles',
'profiles',
'current_profile',
'all_columns',
'derived_columns_payload',
'urls',
'spinner_utility',
'naturalSort',
'$translate',
'uiGridConstants',
'i18nService', // from ui-grid
'organization_payload',
'gridUtil',
function (
$scope,
$filter,
$window,
$uibModal,
$sce,
$state,
$stateParams,
$q,
inventory_service,
label_service,
data_quality_service,
geocode_service,
user_service,
derived_columns_service,
Notification,
cycles,
profiles,
current_profile,
all_columns,
derived_columns_payload,
urls,
spinner_utility,
naturalSort,
$translate,
uiGridConstants,
i18nService,
organization_payload,
gridUtil
) {
$scope.inventory_type = $stateParams.inventory_type;
var lastCycleId = inventory_service.get_last_cycle();
$scope.cycle = {
selected_cycle: _.find(cycles.cycles, { id: lastCycleId }) || _.first(cycles.cycles),
cycles: cycles.cycles
};
$scope.chartsInfo = [
{
"chartName": "Year Built vs ECI",
"xDisplayName": "Year Built",
"yDisplayName": "ECI"
},
{
"chartName": "Total GHG Emissions vs Gross Floor Area (ft²)",
"xDisplayName": "Total GHG Emissions (t/year)",
"yDisplayName": "Gross Floor Area (ft²)"
},
{
"chartName": "Potential Energy Savings vs ECI",
"yDisplayName": "BETTER Potential Energy Savings (kWh)",
"xDisplayName": "ECI"
},
{
"chartName": "Total GHG Emissions/sqft vs Year Built",
"xDisplayName": "Total GHG Emissions/sqft",
"yDisplayName": "Year Built"
},
];

const property_name_column = all_columns.find(c => c["column_name"] == "property_name");
neededColumns = new Set([property_name_column["id"]]);
neededDerivedColumns = [];

$scope.chartsInfo.forEach(chartInfo => {
x_column = all_columns.find(c => c["displayName"] == chartInfo["xDisplayName"])
y_column = all_columns.find(c => c["displayName"] == chartInfo["yDisplayName"])

if (!!x_column && !!x_column["derived_column"]) neededDerivedColumns.push(x_column)
else if (!!x_column) neededColumns.add(x_column["id"])
if (!!y_column && !!y_column["derived_column"]) neededDerivedColumns.push(y_column)
else if (!!y_column) neededColumns.add(y_column["id"])

chartInfo["xName"] = x_column? x_column["name"]: null;
chartInfo["yName"] = y_column? y_column["name"]: null;
chartInfo["populated"] = Boolean(!!x_column & !!y_column);
});

var createChart = function (elementId, xAxisKey, xDisplayName, yAxisKey, yDisplayName, onHover) {
var canvas = document.getElementById(elementId);
var ctx = canvas.getContext("2d");

return new Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
data: [],
backgroundColor: "cyan",
hoverBackgroundColor: 'red',
borderColor: "black",
}],
},
options: {
scales: {
x: {
display: true,
title: {
display: true,
text: xDisplayName,
},
},
y: {
display: true,
title: {
display: true,
text: yDisplayName,
}
}
},
plugins: {
title: {
display: true,
text: elementId
},
zoom: {
limits: {
x: { min: 'original', max: 'original', minRange: 50 },
y: { min: 'original', max: 'original', minRange: 50 }
},
pan: {
enabled: true,
mode: 'xy',
},
zoom: {
wheel: {
enabled: true,
},
mode: 'xy',
},
},
legend: {
display: false,
},
tooltip: {
callbacks: {
label: function (ctx) {
let label = ctx.dataset.labels[ctx.dataIndex];
label += " (" + ctx.parsed.x + ", " + ctx.parsed.y + ")";
return label
}
}
}
},
parsing: {
xAxisKey: xAxisKey,
yAxisKey: yAxisKey
},
onClick: (evt) => {
var activePoints = evt.chart.getActiveElements(evt);

if (activePoints[0]) {
activePoint = $scope.data[activePoints[0]["index"]]
window.location.href = '/app/#/' + $scope.inventory_type + '/' + activePoint["id"];
}
},
onHover: (evt) => {
var activePoints = evt.chart.getActiveElements(evt);
onHover(activePoints);
},
},
});
}

function hoverOnAllCharts(activePoints) {
if (activePoints[0]) {
var index = activePoints[0]["index"]
for (const chart of charts) {
chart.setActiveElements([
{
datasetIndex: 0,
index: index,
}
])
chart.update()
}
} else {
for (const chart of charts) {
chart.setActiveElements([]);
chart.update()
}
}
}

charts = $scope.chartsInfo.filter(chartInfo => chartInfo["populated"])
.map(chartInfo => {
return createChart(
elementId = chartInfo["chartName"],
xAxisKey = chartInfo["xName"],
xAxisName = chartInfo["xDisplayName"],
yAxisKey = chartInfo["yName"],
yAxisName = chartInfo["yDisplayName"],
onHover = hoverOnAllCharts
)
})

$scope.update_charts = function () {
spinner_utility.show();
fetch().then(function (data) {
if (data.status === 'error') {
let message = data.message;
Notification.error({ message, delay: 15000 });
spinner_utility.hide();
return;
}

$scope.data = data.results
populate_charts(data.results);
spinner_utility.hide();
});
};

var populate_charts = function (data) {
labels = data.map(property => property[property_name_column["name"]]);

dataIndexById = data.reduce((acc, curr, i) => {
acc[curr["id"]] = i;
return acc
}, {})
for (const column of neededDerivedColumns){
derived_columns_service.evaluate(
organization_payload.organization.id,
column["derived_column"],
$scope.cycle.selected_cycle.id,
data.map(d => d["id"])
).then(derived_columns_data => {
for( const d of derived_columns_data.results){
data[dataIndexById[d["id"]]][column["name"]] = d["value"]
}
});
}

for (const chart of charts) {
chart.data.datasets[0].data = data
chart.data.datasets[0].labels = labels
chart.update();
}
}

var fetch = function () {
var fn;
if ($scope.inventory_type === 'properties') {
fn = inventory_service.get_properties;
} else if ($scope.inventory_type === 'taxlots') {
fn = inventory_service.get_taxlots;
}

return fn(
page = 1,
per_page = undefined,
cycle = $scope.cycle.selected_cycle,
profile_id = null,
include_view_ids = null,
exclude_view_ids = null,
save_last_cycle = true,
organization_id = null,
include_related = true,
column_filters = null,
column_sorts = null,
ids_only = null,
shown_column_ids = Array.from(neededColumns).join() // makes set string, ie {1, 2} -> "1,2"
).then(function (data) {
return data;
});
};

$scope.update_cycle = function (cycle) {
inventory_service.save_last_cycle(cycle.id);
$scope.cycle.selected_cycle = cycle;
$scope.update_charts();
};

$scope.update_charts();
}
]);