webcamLiveFeed
diff --git a/WebContent/WEB-INF/tags/page.tag b/WebContent/WEB-INF/tags/page.tag
index b4a595643c..63420e84db 100644
--- a/WebContent/WEB-INF/tags/page.tag
+++ b/WebContent/WEB-INF/tags/page.tag
@@ -164,6 +164,7 @@
+
diff --git a/WebContent/images/watch_list.png b/WebContent/images/watch_list.png
new file mode 100644
index 0000000000..44ce68ee88
Binary files /dev/null and b/WebContent/images/watch_list.png differ
diff --git a/build.xml b/build.xml
index 34d211de87..23f91e938e 100644
--- a/build.xml
+++ b/build.xml
@@ -271,6 +271,8 @@
+
+
@@ -284,6 +286,9 @@
+
+
+
@@ -335,6 +340,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scadalts-ui/.gitignore b/scadalts-ui/.gitignore
index 724c3febcd..1998a95939 100644
--- a/scadalts-ui/.gitignore
+++ b/scadalts-ui/.gitignore
@@ -4,6 +4,8 @@ node_modules
/tests/e2e/videos/
/tests/e2e/screenshots/
+/cypress/videos
+/cypress/screenshots
# local env files
.env.local
diff --git a/scadalts-ui/cypress.json b/scadalts-ui/cypress.json
index 470c720199..5c635794bd 100644
--- a/scadalts-ui/cypress.json
+++ b/scadalts-ui/cypress.json
@@ -1,3 +1,3 @@
{
- "pluginsFile": "tests/e2e/plugins/index.js"
+ "baseUrl": "http://localhost:8080/ScadaBR"
}
diff --git a/scadalts-ui/cypress/fixtures/example.json b/scadalts-ui/cypress/fixtures/example.json
new file mode 100644
index 0000000000..da18d9352a
--- /dev/null
+++ b/scadalts-ui/cypress/fixtures/example.json
@@ -0,0 +1,5 @@
+{
+ "name": "Using fixtures to represent data",
+ "email": "hello@cypress.io",
+ "body": "Fixtures are a great way to mock data for responses to routes"
+}
\ No newline at end of file
diff --git a/scadalts-ui/cypress/integration/ScadaLTS_Views/datasource_creation.spec.js b/scadalts-ui/cypress/integration/ScadaLTS_Views/datasource_creation.spec.js
new file mode 100644
index 0000000000..6adc371162
--- /dev/null
+++ b/scadalts-ui/cypress/integration/ScadaLTS_Views/datasource_creation.spec.js
@@ -0,0 +1,71 @@
+context('Create Datasource', () => {
+
+ function createVirtualDataSource(name) {
+ cy.visit('data_sources.shtm');
+ cy.get("#dataSourceTypes").select('Virtual Data Source').should('have.value', '1');
+ cy.get('img.ptr[src="images/icon_ds_add.png"]').first().click();
+ cy.get('.smallTitle').should('contain', 'Virtual data source properties')
+ cy.get('#dataSourceName').type(name)
+ cy.get('#updatePeriods').type("{backspace}1").should('have.value', '1');
+ cy.get('img.ptr[src="images/save.png"]').first().click();
+ cy.get('#dataSourceMessage').should('contain', 'Data source has been saved')
+ // cy.debug('DataSource created!')
+ }
+
+ /**
+ *
+ * @param {*} name DataPoint name
+ * @param {*} type [Multistate = 2| Numeric = 3]
+ */
+ function addVirtualDataPoint(name, type) {
+ cy.get('img.ptr[src="images/icon_comp_add.png"]').click();
+ cy.get('input#name').type(name)
+ cy.get('input#settable').click()
+ cy.get('select#dataTypeId').select(type)
+ if (type === '3') {
+ cy.wait(500)
+ cy.get('select#changeTypeId').select('Random')
+ cy.get('#divCH6').children().first().next().find('input').type("{backspace}20")
+ cy.get('#divCH6').children().first().next().next().find('input').type("2")
+ } else if (type === '2') {
+ cy.get('select#changeTypeId').select('8')
+ cy.get('input#randomMultistate').type("1")
+ for (let x = 1; x <= 5; x = x + 1) {
+ cy.get('img.ptr[src="images/add.png"]').click()
+ }
+ cy.get('select#randomMultistateChange.startValue').select('1')
+ }
+ cy.get('img.ptr#pointSaveImg').click()
+ cy.get('#pointMessage').should('contain', 'Point details saved')
+ // cy.debug(`DataPoint ${name} created!`)
+ }
+
+ function login(username, password) {
+ cy.visit('/login.htm')
+ cy.get('input#username').type(username)
+ cy.get('input#password').type(password)
+ cy.get('.login-button > input').click()
+ }
+
+ before(() => {
+ login("admin", "admin")
+ })
+
+ describe("Create Test Datasource", function () {
+
+ it('Create datasource with 10 numeric datapoints', function () {
+ const count = 10;
+ cy.visit('/data_sources.shtm')
+ createVirtualDataSource(`Test-${new Date().toISOString()}`)
+ for (let i = 0; i < count; i = i + 1) {
+ addVirtualDataPoint(`0${i}-Test`, '3')
+ }
+ cy.get('#pointsList').children().should('have.length', count)
+ cy.get('img.ptr#enableAllImg').click();
+ cy.get('#pointsList').find('img[src="images/brick_go.png"]').should('have.length', count)
+ cy.get('img.ptr#dsStatusImg').click();
+ })
+
+ })
+
+})
\ No newline at end of file
diff --git a/scadalts-ui/cypress/integration/ScadaLTS_Views/modern_charts.spec.js b/scadalts-ui/cypress/integration/ScadaLTS_Views/modern_charts.spec.js
new file mode 100644
index 0000000000..d27cc3c2da
--- /dev/null
+++ b/scadalts-ui/cypress/integration/ScadaLTS_Views/modern_charts.spec.js
@@ -0,0 +1,161 @@
+context('Verify Modern Watch List Page and Modern Charts', () => {
+
+ before(() => {
+ cy.clearCookies()
+ cy.visit('/login.htm')
+ cy.get('input#username').type("admin").should('have.value', 'admin')
+ cy.get('input#password').type("admin").should('have.value', 'admin')
+ cy.get('.login-button > input').click()
+ cy.location('pathname').should('include', 'watch_list')
+ })
+
+ describe("Modern Watch List", function () {
+
+ it('Open Modern Watch List Page', function () {
+ cy.visit('/modern_watch_list.shtm')
+ cy.location('pathname').should('include', 'modern_watch_list.shtm')
+ })
+ it("Validate Modern Charts Vue component exist", function () {
+ cy.get(".smallTitle").should('contain', 'Modern Chart')
+ })
+ })
+
+ describe("Chart with 1 datapoint", function () {
+
+ it('Create chart', function () {
+ cy.get('#watchListSelect').select("Test_WL_1");
+ cy.get('.scada-widget .settings').find('button').first().click()
+ cy.get('.hello').find('svg')
+ })
+ it('Modify chart - set to Line Chart', function () {
+ cy.get('.settings-btn[src="/ScadaBR/images/cog.png"]').click()
+ cy.get('.radio-button[value="line"]').click()
+ cy.get('.settings-btn[src="/ScadaBR/images/accept.png"]').click()
+ })
+ it('Modify chart - set to Step Line Chart', function () {
+ cy.get('.settings-btn[src="/ScadaBR/images/cog.png"]').click()
+ cy.get('.radio-button[value="stepLine"]').click()
+ cy.get('.settings-btn[src="/ScadaBR/images/accept.png"]').click()
+ })
+ it('Modify chart - change Chart Color', function () {
+ cy.get('.settings-btn[src="/ScadaBR/images/cog.png"]').click()
+ cy.get('.farbtastic-overlay').click(50, 2)
+ cy.get('.farbtastic-solid').should('have.css', 'background-color').and('eq', 'rgb(255, 3, 0)')
+ cy.get('.settings-btn[src="/ScadaBR/images/accept.png"]').click()
+ })
+ it('Modify chart - change to Static Chart', function () {
+ cy.get('.settings-btn[src="/ScadaBR/images/cog.png"]').click()
+ cy.get('.radio-button[value="live"]').click()
+ cy.get('.settings-btn[src="/ScadaBR/images/accept.png"]').click()
+ })
+ it('Modify chart - change values from last: 6 hours', function () {
+ cy.get('.settings-btn[src="/ScadaBR/images/cog.png"]').click()
+ cy.get('#live-sd').type('{backspace}6')
+ cy.get('#live-sd').should('have.value', '6')
+ cy.get('#live-sd').next().select('hour')
+ cy.get('#live-sd').next().should('have.value', 'hour')
+ cy.get('.settings-btn[src="/ScadaBR/images/accept.png"]').click()
+ })
+ it('Modify chart - change values from last: 1 day', function () {
+ cy.get('.settings-btn[src="/ScadaBR/images/cog.png"]').click()
+ cy.get('#live-sd').type('{backspace}1')
+ cy.get('#live-sd').should('have.value', '1')
+ cy.get('#live-sd').next().select('day')
+ cy.get('#live-sd').next().should('have.value', 'day')
+ cy.get('.settings-btn[src="/ScadaBR/images/accept.png"]').click()
+ })
+ it('Modify chart - change to Static Chart', function () {
+ cy.get('.settings-btn[src="/ScadaBR/images/cog.png"]').click()
+ cy.get('.radio-button[value="static"]').click()
+ })
+ it('Modify chart - cancel', function () {
+ cy.get('.settings-btn[src="/ScadaBR/images/cross.png"]').click()
+ cy.get('.settings-btn[src="/ScadaBR/images/cog.png"]').click()
+ cy.get('.radio-button[value="live"]').should('not.be.checked')
+ cy.get('.settings-btn[src="/ScadaBR/images/cross.png"]').click()
+ })
+ it('Delete chart', function () {
+ cy.get('.settings-btn[src="/ScadaBR/images/delete.png"]').click()
+ cy.get('.chart-container horizontal').should('not.contain', 'svg')
+ })
+ })
+
+ describe('Chart with multiple datapoints', function () {
+ it('Create chart with 5 numeric datapoints', function () {
+ cy.get('#watchListSelect').select("Test_WL_5");
+ cy.get('.scada-widget .settings').find('button').first().click()
+ cy.get('.hello').find('svg')
+ cy.get('.settings-btn[src="/ScadaBR/images/delete.png"]').click()
+ })
+ it('Create chart with 10 numeric datapoints', function () {
+ cy.get('#watchListSelect').select("Test_WL_10");
+ cy.get('.scada-widget .settings').find('button').first().click()
+ cy.get('.hello').find('svg')
+ cy.get('.settings-btn[src="/ScadaBR/images/delete.png"]').click()
+ })
+ })
+
+ describe("Chart memory tests", function () {
+ it('Modify chart and save. Validate cookie', function () {
+ cy.get('#watchListSelect').select("Test_WL_1");
+ cy.get('.scada-widget .settings').find('button').first().click()
+ cy.get('.settings-btn[src="/ScadaBR/images/cog.png"]').click()
+ cy.get('#live-sd').type('{backspace}3')
+ cy.get('#live-sd').should('have.value', '3')
+ cy.get('.settings-btn[src="/ScadaBR/images/accept.png"]').click()
+ cy.getCookie('WatchListChartDashboard_admin').should('exist')
+ cy.get('.settings-btn[src="/ScadaBR/images/delete.png"]').click()
+ })
+ })
+
+ describe("Additional test - multiple charts", function () {
+ it('Create 2 same charts', function () {
+ cy.get('#watchListSelect').select("Test_WL_1");
+ cy.get('.scada-widget .settings').find('button').first().click()
+ cy.get('.scada-widget .settings').find('button').first().click()
+ cy.get('.hello').should('have.length', 2)
+ cy.get('.settings-btn[src="/ScadaBR/images/delete.png"]').click({ multiple: true })
+ cy.get('.hello').should('not.exist')
+ })
+ it('Create 2 different charts', function () {
+ cy.get('#watchListSelect').select("Test_WL_5");
+ let pointList = cy.get(".dojoTreeNodeLabelTitle");
+ let count = 5;
+ pointList.get('img[src="images/bullet_go.png"]').each(($el, index, $list) => {
+ if (count > 0) {
+ cy.wrap($el).click()
+ count = count - 1
+ }
+ })
+ cy.get('#watchListTable').get('input[type="checkbox"]').click({ multiple: true, force: true });
+ cy.get('#watchListTable').get('input[type="checkbox"]').first().click({ force: true });
+ cy.get('.scada-widget .settings').find('button').first().click()
+ cy.get('#watchListTable').get('input[type="checkbox"]').first().click({ force: true });
+ cy.get('#watchListTable').get('input[type="checkbox"]').eq(1).click({ force: true });
+ cy.get('.scada-widget .settings').find('button').first().click()
+ cy.get('.hello').should('have.length', 2)
+ cy.get('.settings-btn[src="/ScadaBR/images/delete.png"]').click({ multiple: true })
+ cy.get('.hello').should('not.exist')
+ })
+ it('Create 4 different charts', function () {
+ cy.get('#watchListSelect').select("Test_WL_10");
+ let pointList = cy.get(".dojoTreeNodeLabelTitle");
+ let count = 5;
+ pointList.get('img[src="images/bullet_go.png"]').each(($el, index, $list) => {
+ if (count > 0) {
+ cy.wrap($el).click()
+ count = count - 1
+ }
+ })
+ cy.get('#watchListTable').get('input[type="checkbox"]').click({ multiple: true, force: true });
+ for (let x = 0; x < 4; x = x + 1) {
+ cy.get('#watchListTable').get('input[type="checkbox"]').eq(x).click({ force: true });
+ cy.get('.scada-widget .settings').find('button').first().click()
+ cy.get('#watchListTable').get('input[type="checkbox"]').eq(x).click({ force: true });
+ }
+ cy.get('.hello').should('have.length', 4)
+ })
+
+ })
+
+})
\ No newline at end of file
diff --git a/scadalts-ui/cypress/integration/ScadaLTS_Views/watch_list.spec.js b/scadalts-ui/cypress/integration/ScadaLTS_Views/watch_list.spec.js
new file mode 100644
index 0000000000..b6077f5608
--- /dev/null
+++ b/scadalts-ui/cypress/integration/ScadaLTS_Views/watch_list.spec.js
@@ -0,0 +1,43 @@
+context('Create WatchList', () => {
+
+ function createWatchList(name, count) {
+ cy.visit('/watch_list.shtm');
+ cy.get('img.ptr[src="images/add.png"]').last().click();
+ cy.get('#watchListSelect').select("(unnamed)");
+ cy.get('#wlEditImg').trigger('mouseover').get('#newWatchListName').type(`{selectall}${name}`);
+ cy.get('#saveWatchListNameLink').click();
+
+ let pointList = cy.get(".dojoTreeNodeLabelTitle");
+ pointList.get('img[src="images/bullet_go.png"]').each(($el, index, $list) => {
+ if (count > 0) {
+ cy.wrap($el).click()
+ count = count - 1
+ }
+ })
+
+ }
+
+ function login(username, password) {
+ cy.visit('/login.htm')
+ cy.get('input#username').type(username)
+ cy.get('input#password').type(password)
+ cy.get('.login-button > input').click()
+ }
+
+ beforeEach(() => {
+ login("admin", "admin")
+ })
+
+ describe("Create WatchList", function () {
+
+ let watchListState = [1, 5, 10];
+ watchListState.forEach(element => {
+ it(`Create Watch List with ${element} numeric datapoints`, function() {
+ createWatchList(`Test_WL_${element}`, element);
+ cy.get('#watchListTable').children().should('have.length', element)
+ })
+ });
+
+ })
+
+})
\ No newline at end of file
diff --git a/scadalts-ui/cypress/plugins/index.js b/scadalts-ui/cypress/plugins/index.js
new file mode 100644
index 0000000000..aa9918d215
--- /dev/null
+++ b/scadalts-ui/cypress/plugins/index.js
@@ -0,0 +1,21 @@
+///
+// ***********************************************************
+// This example plugins/index.js can be used to load plugins
+//
+// You can change the location of this file or turn off loading
+// the plugins file with the 'pluginsFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/plugins-guide
+// ***********************************************************
+
+// This function is called when a project is opened or re-opened (e.g. due to
+// the project's config changing)
+
+/**
+ * @type {Cypress.PluginConfig}
+ */
+module.exports = (on, config) => {
+ // `on` is used to hook into various events Cypress emits
+ // `config` is the resolved Cypress config
+}
diff --git a/scadalts-ui/cypress/support/commands.js b/scadalts-ui/cypress/support/commands.js
new file mode 100644
index 0000000000..ca4d256f3e
--- /dev/null
+++ b/scadalts-ui/cypress/support/commands.js
@@ -0,0 +1,25 @@
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add("login", (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
diff --git a/scadalts-ui/cypress/support/index.js b/scadalts-ui/cypress/support/index.js
new file mode 100644
index 0000000000..d68db96df2
--- /dev/null
+++ b/scadalts-ui/cypress/support/index.js
@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/index.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
diff --git a/scadalts-ui/package.json b/scadalts-ui/package.json
index e7684ff8d3..2c16b1366b 100644
--- a/scadalts-ui/package.json
+++ b/scadalts-ui/package.json
@@ -10,6 +10,7 @@
"test:unit": "vue-cli-service test:unit"
},
"dependencies": {
+ "@amcharts/amcharts4": "4.8.9",
"ag-grid": "18.1.2",
"ag-grid-community": "21.2.2",
"ag-grid-vue": "21.2.2",
@@ -21,12 +22,15 @@
"moment": "2.22.2",
"uiv": "0.27.0",
"vue": "2.6.10",
+ "vue-color-picker-wheel": "^0.4.3",
+ "vue-cookie": "^1.1.4",
"vue-jsoneditor": "1.0.13",
"vue-loader": "15.7.1",
"vue-lodash": "2.0.2",
"vue-property-decorator": "7.3.0",
"vue-router": "3.0.1",
"vue-underscore": "0.1.4",
+ "vuejs-datepicker": "^1.6.2",
"vuejs-logger": "1.5.3",
"vuex": "3.0.1"
},
@@ -39,8 +43,10 @@
"@vue/test-utils": "1.0.0-beta.29",
"babel-eslint": "10.0.1",
"chai": "4.1.2",
+ "cypress": "^4.0.1",
"eslint": "5.16.0",
"eslint-plugin-vue": "5.0.0",
+ "http-proxy-middleware": "^0.20.0",
"sass": "1.18.0",
"sass-loader": "7.1.0",
"vue-template-compiler": "2.6.10"
diff --git a/scadalts-ui/src/assets/doc/watch_list/MWL_4-DataPoints.gif b/scadalts-ui/src/assets/doc/watch_list/MWL_4-DataPoints.gif
new file mode 100644
index 0000000000..8eed603371
Binary files /dev/null and b/scadalts-ui/src/assets/doc/watch_list/MWL_4-DataPoints.gif differ
diff --git a/scadalts-ui/src/assets/doc/watch_list/MWL_AddChart.gif b/scadalts-ui/src/assets/doc/watch_list/MWL_AddChart.gif
new file mode 100644
index 0000000000..acc607deae
Binary files /dev/null and b/scadalts-ui/src/assets/doc/watch_list/MWL_AddChart.gif differ
diff --git a/scadalts-ui/src/assets/doc/watch_list/MWL_CompareCharts.gif b/scadalts-ui/src/assets/doc/watch_list/MWL_CompareCharts.gif
new file mode 100644
index 0000000000..3aa191e907
Binary files /dev/null and b/scadalts-ui/src/assets/doc/watch_list/MWL_CompareCharts.gif differ
diff --git a/scadalts-ui/src/assets/doc/watch_list/MWL_Muilistate.gif b/scadalts-ui/src/assets/doc/watch_list/MWL_Muilistate.gif
new file mode 100644
index 0000000000..348a42a817
Binary files /dev/null and b/scadalts-ui/src/assets/doc/watch_list/MWL_Muilistate.gif differ
diff --git a/scadalts-ui/src/assets/doc/watch_list/MWL_MultistateNumeric.gif b/scadalts-ui/src/assets/doc/watch_list/MWL_MultistateNumeric.gif
new file mode 100644
index 0000000000..bb792a2dfa
Binary files /dev/null and b/scadalts-ui/src/assets/doc/watch_list/MWL_MultistateNumeric.gif differ
diff --git a/scadalts-ui/src/assets/doc/watch_list/MWL_Navigate.gif b/scadalts-ui/src/assets/doc/watch_list/MWL_Navigate.gif
new file mode 100644
index 0000000000..740772e8b7
Binary files /dev/null and b/scadalts-ui/src/assets/doc/watch_list/MWL_Navigate.gif differ
diff --git a/scadalts-ui/src/assets/doc/watch_list/MWL_StepLine.gif b/scadalts-ui/src/assets/doc/watch_list/MWL_StepLine.gif
new file mode 100644
index 0000000000..66a8394f06
Binary files /dev/null and b/scadalts-ui/src/assets/doc/watch_list/MWL_StepLine.gif differ
diff --git a/scadalts-ui/src/components/amcharts/BaseChart.js b/scadalts-ui/src/components/amcharts/BaseChart.js
new file mode 100644
index 0000000000..2e19f97a90
--- /dev/null
+++ b/scadalts-ui/src/components/amcharts/BaseChart.js
@@ -0,0 +1,571 @@
+/**
+ * @fileoverview BaseChart Class Definition.
+ * @author Radoslaw Jajko
+ * @version 1.0.0
+ *
+ * @requires am4core
+ * @requires am4charts
+ * @requires Axios
+ */
+import * as am4core from "@amcharts/amcharts4/core";
+import * as am4charts from "@amcharts/amcharts4/charts";
+import Axios from "axios";
+
+/**
+ * BaseChart class allows to create many of am4chart chart types. Base chart is based on line chart
+ * but it could be extended by child classes to handle more complex chart definitions.
+ * @class
+ */
+export default class BaseChart {
+
+ //xcopy .\dist\static C:\services\tomcat7.0\webapps\ScadaLTS\resources\new-ui\ /E /K /Y
+ /**
+ *
+ * @param {any} chartReference Id of DOM element where this char will be initialized
+ * @param {String} chartType [ XYChart | PieChart | GaugeChart] available chart types
+ * @param {String} colors Hex value of base chart color.
+ * @param {String} [domain] Protocol, domain and the address of the API interface
+ */
+ constructor(chartReference, chartType, colors, domain = '.') {
+
+ if (chartType === "XYChart") {
+ this.chart = am4core.create(chartReference, am4charts.XYChart)
+ } else if (chartType === "PieChart") {
+ this.chart = am4core.create(chartReference, am4charts.PieChart);
+ } else if (chartType === "GaugeChart") {
+ this.chart = am4core.create(chartReference, am4charts.GaugeChart);
+ }
+ this.pointPastValues = new Map();
+ this.pointCurrentValue = new Map();
+ this.liveUpdatePointValues = new Map();
+ this.lastUpdate = 0;
+ this.lastTimestamp = new Date().getTime();
+ this.liveUpdateInterval = 5000;
+ this.domain = domain;
+ let colorPallete = [
+ am4core.color("#39B54A"),
+ am4core.color("#69FF7D"),
+ am4core.color("#166921"),
+ am4core.color("#690C24"),
+ am4core.color("#B53859"),
+ am4core.color("#734FC1"),
+ am4core.color("#824F1B"),
+ am4core.color("#69421B"),
+ ];
+ if (colors !== undefined && colors !== null) {
+ colors = colors.split(",");
+ if (colors.length > 0) {
+ for (let i = colors.length - 1; i >= 0; i--) {
+ colorPallete.unshift(am4core.color(colors[i].trim()));
+ }
+ }
+ }
+ this.chart.colors.list = colorPallete;
+ this.yAxesCount = 0;
+ }
+
+ /**
+ * Main method to display chart
+ * Before launch: Load data from API
+ */
+ showChart() {
+ this.setupChart()
+ }
+
+ /**
+ * Download from API DataPoint values from specific time period.
+ * Override this method in children classes to prepare data for displaying in charts
+ *
+ * @param {Number} pointId Data Point ID number
+ * @param {Number} [startTimestamp] Default get values from 1 hour ago
+ * @param {Number} [endTimestamp] Default get values till now
+ */
+ loadData(pointId, startTimestamp, endTimestamp, exportId) {
+ if (startTimestamp === undefined || startTimestamp === null) {
+ startTimestamp = new Date().getTime() - 3600000;
+ endTimestamp = new Date().getTime();
+ } else if ((startTimestamp !== undefined && startTimestamp !== null) && (endTimestamp === undefined || endTimestamp === null)) {
+ startTimestamp = BaseChart.convertDate(startTimestamp);
+ endTimestamp = new Date().getTime();
+ if (isNaN(startTimestamp)) {
+ console.warn("Parameter start-date is not valid!\nConnecting to API with default values");
+ startTimestamp = new Date().getTime() - 3600000;
+ }
+ } else if ((startTimestamp !== undefined && startTimestamp !== null) && (endTimestamp !== undefined && endTimestamp !== null)) {
+ startTimestamp = new Date(startTimestamp).getTime();
+ endTimestamp = new Date(endTimestamp).getTime();
+ if (isNaN(startTimestamp) || isNaN(endTimestamp)) {
+ console.warn("Parameter [start-date | end-date] are not valid!\nConnecting to API with default values")
+ startTimestamp = new Date().getTime() - 3600000;
+ endTimestamp = new Date().getTime();
+ }
+ }
+ let url;
+ if (exportId) {
+ url = `${this.domain}/api/point_value/getValuesFromTimePeriod/xid/${pointId}/${startTimestamp}/${endTimestamp}`
+ } else {
+ url = `${this.domain}/api/point_value/getValuesFromTimePeriod/${pointId}/${startTimestamp}/${endTimestamp}`
+ }
+ return new Promise((resolve, reject) => {
+ try {
+ Axios.get(url, { timeout: 5000, useCredentails: true, credentials: 'same-origin' }).then(response => {
+ resolve(response.data);
+ }).catch(webError => {
+ reject(webError)
+ })
+ } catch (error) {
+ reject(error)
+ }
+ })
+ }
+
+ /**
+ * Start LiveChart Interval
+ *
+ * @param {Number} refreshRate How often send request for a new data.
+ */
+ startLiveUpdate(refreshRate, exportId) {
+ this.liveUpdateInterval = setInterval(() => {
+ // this.loadLiveData()
+ this.refreshPointValues(exportId);
+ }, refreshRate);
+ }
+
+ /**
+ * Connect with API and parse new data for chart
+ * @deprecated
+ */
+ loadLiveData() {
+ for (let [k, v] of this.pointCurrentValue) {
+ Axios.get(`${this.domain}/api/point_value/getValue/id/${k}`, { timeout: 5000, useCredentails: true, credentials: 'same-origin' }).then(response => {
+ if (isNaN(response.data.value)) {
+ response.data.value == "true" ? response.data.value = 1 : response.data.value = 0;
+ }
+ let point = { "name": response.data.name, "value": response.data.value };
+ if (this.liveUpdatePointValues.get(response.data.ts) == undefined) {
+ this.liveUpdatePointValues.set(response.data.ts, [point]);
+ } else {
+ this.liveUpdatePointValues.get(response.data.ts).push(point);
+ }
+ })
+ }
+ this.chart.addData(BaseChart.prepareChartData(BaseChart.sortMapKeys(this.liveUpdatePointValues)))
+ if (this.liveUpdatePointValues != undefined) {
+ this.liveUpdatePointValues.clear();
+ }
+ }
+
+ /**
+ * Get data point data from REST API.
+ *
+ * @param {Number} pointId ID of data point.
+ */
+ getPointValue(pointId) {
+ return new Promise((resolve, reject) => {
+ try {
+ Axios.get(`${this.domain}/api/point_value/getValue/id/${pointId}`, { timeout: 5000, useCredentails: true, credentials: 'same-origin' }).then(resp => {
+ resolve(resp.data);
+ }).catch(webError => {
+ reject(webError)
+ });
+ } catch (error) {
+ reject(error)
+ }
+ })
+ }
+
+ /**
+ * Get data point data from REST API.
+ *
+ * @param {Number} pointXid Export ID of data point.
+ */
+ getPointValueXid(pointXid) {
+ return new Promise((resolve, reject) => {
+ try {
+ Axios.get(`${this.domain}/api/point_value/getValue/${pointXid}`, { timeout: 5000, useCredentails: true, credentials: 'same-origin' }).then(resp => {
+ resolve(resp.data);
+ }).catch(webError => {
+ reject(webError)
+ });
+ } catch (error) {
+ reject(error)
+ }
+ })
+ }
+
+ /**
+ *
+ * @param {String} pointId PointExportID or PointID
+ * @param {Number} startTimestamp LastUpdateTime
+ * @param {Boolean} exportId Using XID or ID
+ */
+ getPeriodicUpdate(pointId, startTimestamp, exportId) {
+ let endTimestamp = new Date().getTime();
+ let url;
+ if (exportId) {
+ url = `${this.domain}/api/point_value/getValuesFromTimePeriod/xid/${pointId}/${startTimestamp}/${endTimestamp}`
+ } else {
+ url = `${this.domain}/api/point_value/getValuesFromTimePeriod/${pointId}/${startTimestamp}/${endTimestamp}`
+ }
+ return new Promise((resolve, reject) => {
+ try {
+ Axios.get(url, { timeout: 5000, useCredentails: true, credentials: 'same-origin' }).then(response => {
+ resolve(response.data);
+ }).catch(webError => {
+ reject(webError)
+ })
+ } catch (error) {
+ reject(error)
+ }
+ })
+ }
+
+ /**
+ * Stategy how to parse data received from API server.
+ *
+ * @param {Object} pointValue Object of recived point data value.
+ * @param {String} pointName Name of datapoint.
+ * @param {Map} referencedArray Map of values to which we want to save data. [pointPastValues or liveUpdatePoints]
+ */
+ addValue(pointValue, pointName, referencedArray) {
+ if (isNaN(pointValue.value)) {
+ pointValue.value == "true" ? pointValue.value = 1 : pointValue.value = 0;
+ }
+ let point = { "name": pointName, "value": pointValue.value };
+ if (referencedArray.get(pointValue.ts) == undefined) {
+ referencedArray.set(pointValue.ts, [point]);
+ } else {
+ referencedArray.get(pointValue.ts).push(point);
+ }
+ }
+
+ /**
+ * For each defined point get current value and update chart when all request has been recived.
+ */
+ refreshPointValues(exportId) {
+ let pointData = [];
+ for (let [k, v] of this.pointCurrentValue) {
+ if (exportId) {
+ pointData.push(this.getPeriodicUpdate(k, this.lastTimestamp, exportId).then(data => {
+ data.values.forEach(e => {
+ this.addValue(e, data.name, this.liveUpdatePointValues)
+ })
+ }))
+ }
+ }
+ Promise.all(pointData).then(() => {
+ let lastData = BaseChart.prepareChartData(this.liveUpdatePointValues);
+ if (lastData.length > 0) {
+ if (lastData[lastData.length - 1].date > this.lastUpdate) {
+ this.chart.addData(lastData, 1);
+ this.lastTimestamp = new Date().getTime();
+ this.lastUpdate = lastData[lastData.length - 1].date;
+ this.liveUpdatePointValues.clear();
+ if (lastData != undefined) {
+ // console.debug(lastData);
+ // lastData.clear();
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Clear all data associated with this chart.
+ */
+ stopLiveUpdate() {
+ clearInterval(this.liveUpdateInterval);
+ this.pointCurrentValue.clear();
+ this.pointPastValues.clear();
+ this.liveUpdatePointValues.clear();
+ }
+
+ /**
+ * When all point data has been downloaded, add this to chart data.
+ */
+ setupChart() {
+ this.chart.data = BaseChart.prepareChartData(BaseChart.sortMapKeys(this.pointPastValues));
+ this.createAxisX("DateAxis", null);
+ this.createAxisY();
+ this.createScrollBarsAndLegend();
+ this.createExportMenu();
+ for (let [k, v] of this.pointCurrentValue) {
+ this.createSeries("StepLine", "date", v.name, v.name)
+ }
+ }
+
+
+ /**
+ * Prepare series for chart.
+ *
+ * @param {String} seriesType [ Column | Pie | Line | StepLine ] types
+ * @param {String} seriesValueX Category name or "date" field in data.
+ * @param {String} seriesValueY Which values disply in series
+ * @param {String} seriesName Name of this series.
+ * @param {string} suffix Additional suffix for series units. [square meteters etc.]
+ */
+ createSeries(seriesType, seriesValueX, seriesValueY, seriesName, suffix = "") {
+ let series;
+ if (seriesType === "Column") {
+ series = this.chart.series.push(new am4charts.ColumnSeries());
+ } else if (seriesType === "Line") {
+ series = this.chart.series.push(new am4charts.LineSeries());
+ } else if (seriesType === "StepLine") {
+ series = this.chart.series.push(new am4charts.StepLineSeries());
+ series.startLocation = 0.5;
+ } else if (seriesType === "Pie") {
+ series = this.chart.series.push(new am4charts.PieSeries());
+ }
+
+ if (seriesType === "Column") {
+ series.dataFields.categoryX = seriesValueX;
+ series.dataFields.valueY = seriesValueY;
+ series.columns.template.tooltipText = "{valueY.value}";
+ series.columns.template.tooltipY = 0;
+ series.columns.template.strokeOpacity = 0;
+ } else if (seriesType === "Pie") {
+ series.dataFields.value = seriesValueY;
+ series.dataFields.category = seriesValueX;
+ series.slices.template.strokeWidth = 2;
+ series.slices.template.strokeOpacity = 1;
+ } else {
+ series.dataFields.dateX = seriesValueX;
+ series.dataFields.valueY = seriesValueY;
+ if (suffix.trim().startsWith("[")) {
+ suffix = `[${suffix}]`;
+ }
+ series.tooltipText = "{name}: [bold]{valueY}[/] " + suffix;
+ series.tooltip.background.cornerRadius = 20;
+ series.tooltip.background.strokeOpacity = 0;
+ series.tooltip.pointerOrientation = "vertical";
+ series.tooltip.label.minWidth = 40;
+ series.tooltip.label.minHeight = 40;
+ series.tooltip.label.textAlign = "middle";
+ series.tooltip.label.textValign = "middle";
+ series.strokeWidth = 3;
+ series.fillOpacity = 0.3;
+ series.minBulletDistance = 15;
+ let bullet = series.bullets.push(new am4charts.CircleBullet());
+ bullet.circle.strokeWidth = 2;
+ bullet.circle.radius = 5;
+ bullet.circle.fill = am4core.color("#fff");
+ }
+
+ series.name = seriesName;
+
+ if (this.chart.scrollbarX) {
+ this.chart.scrollbarX.series.push(series);
+ }
+
+ return series;
+
+ }
+
+ /**
+ * Create X data Axis for chart
+ * @param {String} axisType [ValueAxis | DateAxis | CategoryAxis] Specified types for different chart axes
+ * @param {String} category When Category Axis has been chosen set the name of category to display.
+ */
+ createAxisX(axisType, category) {
+ let axis;
+ if (axisType === "ValueAxis") {
+ axis = this.chart.xAxes.push(new am4charts.ValueAxis());
+ axis.min = 0;
+ axis.max = 100;
+ axis.renderer.grid.template.strokeOpacity = 0.3;
+ } else if (axisType === "DateAxis") {
+ axis = this.chart.xAxes.push(new am4charts.DateAxis());
+ axis.dateFormats.setKey("second", "HH:mm:ss")
+ axis.dateFormats.setKey("minute", "HH:mm:ss")
+ axis.dateFormats.setKey("hour", "HH:mm")
+ axis.dateFormats.setKey("day", "MMM dd")
+ axis.periodChangeDateFormats.setKey("hour", "[bold]dd MMM HH:mm[/]");
+ axis.periodChangeDateFormats.setKey("day", "[bold]MMM[/] dd");
+ axis.periodChangeDateFormats.setKey("month", "[bold]yyyy[/] MMM");
+ } else if (axisType === "CategoryAxis") {
+ axis = this.chart.xAxes.push(new am4charts.CategoryAxis());
+ axis.dataFields.category = category;
+ axis.renderer.grid.template.location = 0;
+ axis.renderer.minGridDistance = 30;
+ axis.renderer.labels.template.horizontalCenter = "right";
+ axis.renderer.labels.template.verticalCenter = "middle";
+ axis.renderer.labels.template.rotation = 315;
+ axis.tooltip.disabled = true;
+ axis.renderer.minHeight = 110;
+ }
+ }
+
+ /**
+ * Create Y Value Axis for chart
+ * @param {Map} textLabels Map with key value pair. In keys are values to be converted into label text
+ * @return yAxis definition.
+ */
+ createAxisY(textLabels) {
+ let axis;
+ axis = this.chart.yAxes.push(new am4charts.ValueAxis());
+ axis.tooltip.disabled = false;
+ axis.renderer.opposite = Boolean(this.yAxesCount % 2);
+
+ //TextRender
+ if (textLabels !== undefined) {
+ if (textLabels.size > 0) {
+ axis.renderer.labels.template.adapter.add("text", function (text) {
+ if (textLabels.get(text) !== undefined) {
+ return textLabels.get(text);
+ } else {
+ return "";
+ }
+ })
+ }
+ }
+ this.yAxesCount = this.yAxesCount + 1;
+ return axis;
+ }
+
+ /**
+ * Add single line range gudided by label to chart.
+ * @param value Value of line for yAxis
+ * @param color Color of this line
+ * @param label Label for this line (eg. 'average count')
+ */
+ addRangeValue(value, color, label) {
+ if (color === undefined || color === "") {
+ color = "#FF150A";
+ }
+ if (label === undefined) {
+ label = "";
+ }
+ let range = this.chart.yAxes.getIndex(0).axisRanges.create();
+ range.value = value;
+ range.grid.stroke = am4core.color(color);
+ range.grid.strokeWidth = 2;
+ range.grid.strokeOpacity = 1;
+ range.label.inside = true;
+ range.label.text = label;
+ range.label.fill = range.grid.stroke;
+ range.label.verticalCenter = "bottom";
+ }
+
+ /**
+ * Create specific elements of chart.
+ *
+ * @param {Boolean} [scrollbarX=true] Show scrollbar for xAxes
+ * @param {Boolean} [scrollbarY=false] Show scrollbar for yAxes
+ * @param {Boolean} [legend=true] Show chart legend
+ * @param {Boolean} [cursor=true] Show cursor over the chart
+ */
+ createScrollBarsAndLegend(scrollbarX = true, scrollbarY = false, legend = true, cursor = true) {
+ if (scrollbarX) {
+ this.chart.scrollbarX = new am4charts.XYChartScrollbar();
+ this.chart.scrollbarX.parent = this.chart.bottomAxesContainer;
+ }
+ if (scrollbarY) {
+ this.chart.scrollbarY = new am4core.Scrollbar();
+ this.chart.scrollbarY.parent = this.chart.leftAxesContainer;
+ }
+ if (legend) {
+ this.chart.legend = new am4charts.Legend();
+ }
+ if (cursor) {
+ this.chart.cursor = new am4charts.XYCursor();
+ this.chart.cursor.behavior = "panXY";
+ }
+ }
+
+ /**
+ * Add export possibility to chart. Save chart as an image or export chart data to *.csv or *.xlsx format.
+ *
+ * @param {Boolean} [enabled = true] is Export menu enabled in this chart.
+ * @param {String} [filePrefix = "Scada_Chart"] File name to which save exported chart data.
+ */
+ createExportMenu(enabled = true, filePrefix = "Scada_Chart") {
+ if (enabled) {
+ this.chart.exporting.menu = new am4core.ExportMenu();
+ this.chart.exporting.menu.align = "right"
+ this.chart.exporting.menu.vetricalAlign = "top"
+ this.chart.exporting.filePrefix = filePrefix + "_" + String(new Date().getTime());
+ }
+ }
+
+ /**
+ * Improving perfromance of chart. Display only a points every "step" pixels omitting this between.
+ * It is useful for charts presenting huge amount of data. For example charts displaying values from one month period.
+ *
+ * @param {Number} step - Ommit all line point if they are closer than "step" pixels to the last point drawn
+ */
+ static setPolylineStep(step) {
+ am4core.options.minPolylineStep = step;
+ }
+
+ /**
+ * Order values stored inside Map by keys (key == timestamp)
+ *
+ * @param {Map} map Scada Data Point Values Map
+ */
+ static sortMapKeys(map) {
+ var sortByKeys = (a, b) => a[0] > b[0] ? 1 : -1
+ return new Map([...map].sort(sortByKeys))
+ }
+
+ /**
+ *
+ * @param {Map} map Sorted keys in chronological order TimeValueMap
+ */
+ static prepareChartData(map) {
+ let data = []; // [{date:, :}]
+ map.forEach(function (value, key) {
+ let jsonString = '{ "date":' + key
+ value.forEach(e => {
+ if (!isNaN(Number(e.value))) {
+ jsonString = jsonString + ', "' + e.name + '":' + e.value;
+ }
+ })
+ jsonString = jsonString + '}';
+ data.push(JSON.parse(jsonString));
+ });
+ return data;
+ }
+
+ /**
+ * Util for converting from text time to specific timestamp
+ *
+ * @param {String} dateText Text based time period (date format or written format)
+ * @returns Calculated timestamp
+ */
+ static convertDate(dateText) {
+ let date = new Date(dateText);
+ if (date == "Invalid Date") {
+ date = dateText.split("-");
+ if (date.length === 2) {
+ let dateNow = new Date();
+ let multiplier = 1;
+ switch (date[1]) {
+ case "hour":
+ case "hours":
+ multiplier = 1000 * 3600;
+ break;
+ case "day":
+ case "days":
+ multiplier = 1000 * 3600 * 24;
+ break;
+ case "week":
+ case "weeks":
+ multiplier = 1000 * 3600 * 24 * 7;
+ break;
+ case "month":
+ case "months":
+ multiplier = 1000 * 3600 * 24 * 31;
+ break;
+ }
+ return dateNow.getTime() - Number(date[0]) * multiplier;
+ } else {
+ console.warn("Not vaild date. Use for example ['1-day' | '2-months' | '3-days']\nReturning default value!")
+ return dateNow.getTime() - 3600000;
+ }
+ } else {
+ return date.getTime();
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/scadalts-ui/src/components/amcharts/LineChartComponent.vue b/scadalts-ui/src/components/amcharts/LineChartComponent.vue
new file mode 100644
index 0000000000..4d38edd98c
--- /dev/null
+++ b/scadalts-ui/src/components/amcharts/LineChartComponent.vue
@@ -0,0 +1,201 @@
+
+
+
{{label}}
+
+
+
+ Reload
+
+
+
+
+
\ No newline at end of file
diff --git a/scadalts-ui/src/components/amcharts/StepLineChartComponent.vue b/scadalts-ui/src/components/amcharts/StepLineChartComponent.vue
new file mode 100644
index 0000000000..473380fdc6
--- /dev/null
+++ b/scadalts-ui/src/components/amcharts/StepLineChartComponent.vue
@@ -0,0 +1,218 @@
+
+
+
{{label}}
+
+
+
+ Reload
+
+
+ Debug
+
+
+
+
+
\ No newline at end of file
diff --git a/scadalts-ui/src/components/amcharts/readme.md b/scadalts-ui/src/components/amcharts/readme.md
new file mode 100644
index 0000000000..865d454fa4
--- /dev/null
+++ b/scadalts-ui/src/components/amcharts/readme.md
@@ -0,0 +1,131 @@
+# Modern Charts Components
+## February 2020 - Version 1.0.2
+ScadaLTS modern charts components it is a set of new VueJS v2.0 components designed for GraphicalView in ScadaLTS. It is based on [am4chart](https://www.amcharts.com/) library. It generates charts using JavaScript from user-side which is a new approach to charts in Scada (they were generated via server-side scripts and libraries). It is more browser load than it was before, but server application becomes lighter and gains performance.
+
+## Types of charts:
+- __\__ Line Series Chart
+- __\__ Step Line Series Chart
+
+## Usage:
+New charts could be added to ScadaLTS Graphical View by adding a new HTML component with specific content. Each chart has to be initialized by using this listed above Extended HTML Tags. Each of this tag take a specific properties required to set up specific chart. Chart is generated inside this tag which has default size 750x500px.
+***
+### Quick start:
+Create simple line chart for specific [ numeric | multistate | binary ] data point.
+```
+
+```
+
+or
+
+```
+
+```
+
+That's it!\
+It has rendered line chart for specific point from last hour with default parameters. So if you want to monitor the state of the point from last hour it is the simplest way how to do it. This chart could be zoomed in and out using scrollbar at the bottom of the component. Values of data point in time are represented by white dots on the chart.
+
+But it is still just a chart like this old ones... What if we really want to monitor status of this point __in real-time__? No problem just add next properties.
+***
+### Live Data
+```
+
+```
+__Now we've got live chart!__\
+It is refreshed every 10s (10000 ms) and when a data point will change state to different value this new one will be added to chart and the oldest one will be deleted from out chart. Now we can monitor state of datapoint in real-time with chosen by us refresh rate. For critical data, we can monitor the status of the point with a high frequency of queries to server (more real-time data but more resource consuming) and for non-critical data we can refresh chart after a few seconds.
+
+But what if we want to display chart for __multiple data points?__
+***
+### Multiple points
+Just add next data point after comma in _'point-id'_ property.
+```
+
+```
+![Example Chart](../../assets/doc/watch_list/MWL_4-DataPoints.gif)
+Now we have chart for 3 data points with values from last 1 hour. This components do not have limitations for a count of points displayed on the one chart, but I hope that you have an intuition that 30 point on a single chart is not a wise move.
+
+Can we display __older values__ than last one hour?
+***
+### Specified time period
+Yes! Just add a new property to our tag.
+```
+
+```
+As you can see it's a piece of cake. Just type inside 'start-date' property, time period from which you want to see the data. You can use a every combination of numbers with specific time period __[ hour(s) | day(s) | weak(s) | month(s) ]__. (eg. '2-days', '1-week', '3-months' etc.) But it is not everything! It is dynamic calculated time from now but we can also use a specific date. If we want see data from beginning of the previous year just type in date _(eg. '2019/02/01' to see data beginning from 1-st February 2019)_. It could be useful to limit displayed data.
+
+To display values from specified period just add __'end-date'__ parameter.
+```
+
+```
+And it still works with multiple data points. It's great! Isn't it? \
+But what if I want to add a horizontal line to chart to create for example warning level, which of it is exceeded it could be dangerous?
+***
+### Level range line
+Ok let's consider this one:
+```
+
+```
+Now we have created horizontal line for our chart which indicates boiling level for water. Thanks to that we can quickly observe that temperature of water inside tank is boiling. It is useful even inside ScadaLTS.
+
+Wait a moment! We decided which color this horizontal line would have. Could we do the same with chart lines?
+***
+### Chart Colors
+For example we have got 3 sensors. This default green colors are too similar. Can we set up a different color set for our charts. Just add this parameter:
+```
+
+```
+Now we have got defined 3 custom colors for our charts. We can give just a one color value and the rest will be retrieved from this default values. What is the most important... __USE HEXADECIMAL COLOR CODE VALUES__\
+Pretty colorful Modern Charts. But we still have the same size for them... Yes, yes it also could be changed.
+***
+### Chart Size
+```
+
+```
+HD Chart? Why not! Values for attributes are given in Pixels (px). That is useful when we have defined a multiple chart instances on one GraphicalView. We can easily calculate the position of the next chart.
+
+### Labels
+```
+
+```
+
+That would be enough from the basics... It is time for more complex tasks.
+***
+### Multiple charts
+To generate multiple charts on View page just use unique identifiers.
+```
+
+
+
+
+
+```
+![Multiple Charts](../../assets/doc/watch_list/MWL_CompareCharts.gif)
+
+## Modern Chart documentation:
+Available properties in one place for all chart types. Charts _(excluding Gauge Charts)_ could be exported to external file in graphical or text way. You can export to *.png, *.jpg, *.csv, *.json files.
+
+Properties properties for Step Line, Line charts
+- point-id
+- point-xid
+- label
+- width
+- height
+- color
+- start-date
+- end-date
+- refresh-rate
+- polyline-step
+- range-value
+- range-color
+- range-label
+- show-scrollbar-x
+- show-scrollbar-y
+- show-legned
+
+# Author
+
+- [Radosław Jajko](https://github.com/radek2s): __rjajko@softq.pl__
+
+### Notes:
+ More image examples can be find here:
+
+[Watch List Example Images](../../assets/doc/watch_list/)
\ No newline at end of file
diff --git a/scadalts-ui/src/components/watch_list/WatchListChart.vue b/scadalts-ui/src/components/watch_list/WatchListChart.vue
new file mode 100644
index 0000000000..25531f4bca
--- /dev/null
+++ b/scadalts-ui/src/components/watch_list/WatchListChart.vue
@@ -0,0 +1,318 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Values from last:
+
+
+
+ {{option.text}}
+
+
+
+
+ Chart performance
+
+
+ {{option.text}}
+
+
+
+
+
+
+ Start Date
+
+
+
+ End Date
+
+
+
+
+
+ Chart color
+
+
+
+ Range value
+
+
+
+ Range color
+
+
+
+ Range label
+
+
+
+ Chart heigh
+
+
+
+
+
Close
+
Save
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/scadalts-ui/src/components/watch_list/WatchListChartWidget.vue b/scadalts-ui/src/components/watch_list/WatchListChartWidget.vue
new file mode 100644
index 0000000000..02ef548f4d
--- /dev/null
+++ b/scadalts-ui/src/components/watch_list/WatchListChartWidget.vue
@@ -0,0 +1,190 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/scadalts-ui/src/components/watch_list/readme.md b/scadalts-ui/src/components/watch_list/readme.md
new file mode 100644
index 0000000000..818a47cca2
--- /dev/null
+++ b/scadalts-ui/src/components/watch_list/readme.md
@@ -0,0 +1,11 @@
+#Modern Watch List View
+Created by _Radek Jajko_ [rjajko@softq.pl](mail:rjajko@softq.pl)
+
+Modern Wach list is a new View for ScadaLTS prepared next to old classic WatchList page. It base on WachList
+data and genereate a Modern Charts using AmChart4 library.
+
+##Example usage:
+It is as simple as user expect. Just click __add chart__ and everything is ready!
+![AddChart](../../assets/doc/watch_list/MWL_AddChart.gif)
+Modern Chart is a vue.js component that can be placed in other views. On the Modern Watch List you can find
+settings button to modify chart behaviour. Complete guide how to use Modern Charts in Graphical Views you can find [here](../amcharts/readme.md)
\ No newline at end of file
diff --git a/scadalts-ui/src/main.js b/scadalts-ui/src/main.js
index 688cfca3dd..33bb7dbb2b 100644
--- a/scadalts-ui/src/main.js
+++ b/scadalts-ui/src/main.js
@@ -3,6 +3,7 @@ import App from './apps/App.vue'
import router from './router'
import store from './store'
import * as uiv from 'uiv'
+import VueCookie from 'vue-cookie'
import VueLogger from 'vuejs-logger'
import 'bootstrap/dist/css/bootstrap.min.css'
@@ -12,16 +13,21 @@ import CMP from './components/graphical_views/cmp/CMP'
import SimpleComponentSVG from './components/graphical_views/SimpleComponentSVG'
import ExportImportPointHierarchy from './components/point_hierarchy/ExportImportPointHierarchy'
import SleepAndReactivationDS from './components/forms/SleepAndReactivationDS'
+// import ExampleChartCmp from './views/components/ExampleChartCmp'
+import WatchListChartWidget from './components/watch_list/WatchListChartWidget'
import VueLodash from 'vue-lodash'
+import StepLineChartComponent from './components/amcharts/StepLineChartComponent'
+import LineChartComponent from './components/amcharts/LineChartComponent'
+
const isProduction = process.env.NODE_ENV === 'production';
const options = {
isEnabled: true,
- logLevel : isProduction ? 'error' : 'debug',
- stringifyArguments : false,
- showLogLevel : true,
- showMethodName : true,
+ logLevel: isProduction ? 'error' : 'debug',
+ stringifyArguments: false,
+ showLogLevel: true,
+ showMethodName: true,
separator: '|',
showConsoleColors: true
};
@@ -33,6 +39,7 @@ const optionsLodash = { name: 'lodash' }
Vue.use(VueLodash, optionsLodash)
Vue.use(uiv)
+Vue.use(VueCookie)
Vue.config.devtools = true
@@ -47,7 +54,7 @@ if (window.document.getElementById('app-test') != undefined) {
render: h => h(Test,
{
props:
- {plabel: window.document.getElementById('app-test').getAttribute('plabel')}
+ { plabel: window.document.getElementById('app-test').getAttribute('plabel') }
})
}).$mount('#app-test')
}
@@ -58,12 +65,12 @@ if (window.document.getElementById('app-isalive') != undefined) {
render: h => h(IsAlive,
{
props:
- {
- plabel: window.document.getElementById('app-isalive').getAttribute('plabel'),
- ptimeWarning: window.document.getElementById('app-isalive').getAttribute('ptime-warning'),
- ptimeError: window.document.getElementById('app-isalive').getAttribute('ptime-error'),
- ptimeRefresh: window.document.getElementById('app-isalive').getAttribute('ptime-refresh'),
- }
+ {
+ plabel: window.document.getElementById('app-isalive').getAttribute('plabel'),
+ ptimeWarning: window.document.getElementById('app-isalive').getAttribute('ptime-warning'),
+ ptimeError: window.document.getElementById('app-isalive').getAttribute('ptime-error'),
+ ptimeRefresh: window.document.getElementById('app-isalive').getAttribute('ptime-refresh'),
+ }
})
}).$mount('#app-isalive')
}
@@ -76,12 +83,12 @@ for (let i = 0; i < 20; i++) {
{
store,
props:
- {
- pLabel: window.document.getElementById(cmpId).getAttribute('plabel'),
- pTimeRefresh: window.document.getElementById(cmpId).getAttribute('ptimeRefresh'),
- pConfig: window.document.getElementById(cmpId).getAttribute('pconfig'),
- pxIdViewAndIdCmp: window.document.getElementById(cmpId).getAttribute('pxIdViewAndIdCmp')
- }
+ {
+ pLabel: window.document.getElementById(cmpId).getAttribute('plabel'),
+ pTimeRefresh: window.document.getElementById(cmpId).getAttribute('ptimeRefresh'),
+ pConfig: window.document.getElementById(cmpId).getAttribute('pconfig'),
+ pxIdViewAndIdCmp: window.document.getElementById(cmpId).getAttribute('pxIdViewAndIdCmp')
+ }
})
}).$mount('#' + cmpId)
}
@@ -91,12 +98,12 @@ if (window.document.getElementById('simple-component-svg') != undefined) {
new Vue({
render: h => h(SimpleComponentSVG, {
props:
- {
- pxidPoint: window.document.getElementById('simple-component-svg').getAttribute('pxidPoint'),
- ptimeRefresh: window.document.getElementById('simple-component-svg').getAttribute('ptimeRefresh'),
- plabel: window.document.getElementById('simple-component-svg').getAttribute('plabel'),
- pvalue: window.document.getElementById('simple-component-svg').getAttribute('pvalue')
- }
+ {
+ pxidPoint: window.document.getElementById('simple-component-svg').getAttribute('pxidPoint'),
+ ptimeRefresh: window.document.getElementById('simple-component-svg').getAttribute('ptimeRefresh'),
+ plabel: window.document.getElementById('simple-component-svg').getAttribute('plabel'),
+ pvalue: window.document.getElementById('simple-component-svg').getAttribute('pvalue')
+ }
})
}).$mount('#simple-component-svg')
}
@@ -112,3 +119,67 @@ if (window.document.getElementById('export-import-ph') != undefined) {
render: h => h(ExportImportPointHierarchy)
}).$mount('#export-import-ph')
}
+
+if (window.document.getElementById('example-chart-cmp') != undefined) {
+ new Vue({
+ render: h => h(WatchListChartWidget)
+ }).$mount('#example-chart-cmp')
+}
+
+for (let x = 0; x < 10; x++) {
+ const chartId = `chart-step-line-${x}`
+ if (window.document.getElementById(chartId) != undefined) {
+ new Vue({
+ render: h => h(StepLineChartComponent, {
+ props:
+ {
+ pointId: window.document.getElementById(chartId).getAttribute('point-id'),
+ pointXid: window.document.getElementById(chartId).getAttribute('point-xid'),
+ color: window.document.getElementById(chartId).getAttribute('color'),
+ label: window.document.getElementById(chartId).getAttribute('label'),
+ startDate: window.document.getElementById(chartId).getAttribute('start-date'),
+ endDate: window.document.getElementById(chartId).getAttribute('end-date'),
+ refreshRate: window.document.getElementById(chartId).getAttribute('refresh-rate'),
+ width: window.document.getElementById(chartId).getAttribute('width'),
+ height: window.document.getElementById(chartId).getAttribute('height'),
+ polylineStep: window.document.getElementById(chartId).getAttribute('polyline-step'),
+ rangeValue: window.document.getElementById(chartId).getAttribute('range-value'),
+ rangeColor: window.document.getElementById(chartId).getAttribute('range-color'),
+ rangeLabel: window.document.getElementById(chartId).getAttribute('range-label'),
+ showScrollbarX: window.document.getElementById(chartId).getAttribute('show-scrollbar-x'),
+ showScrollbarY: window.document.getElementById(chartId).getAttribute('show-scrollbar-y'),
+ showLegend: window.document.getElementById(chartId).getAttribute('show-legned'),
+ }
+ })
+ }).$mount(`#${chartId}`)
+ }
+}
+
+for (let x = 0; x < 10; x++) {
+ const chartId = `chart-line-${x}`
+ if (window.document.getElementById(chartId) != undefined) {
+ new Vue({
+ render: h => h(LineChartComponent, {
+ props:
+ {
+ pointId: window.document.getElementById(chartId).getAttribute('point-id'),
+ pointXid: window.document.getElementById(chartId).getAttribute('point-xid'),
+ color: window.document.getElementById(chartId).getAttribute('color'),
+ label: window.document.getElementById(chartId).getAttribute('label'),
+ startDate: window.document.getElementById(chartId).getAttribute('start-date'),
+ endDate: window.document.getElementById(chartId).getAttribute('end-date'),
+ refreshRate: window.document.getElementById(chartId).getAttribute('refresh-rate'),
+ width: window.document.getElementById(chartId).getAttribute('width'),
+ height: window.document.getElementById(chartId).getAttribute('height'),
+ polylineStep: window.document.getElementById(chartId).getAttribute('polyline-step'),
+ rangeValue: window.document.getElementById(chartId).getAttribute('range-value'),
+ rangeColor: window.document.getElementById(chartId).getAttribute('range-color'),
+ rangeLabel: window.document.getElementById(chartId).getAttribute('range-label'),
+ showScrollbarX: window.document.getElementById(chartId).getAttribute('show-scrollbar-x'),
+ showScrollbarY: window.document.getElementById(chartId).getAttribute('show-scrollbar-y'),
+ showLegend: window.document.getElementById(chartId).getAttribute('show-legned'),
+ }
+ })
+ }).$mount(`#${chartId}`)
+ }
+}
\ No newline at end of file
diff --git a/scadalts-ui/src/router.js b/scadalts-ui/src/router.js
index 196b10537c..8eabce01ec 100644
--- a/scadalts-ui/src/router.js
+++ b/scadalts-ui/src/router.js
@@ -55,6 +55,16 @@ export default new Router({
path: '/example-cmp',
name: 'example-cmp',
component: () => import(/* webpackChunkName: "cmp-component" */ './views/components/ExampleCmp.vue')
+ },
+ {
+ path: '/example-chart-cmp',
+ name: 'example-chart-cmp',
+ component: () => import(/* webpackChunkName: "example-chart-cmp" */ './views/components/ExampleChartCmp.vue')
+ },
+ {
+ path: '/example-step-line-chart-cmp',
+ name: 'example-step-line-chart-cmp',
+ component: () => import(/* webpackChunkName: "step-line-chart-component" */ './views/components/ExampleStepLineChartCmp.vue')
}
]
})
diff --git a/scadalts-ui/src/store/amcharts/index.js b/scadalts-ui/src/store/amcharts/index.js
new file mode 100644
index 0000000000..11360806a0
--- /dev/null
+++ b/scadalts-ui/src/store/amcharts/index.js
@@ -0,0 +1,77 @@
+import Axios from 'axios'
+
+import BaseChart from "../../components/amcharts/BaseChart"
+
+/**
+ *
+ */
+//TODO: Prepare Vuex store to manage state of ModernCharts
+const modernCharts = {
+ modules: {
+
+ },
+ state: {
+ domain: '.',
+ timeout: 2000
+ },
+ mutations: {
+
+ },
+ actions: {
+ loadData({ state, dispatch }, pointId, startTimestamp, endTimestamp) {
+ const url = `${state.domain}/api/point_value/getValuesFromTimePeriod`;
+ let timestamp = validateInputData(startTimestamp, endTimestamp)
+ return new Promise((resolve, reject) => {
+ try {
+ Axios.get(`${url}/${pointId}/${timestamp.start}/${timestamp.end}`, {
+ timeout: state.timeout,
+ useCredentails: true,
+ credentials: 'same-origin'
+ }).then(response => {
+ resolve(response.data);
+ }).catch(webError => { reject(webError) })
+ } catch (error) { reject(error) }
+ })
+ },
+ getPointValueById({ dispatch }, pointId) {
+ const url = `${state.domain}/api/point_value/getValue/id`;
+ return new Promise((resolve, reject) => {
+ try {
+ Axios.get(`${url}/${pointId}`, {
+ timeout: state.timeout,
+ useCredentails: true,
+ credentials: 'same-origin'
+ }).then(resp => {
+ resolve(resp.data);
+ }).catch(webError => { reject(webError) });
+ } catch (error) { reject(error) }
+ })
+ }
+ },
+ getters: {
+
+ }
+}
+function validateInputData(startTimestamp, endTimestamp) {
+ if (startTimestamp === undefined || startTimestamp === null) {
+ startTimestamp = new Date().getTime() - 3600000;
+ endTimestamp = new Date().getTime();
+ } else if ((startTimestamp !== undefined && startTimestamp !== null) && (endTimestamp === undefined || endTimestamp === null)) {
+ startTimestamp = BaseChart.convertDate(startTimestamp);
+ endTimestamp = new Date().getTime();
+ if (isNaN(startTimestamp)) {
+ console.warn("Parameter start-date is not valid!\nConnecting to API with default values");
+ startTimestamp = new Date().getTime() - 3600000;
+ }
+ } else if ((startTimestamp !== undefined && startTimestamp !== null) && (endTimestamp !== undefined && endTimestamp !== null)) {
+ startTimestamp = new Date(startTimestamp).getTime();
+ endTimestamp = new Date(endTimestamp).getTime();
+ if (isNaN(startTimestamp) || isNaN(endTimestamp)) {
+ console.warn("Parameter [start-date | end-date] are not valid!\nConnecting to API with default values")
+ startTimestamp = new Date().getTime() - 3600000;
+ endTimestamp = new Date().getTime();
+ }
+ }
+ return { "start": startTimestamp, "end": endTimestamp };
+}
+export default modernCharts
\ No newline at end of file
diff --git a/scadalts-ui/src/store/amcharts/storeStepLine.js b/scadalts-ui/src/store/amcharts/storeStepLine.js
new file mode 100644
index 0000000000..ca454bdb77
--- /dev/null
+++ b/scadalts-ui/src/store/amcharts/storeStepLine.js
@@ -0,0 +1,15 @@
+const storeStepLine = {
+ state: {
+
+ },
+ mutations: {
+
+ },
+ actions: {
+
+ },
+ getters: {
+
+ }
+}
+export default storeStepLine
\ No newline at end of file
diff --git a/scadalts-ui/src/store/index.js b/scadalts-ui/src/store/index.js
index 05a231e888..1d692ba425 100644
--- a/scadalts-ui/src/store/index.js
+++ b/scadalts-ui/src/store/index.js
@@ -3,6 +3,7 @@ import Vuex from 'vuex'
import dataSource from "./dataSource"
import graphicView from "./graphicView"
import pointHierarchy from "./pointHierarchy"
+import amcharts from './amcharts'
Vue.use(Vuex)
@@ -16,7 +17,8 @@ export default new Vuex.Store({
modules: {
dataSource,
graphicView,
- pointHierarchy
+ pointHierarchy,
+ amcharts
},
state: {
diff --git a/scadalts-ui/src/views/Components.vue b/scadalts-ui/src/views/Components.vue
index 6fef447f86..2e7bbf16c7 100644
--- a/scadalts-ui/src/views/Components.vue
+++ b/scadalts-ui/src/views/Components.vue
@@ -10,6 +10,8 @@
Tests of the SleepAndReactivationDS component
Tests of the CMP component
Tests of the ExportImportPointHierarchy component
+ Tests of the AmCharts component
+ Tests of the StepLine Chart component
diff --git a/scadalts-ui/src/views/components/ExampleChartCmp.vue b/scadalts-ui/src/views/components/ExampleChartCmp.vue
new file mode 100644
index 0000000000..4eec73d40d
--- /dev/null
+++ b/scadalts-ui/src/views/components/ExampleChartCmp.vue
@@ -0,0 +1,33 @@
+
+
+
Example of the use of the AmCharts component
+
+
+
+
+ Describe: A simple test to see if the value of the component is displayed.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scadalts-ui/src/views/components/ExampleStepLineChartCmp.vue b/scadalts-ui/src/views/components/ExampleStepLineChartCmp.vue
new file mode 100644
index 0000000000..221124235b
--- /dev/null
+++ b/scadalts-ui/src/views/components/ExampleStepLineChartCmp.vue
@@ -0,0 +1,33 @@
+
+
+
Example of the use of the StepLineChart component
+
+
+
+
+ Describe: A simple test to see if the chart is displayed.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/org/scada_lts/web/mvc/api/PointValueAPI.java b/src/org/scada_lts/web/mvc/api/PointValueAPI.java
index 32b665e6ab..ccfe6e314b 100644
--- a/src/org/scada_lts/web/mvc/api/PointValueAPI.java
+++ b/src/org/scada_lts/web/mvc/api/PointValueAPI.java
@@ -361,6 +361,40 @@ public ResponseEntity getValue(@PathVariable("xid") String xid, HttpServ
}
}
+ /**
+ * @param id
+ * @param request
+ * @return
+ */
+ @RequestMapping(value = "/api/point_value/getValue/id/{id}", method = RequestMethod.GET)
+ public ResponseEntity getValue(@PathVariable("id") int id, HttpServletRequest request) {
+ LOG.info("/api/point_value/getValue/id/{id} id:" + id);
+
+ try {
+ User user = Common.getUser(request);
+
+ if (user != null) {
+ DataPointVO dpvo = dataPointService.getDataPoint(id);
+ PointValueTime pvt = pointValueService.getLatestPointValue(dpvo.getId());
+ String json = null;
+ ObjectMapper mapper = new ObjectMapper();
+
+ ValueToJSON v = new ValueToJSON();
+ v.set(pvt, dpvo);
+
+ json = mapper.writeValueAsString(v);
+
+ return new ResponseEntity(json, HttpStatus.OK);
+ }
+
+ return new ResponseEntity(HttpStatus.UNAUTHORIZED);
+
+ } catch (Exception e) {
+ LOG.error(e);
+ return new ResponseEntity(HttpStatus.BAD_REQUEST);
+ }
+ }
+
/**
* @param xid
@@ -467,6 +501,132 @@ public ResponseEntity getValuesFromTime(@PathVariable("ts") long ts, @Pa
}
}
+ /**
+ * @param ts, xid
+ * @param request
+ * @return
+ */
+ @RequestMapping(value = "/api/point_value/getValuesFromTime/id/{ts}/{xid}", method = RequestMethod.GET)
+ public ResponseEntity getValuesFromTimeId(@PathVariable("ts") long ts, @PathVariable("xid") int id, HttpServletRequest request) {
+
+ LOG.info("/api/point_value/getValuesFromTime/{ts}/{xid} ts:" + ts + " id:" + id);
+
+ try {
+ User user = Common.getUser(request);
+ DataPointVO dpvo = dataPointService.getDataPoint(id);
+ if (user != null) {
+ long to = System.currentTimeMillis();
+ List pvts = pointValueService.getPointValuesBetween(dpvo.getId(), ts, to);
+ String json = null;
+ ObjectMapper mapper = new ObjectMapper();
+
+ List values = new ArrayList();
+ String type = null;
+ for (PointValueTime pvt : pvts) {
+ values.add(new ValueTime(getValue(pvt.getValue(), type), pvt.getTime()));
+ }
+ ValuesToJSON v = new ValuesToJSON(values, dpvo, type, ts, to);
+ json = mapper.writeValueAsString(v);
+
+ return new ResponseEntity(json, HttpStatus.OK);
+ }
+
+ return new ResponseEntity(HttpStatus.UNAUTHORIZED);
+
+ } catch (Exception e) {
+ LOG.error(e);
+ return new ResponseEntity(HttpStatus.BAD_REQUEST);
+ }
+ }
+
+ /**
+ * @param id, sts, ets - id of datapoint, start timestamp, end timestamp
+ * @param request
+ * @return
+ */
+ @RequestMapping(value = "/api/point_value/getValuesFromTimePeriod/xid/{xid}/{sts}/{ets}", method = RequestMethod.GET)
+ public ResponseEntity getValuesFromTimePeriodXid(@PathVariable("xid") String xid, @PathVariable("sts") long sts, @PathVariable("ets") long ets, HttpServletRequest request) {
+
+ LOG.info("/api/point_value/getValuesFromTimePeriod/xid/{id}/{sts}/{ets} id: " + xid + " sts: " + sts + " ets: " + ets);
+
+ try {
+ User user = Common.getUser(request);
+ DataPointVO dpvo = dataPointService.getDataPoint(xid);
+ if (user != null) {
+ List pvts = pointValueService.getPointValuesBetween(dpvo.getId(), sts, ets);
+ String json = null;
+ ObjectMapper mapper = new ObjectMapper();
+
+ List values = new ArrayList();
+ String type = null;
+ if(pvts.size() > 0) {
+ MangoValue value = pvts.get(0).getValue();
+ if(value instanceof AlphanumericValue) { type = "Alphanumeric"; }
+ else if(value instanceof BinaryValue) { type = "Binary"; }
+ else if(value instanceof MultistateValue) { type = "Multistate"; }
+ else if(value instanceof NumericValue) { type = "Numeric"; }
+ }
+ for (PointValueTime pvt : pvts) {
+ values.add(new ValueTime(getValue(pvt.getValue(), type), pvt.getTime()));
+ }
+ ValuesToJSON v = new ValuesToJSON(values, dpvo, type, sts, ets);
+ json = mapper.writeValueAsString(v);
+
+ return new ResponseEntity(json, HttpStatus.OK);
+ }
+
+ return new ResponseEntity(HttpStatus.UNAUTHORIZED);
+
+ } catch (Exception e) {
+ LOG.error(e);
+ return new ResponseEntity(HttpStatus.BAD_REQUEST);
+ }
+ }
+
+ /**
+ * @param id, sts, ets - id of datapoint, start timestamp, end timestamp
+ * @param request
+ * @return
+ */
+ @RequestMapping(value = "/api/point_value/getValuesFromTimePeriod/{id}/{sts}/{ets}", method = RequestMethod.GET)
+ public ResponseEntity getValuesFromTimePeriod(@PathVariable("id") int id, @PathVariable("sts") long sts, @PathVariable("ets") long ets, HttpServletRequest request) {
+
+ LOG.info("/api/point_value/getValuesFromTimePeriod/{id}/{sts}/{ets} id: " + id + " sts: " + sts + " ets: " + ets);
+
+ try {
+ User user = Common.getUser(request);
+ DataPointVO dpvo = dataPointService.getDataPoint(id);
+ if (user != null) {
+ List pvts = pointValueService.getPointValuesBetween(dpvo.getId(), sts, ets);
+ String json = null;
+ ObjectMapper mapper = new ObjectMapper();
+
+ List values = new ArrayList();
+ String type = null;
+ if(pvts.size() > 0) {
+ MangoValue value = pvts.get(0).getValue();
+ if(value instanceof AlphanumericValue) { type = "Alphanumeric"; }
+ else if(value instanceof BinaryValue) { type = "Binary"; }
+ else if(value instanceof MultistateValue) { type = "Multistate"; }
+ else if(value instanceof NumericValue) { type = "Numeric"; }
+ }
+ for (PointValueTime pvt : pvts) {
+ values.add(new ValueTime(getValue(pvt.getValue(), type), pvt.getTime()));
+ }
+ ValuesToJSON v = new ValuesToJSON(values, dpvo, type, sts, ets);
+ json = mapper.writeValueAsString(v);
+
+ return new ResponseEntity(json, HttpStatus.OK);
+ }
+
+ return new ResponseEntity(HttpStatus.UNAUTHORIZED);
+
+ } catch (Exception e) {
+ LOG.error(e);
+ return new ResponseEntity(HttpStatus.BAD_REQUEST);
+ }
+ }
+
@RequestMapping(value = "/api/point_value/updateMetaDataPointByScript/{xid}", method = RequestMethod.GET)
public ResponseEntity updateMetaDataPointByScript(@PathVariable("xid") String xid, HttpServletRequest request) {