-
+
-
+
-
+
diff --git a/src/renderer/components/commands/CommandSelector.vue b/src/renderer/components/commands/CommandSelector.vue
index 2150a87..0dfe7b5 100644
--- a/src/renderer/components/commands/CommandSelector.vue
+++ b/src/renderer/components/commands/CommandSelector.vue
@@ -81,6 +81,3 @@ export default class CommandSelector extends Vue {
}
}
-
-
diff --git a/src/renderer/components/commands/CommandUpdateDialog.vue b/src/renderer/components/commands/CommandUpdateDialog.vue
index 37b11dd..18cfb09 100644
--- a/src/renderer/components/commands/CommandUpdateDialog.vue
+++ b/src/renderer/components/commands/CommandUpdateDialog.vue
@@ -12,6 +12,7 @@
-
-
diff --git a/src/renderer/components/commands/ParametersPanel.vue b/src/renderer/components/commands/ParametersPanel.vue
index 0667ccf..5bd3626 100644
--- a/src/renderer/components/commands/ParametersPanel.vue
+++ b/src/renderer/components/commands/ParametersPanel.vue
@@ -18,7 +18,7 @@
>{{ (props.item.type.length > 25) ? props.item.type.substring(0, 25) + "..." : props.item.type }}
-
+
|
@@ -36,7 +36,7 @@
-
+
-
+
@@ -196,6 +196,3 @@ export default class ParametersPanel extends DialogSection {
}
}
-
-
diff --git a/src/renderer/components/common/ApplicationModel.ts b/src/renderer/components/common/ApplicationModel.ts
index ef74b8e..a319daa 100644
--- a/src/renderer/components/common/ApplicationModel.ts
+++ b/src/renderer/components/common/ApplicationModel.ts
@@ -5,3 +5,22 @@ export interface IAlertMessage {
message: string;
type: string;
}
+
+/**
+ * Information required to connect to a remote SiteWhere instance.
+ */
+export interface IRemoteConnection {
+ id: string;
+ name: string;
+ protocol: string;
+ host: string;
+ port: number;
+}
+
+/**
+ * Information about known remote SiteWhere instances.
+ */
+export interface IRemotes {
+ connections: IRemoteConnection[];
+ default: string;
+}
diff --git a/src/renderer/components/common/BrandingImage.vue b/src/renderer/components/common/BrandingImage.vue
new file mode 100644
index 0000000..aa57f5e
--- /dev/null
+++ b/src/renderer/components/common/BrandingImage.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/common/NoResultsPanel.vue b/src/renderer/components/common/NoResultsPanel.vue
index 1e18f80..d229812 100644
--- a/src/renderer/components/common/NoResultsPanel.vue
+++ b/src/renderer/components/common/NoResultsPanel.vue
@@ -1,59 +1,31 @@
-
-
-
- {{text}}
-
-
-
+
+
+
-
diff --git a/src/renderer/components/common/TreeNodePanel.vue b/src/renderer/components/common/TreeNodePanel.vue
new file mode 100644
index 0000000..aac4a62
--- /dev/null
+++ b/src/renderer/components/common/TreeNodePanel.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/src/renderer/components/common/Utils.ts b/src/renderer/components/common/Utils.ts
index bdc55a9..4597dad 100644
--- a/src/renderer/components/common/Utils.ts
+++ b/src/renderer/components/common/Utils.ts
@@ -1,5 +1,6 @@
import moment from "moment";
import { IAlertMessage } from "./ApplicationModel";
+import { AxiosResponse } from "axios";
import Vue from "vue";
/**
@@ -24,6 +25,20 @@ export function showMessage(component: Vue, message: string): IAlertMessage {
return alert;
}
+/**
+ * Show error message in snackbar.
+ * @param component
+ * @param message
+ */
+export function showError(component: Vue, message: string): IAlertMessage {
+ let alert: IAlertMessage = {
+ message: message,
+ type: "error"
+ };
+ component.$store.commit("message", alert);
+ return alert;
+}
+
/**
* Format date in YYYY-MM-DD H:mm:ss format. N/A for null.
* @param date
@@ -39,7 +54,7 @@ export function formatDate(date: Date) {
* Format date in YYYY-MM-DD H:mm:ss format.
* @param date
*/
-export function formatIso8601(date: Date) {
+export function formatIso8601(date: Date | null) {
if (!date) {
return null;
}
@@ -134,7 +149,11 @@ export function isAuthForAll(component: any, list: string[]): boolean {
export function routeTo(component: any, url: string): void {
var tenant = component.$store.getters.selectedTenant;
if (tenant) {
- component.$router.push("/tenants/" + tenant.token + url);
+ let route: string = "/tenants/" + tenant.token + url;
+ console.log("route to", route);
+ component.$router.push(route);
+ } else {
+ console.log("tenant was not set");
}
}
@@ -153,3 +172,46 @@ export function routeToDevice(component: any, token: string) {
export function pagingForAllResults() {
return "page=1&pageSize=0";
}
+
+/** Generate a unique id */
+export function generateUniqueId(): string {
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
+ let r = crypto.getRandomValues(new Uint8Array(1))[0] % 16 | 0;
+ let v = c === "x" ? r : (r & 0x3) | 0x8;
+ return v.toString(16);
+ });
+}
+
+/** Type guard to differentiate between responses */
+export function isAxiosResponse(
+ response: AxiosResponse | any
+): response is AxiosResponse {
+ return (response).data !== undefined;
+}
+
+/**
+ * Move an element in an array from one index to another.
+ * @param arr
+ * @param old_index
+ * @param new_index
+ */
+export function arrayMove(
+ arr: any[],
+ old_index: number,
+ new_index: number
+): any[] {
+ while (old_index < 0) {
+ old_index += arr.length;
+ }
+ while (new_index < 0) {
+ new_index += arr.length;
+ }
+ if (new_index >= arr.length) {
+ var k = new_index - arr.length;
+ while (k-- + 1) {
+ arr.push(undefined);
+ }
+ }
+ arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
+ return arr;
+}
diff --git a/src/renderer/components/common/dialog/DialogHeader.vue b/src/renderer/components/common/dialog/DialogHeader.vue
new file mode 100644
index 0000000..f2753b8
--- /dev/null
+++ b/src/renderer/components/common/dialog/DialogHeader.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/common/form/Chooser.vue b/src/renderer/components/common/form/Chooser.vue
new file mode 100644
index 0000000..62c2d5f
--- /dev/null
+++ b/src/renderer/components/common/form/Chooser.vue
@@ -0,0 +1,98 @@
+
+
+
+ {{ chosenText }}
+
+
+
+
+
+
+
+
+
+
+
+ delete
+
+
+
+
+
+
+ {{ notChosenText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/common/form/ClipboardCopyField.vue b/src/renderer/components/common/form/ClipboardCopyField.vue
new file mode 100644
index 0000000..5f11691
--- /dev/null
+++ b/src/renderer/components/common/form/ClipboardCopyField.vue
@@ -0,0 +1,44 @@
+
+
+ {{ field }}
+
+
+
+
+ Copy to Clipboard
+
+
+
+
+
diff --git a/src/renderer/components/common/form/DialogForm.vue b/src/renderer/components/common/form/DialogForm.vue
index 0499f97..8eed358 100644
--- a/src/renderer/components/common/form/DialogForm.vue
+++ b/src/renderer/components/common/form/DialogForm.vue
@@ -1,7 +1,7 @@
-
+
-
+
@@ -13,6 +13,3 @@ import { Component } from "sitewhere-ide-common";
@Component({})
export default class DialogForm extends Vue {}
-
-
diff --git a/src/renderer/components/common/form/FormDateTimePicker.vue b/src/renderer/components/common/form/FormDateTimePicker.vue
new file mode 100644
index 0000000..80133be
--- /dev/null
+++ b/src/renderer/components/common/form/FormDateTimePicker.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
diff --git a/src/renderer/components/common/form/FormSelectCondensed.vue b/src/renderer/components/common/form/FormSelectCondensed.vue
new file mode 100644
index 0000000..c60c68a
--- /dev/null
+++ b/src/renderer/components/common/form/FormSelectCondensed.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/common/form/FormToken.vue b/src/renderer/components/common/form/FormToken.vue
new file mode 100644
index 0000000..d316eca
--- /dev/null
+++ b/src/renderer/components/common/form/FormToken.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+ {{ label || 'Token'}} is required.
+ {{ label || 'Token'}} is not valid (Alphanumeric with '-' or '_').
+
+
+
+
+
+
diff --git a/src/renderer/components/common/form/Multichooser.vue b/src/renderer/components/common/form/Multichooser.vue
new file mode 100644
index 0000000..62a3ca1
--- /dev/null
+++ b/src/renderer/components/common/form/Multichooser.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+ {{ props.item.name }} |
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/common/map/MapPanel.vue b/src/renderer/components/common/map/MapPanel.vue
new file mode 100644
index 0000000..a4797dd
--- /dev/null
+++ b/src/renderer/components/common/map/MapPanel.vue
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
diff --git a/src/renderer/components/common/map/MapUtils.ts b/src/renderer/components/common/map/MapUtils.ts
new file mode 100644
index 0000000..c153927
--- /dev/null
+++ b/src/renderer/components/common/map/MapUtils.ts
@@ -0,0 +1,121 @@
+import L, { Polygon } from "leaflet";
+
+import { ILocation, IZoneCreateRequest } from "sitewhere-rest-api";
+import {
+ Map as LeafletMap,
+ LatLng,
+ LatLngBounds,
+ Control,
+ FeatureGroup
+} from "leaflet";
+
+/** Convert SiteWhere location bounds to leaflet LatLng array */
+export function swToLeafletBounds(bounds: ILocation[]): LatLng[] {
+ let latLngs: LatLng[] = [];
+ bounds.forEach(location => {
+ latLngs.push(new L.LatLng(location.latitude, location.longitude));
+ });
+ return latLngs;
+}
+
+/** Convert leaflet LatLng values to SiteWhere locations */
+export function leafletToSwBounds(
+ bounds: LatLng | LatLng[] | LatLng[][]
+): ILocation[] {
+ var locations: ILocation[] = [];
+ if (bounds && Array.isArray(bounds)) {
+ bounds.forEach((bound: any) => {
+ locations.push({
+ latitude: bound.lat,
+ longitude: bound.lng,
+ elevation: 0
+ });
+ });
+ }
+ return locations;
+}
+
+/** Get drawing options based on UI settings */
+function getDrawOptions(zone: IZoneCreateRequest): Control.DrawOptions {
+ return {
+ polyline: false,
+ circle: false,
+ marker: false,
+ circlemarker: false,
+ polygon: {
+ shapeOptions: {
+ color: zone.borderColor,
+ opacity: zone.borderOpacity,
+ fillColor: zone.fillColor,
+ fillOpacity: zone.fillOpacity
+ }
+ },
+ rectangle: {
+ shapeOptions: {
+ color: zone.borderColor,
+ opacity: zone.borderOpacity,
+ fillColor: zone.fillColor,
+ fillOpacity: zone.fillOpacity
+ }
+ }
+ };
+}
+
+/** Get editing options based on UI settings */
+function getEditOptions(): Control.DrawOptions {
+ return {
+ polyline: false,
+ circle: false,
+ marker: false,
+ circlemarker: false,
+ polygon: false,
+ rectangle: false
+ };
+}
+
+/** Enables drawing features on map */
+export function enableMapDrawing(
+ map: LeafletMap,
+ zone: IZoneCreateRequest
+): Control.Draw {
+ var options: Control.DrawConstructorOptions = {
+ position: "topright"
+ };
+ options.draw = getDrawOptions(zone);
+
+ var drawControl = new Control.Draw(options);
+ map.addControl(drawControl);
+ return drawControl;
+}
+
+/** Enables editing features on map */
+export function enableMapEditing(
+ map: LeafletMap,
+ boundsLayer: Polygon
+): Control.Draw {
+ var editFeatures = new FeatureGroup();
+ editFeatures.addLayer(boundsLayer);
+ map.addLayer(editFeatures);
+ editFeatures.bringToFront();
+
+ var options: Control.DrawConstructorOptions = {
+ position: "topright",
+ edit: {
+ featureGroup: editFeatures,
+ remove: true
+ }
+ };
+ options.draw = getEditOptions();
+
+ var drawControl = new Control.Draw(options);
+ map.addControl(drawControl);
+
+ var bounds: LatLngBounds = boundsLayer.getBounds();
+ if (!boundsLayer.isEmpty()) {
+ map.fitBounds(bounds, {
+ padding: [0, 0]
+ });
+ }
+
+ return drawControl;
+}
diff --git a/src/renderer/components/common/map/MapWithZoneOverlayPanel.vue b/src/renderer/components/common/map/MapWithZoneOverlayPanel.vue
new file mode 100644
index 0000000..10fa04b
--- /dev/null
+++ b/src/renderer/components/common/map/MapWithZoneOverlayPanel.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
diff --git a/src/renderer/components/common/search/FilterChip.vue b/src/renderer/components/common/search/FilterChip.vue
new file mode 100644
index 0000000..5941f65
--- /dev/null
+++ b/src/renderer/components/common/search/FilterChip.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+ {{ label }}
+
+ {{ tooltip }}
+
+
+
+
diff --git a/src/renderer/components/common/search/FilterChipComponent.ts b/src/renderer/components/common/search/FilterChipComponent.ts
new file mode 100644
index 0000000..50d2bf4
--- /dev/null
+++ b/src/renderer/components/common/search/FilterChipComponent.ts
@@ -0,0 +1,59 @@
+import Vue from "vue";
+import { Component, Prop, Watch } from "vue-property-decorator";
+
+import { handleError } from "../Utils";
+import { AxiosPromise, AxiosResponse } from "axios";
+import { IBrandedEntity } from "sitewhere-rest-api";
+
+/**
+ * Base class for filter chip components.
+ */
+@Component
+export class FilterChipComponent extends Vue {
+ @Prop() readonly token!: string;
+ @Prop() readonly tooltip!: string;
+
+ record: T | null = null;
+
+ @Watch("token", { immediate: true })
+ onSelectionUpdated(updated: string) {
+ this.refresh();
+ }
+
+ /** Return method to load record */
+ load(): AxiosPromise | T {
+ throw new Error("Must implement load() method.");
+ }
+
+ /** Type guard to differentiate between responses */
+ isAxiosResponse(
+ response: AxiosResponse | T
+ ): response is AxiosResponse {
+ return (>response).data !== undefined;
+ }
+
+ /** Refresh record content based on token */
+ async refresh() {
+ try {
+ let response: AxiosResponse | T = await this.load();
+ this.record = this.isAxiosResponse(response) ? response.data : response;
+ } catch (err) {
+ handleError(err);
+ }
+ }
+
+ /** Get image information */
+ get image() {
+ return this.record ? this.record.imageUrl : null;
+ }
+
+ /** Get label information */
+ get label() {
+ return this.record ? (this.record as any).name : null;
+ }
+
+ /** Called when close button is clicked */
+ onFilterClosed() {
+ this.$emit("closed", this.token);
+ }
+}
diff --git a/src/renderer/components/common/search/ListFilterBar.vue b/src/renderer/components/common/search/ListFilterBar.vue
new file mode 100644
index 0000000..e75183f
--- /dev/null
+++ b/src/renderer/components/common/search/ListFilterBar.vue
@@ -0,0 +1,36 @@
+
+
+
+ {{ icon }}
+
+
+
+ delete
+ Clear Filter
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/common/search/MultifilterChipComponent.ts b/src/renderer/components/common/search/MultifilterChipComponent.ts
new file mode 100644
index 0000000..9073782
--- /dev/null
+++ b/src/renderer/components/common/search/MultifilterChipComponent.ts
@@ -0,0 +1,76 @@
+import Vue from "vue";
+import { Component, Prop, Watch } from "vue-property-decorator";
+
+import { handleError } from "../Utils";
+import { AxiosPromise, AxiosResponse } from "axios";
+import { IBrandedEntity } from "sitewhere-rest-api";
+
+/**
+ * Base class for multiple filter chip components.
+ */
+@Component
+export class MultifilterChipComponent extends Vue {
+ @Prop() readonly tokens!: string[];
+ @Prop() readonly tooltip!: string;
+
+ record: T | null = null;
+
+ @Watch("tokens", { immediate: true })
+ onSelectionUpdated(updated: string[]) {
+ this.refresh();
+ }
+
+ /** Return method to load record */
+ load(): AxiosPromise | T {
+ throw new Error("Must implement load() method.");
+ }
+
+ /** Type guard to differentiate between responses */
+ isAxiosResponse(
+ response: AxiosResponse | T
+ ): response is AxiosResponse {
+ return (>response).data !== undefined;
+ }
+
+ /** Refresh record content based on token */
+ async refresh() {
+ try {
+ if (this.token) {
+ let response: AxiosResponse | T = await this.load();
+ this.record = this.isAxiosResponse(response) ? response.data : response;
+ } else {
+ this.record = null;
+ }
+ } catch (err) {
+ handleError(err);
+ }
+ }
+
+ /** Token which will be displayed */
+ get token(): string | null {
+ return this.tokens && this.tokens.length > 0 ? this.tokens[0] : null;
+ }
+
+ /** Get image information */
+ get image() {
+ return this.record ? this.record.imageUrl : null;
+ }
+
+ /** Get label information */
+ get label() {
+ if (this.record) {
+ let name = (this.record as any).name;
+ if (this.tokens.length === 1) {
+ return name;
+ } else if (this.tokens.length > 1) {
+ return `${name} and ${this.tokens.length - 1} more`;
+ }
+ }
+ return null;
+ }
+
+ /** Called when close button is clicked */
+ onFilterClosed() {
+ this.$emit("closed", this.tokens);
+ }
+}
diff --git a/src/renderer/components/customers/CustomerAlertEvents.vue b/src/renderer/components/customers/CustomerAlertEvents.vue
index 667550e..6096761 100644
--- a/src/renderer/components/customers/CustomerAlertEvents.vue
+++ b/src/renderer/components/customers/CustomerAlertEvents.vue
@@ -47,7 +47,7 @@ import {
} from "sitewhere-rest-api";
@Component({})
-export default class CustomerMeasurementEvents extends ListComponent<
+export default class CustomerAlertEvents extends ListComponent<
IDeviceAlert,
IDateRangeSearchCriteria,
IDeviceAlertResponseFormat,
@@ -90,6 +90,3 @@ export default class CustomerMeasurementEvents extends ListComponent<
}
}
-
-
diff --git a/src/renderer/components/customers/CustomerAssignments.vue b/src/renderer/components/customers/CustomerAssignments.vue
index 59820f5..adeaf51 100644
--- a/src/renderer/components/customers/CustomerAssignments.vue
+++ b/src/renderer/components/customers/CustomerAssignments.vue
@@ -14,6 +14,11 @@
/>
+
+
+ This customer has no device assignments.
+
+
@@ -21,10 +26,11 @@
import { Component, Prop, ListComponent } from "sitewhere-ide-common";
import AssignmentListEntry from "../assignments/AssignmentListEntry.vue";
+import NoResultsPanel from "../common/NoResultsPanel.vue";
import { routeTo } from "../common/Utils";
import { AxiosPromise } from "axios";
-import { listDeviceAssignments } from "../../rest/sitewhere-device-assignments-api";
+import { searchDeviceAssignments } from "../../rest/sitewhere-device-assignments-api";
import {
IDeviceAssignment,
IDeviceAssignmentSearchCriteria,
@@ -34,10 +40,11 @@ import {
@Component({
components: {
- AssignmentListEntry
+ AssignmentListEntry,
+ NoResultsPanel
}
})
-export default class CustomerTypeCustomers extends ListComponent<
+export default class CustomerAssignments extends ListComponent<
IDeviceAssignment,
IDeviceAssignmentSearchCriteria,
IDeviceAssignmentResponseFormat,
@@ -49,7 +56,7 @@ export default class CustomerTypeCustomers extends ListComponent<
/** Build search criteria for list */
buildSearchCriteria(): IDeviceAssignmentSearchCriteria {
let criteria: IDeviceAssignmentSearchCriteria = {};
- criteria.customerToken = this.customerToken;
+ criteria.customerTokens = [this.customerToken];
return criteria;
}
@@ -68,7 +75,7 @@ export default class CustomerTypeCustomers extends ListComponent<
criteria: IDeviceAssignmentSearchCriteria,
format: IDeviceAssignmentResponseFormat
): AxiosPromise {
- return listDeviceAssignments(this.$store, criteria, format);
+ return searchDeviceAssignments(this.$store, criteria, format);
}
/** Open device assignment detail page */
@@ -77,6 +84,3 @@ export default class CustomerTypeCustomers extends ListComponent<
}
}
-
-
diff --git a/src/renderer/components/customers/CustomerChooser.vue b/src/renderer/components/customers/CustomerChooser.vue
index 58d966e..f84186a 100644
--- a/src/renderer/components/customers/CustomerChooser.vue
+++ b/src/renderer/components/customers/CustomerChooser.vue
@@ -1,110 +1,62 @@
-
-
- {{ chosenText }}
-
-
-
-
-
-
-
-
-
-
-
- remove_circle
-
-
-
-
-
-
- {{ notChosenText }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
diff --git a/src/renderer/components/customers/CustomerCreateDialog.vue b/src/renderer/components/customers/CustomerCreateDialog.vue
index f036e1b..deccf05 100644
--- a/src/renderer/components/customers/CustomerCreateDialog.vue
+++ b/src/renderer/components/customers/CustomerCreateDialog.vue
@@ -11,6 +11,7 @@
-
-
diff --git a/src/renderer/components/customers/CustomerDeleteDialog.vue b/src/renderer/components/customers/CustomerDeleteDialog.vue
index 5bcddce..7bc458d 100644
--- a/src/renderer/components/customers/CustomerDeleteDialog.vue
+++ b/src/renderer/components/customers/CustomerDeleteDialog.vue
@@ -1,50 +1,56 @@
-
-
- Are you sure you want to delete this customer?
-
-
+
+ {{ message }}
+
-
-
+ /** Load payload */
+ prepareDelete(customer: ICustomer): AxiosPromise {
+ return deleteCustomer(this.$store, customer.token);
+ }
+
+ // Called after create button is clicked.
+ onDelete(e: any) {
+ this.delete();
+ }
+
+ // Called after cancel button is clicked.
+ onCancel(e: any) {
+ this.cancel();
+ }
+}
+
diff --git a/src/renderer/components/customers/CustomerDetail.vue b/src/renderer/components/customers/CustomerDetail.vue
index cf52e89..3bb7f1b 100644
--- a/src/renderer/components/customers/CustomerDetail.vue
+++ b/src/renderer/components/customers/CustomerDetail.vue
@@ -7,7 +7,7 @@
:record="customer"
>
-
+
Subcustomers
@@ -17,46 +17,37 @@
Alerts
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
- Up One Level
-
-
-
-
+
+
+
+
-
-
diff --git a/src/renderer/components/customers/CustomerDetailFields.vue b/src/renderer/components/customers/CustomerDetailFields.vue
index b04ee6d..38a2b44 100644
--- a/src/renderer/components/customers/CustomerDetailFields.vue
+++ b/src/renderer/components/customers/CustomerDetailFields.vue
@@ -1,16 +1,13 @@
-
- Customer token is required.
- Customer token is not valid.
-
+ :validator="$v"
+ />
-
-
diff --git a/src/renderer/components/customers/CustomerDetailHeader.vue b/src/renderer/components/customers/CustomerDetailHeader.vue
index 7b77f8f..ee60ecd 100644
--- a/src/renderer/components/customers/CustomerDetailHeader.vue
+++ b/src/renderer/components/customers/CustomerDetailHeader.vue
@@ -1,29 +1,34 @@
-
+
+
+
+
-
-
-
-
-
- {{ customer.name }}
-
-
- {{ customer.description }}
-
-
- {{ formatDate(customer.createdDate) }}
-
-
- {{ formatDate(customer.updatedDate) }}
-
+
+
+
+
+
+
+ {{ customer.name }}
+
+
+ {{ customer.description }}
+
+
+ {{ formatDate(customer.createdDate) }}
+
+
+ {{ formatDate(customer.updatedDate) }}
+
+
-
+
@@ -31,10 +36,13 @@
-
-
diff --git a/src/renderer/components/customers/CustomerListEntry.vue b/src/renderer/components/customers/CustomerListEntry.vue
index 9c6e965..05bc081 100644
--- a/src/renderer/components/customers/CustomerListEntry.vue
+++ b/src/renderer/components/customers/CustomerListEntry.vue
@@ -1,15 +1,13 @@
-
+
-
-
-
+
- {{ customer.name }}
+ {{ customer.name }}
{{ customer.description }}
@@ -22,29 +20,31 @@
import Vue from "vue";
import { Component, Prop } from "sitewhere-ide-common";
+import BrandingImage from "../common/BrandingImage.vue";
+
import { ICustomer } from "sitewhere-rest-api";
+import { IStyle } from "../common/Style";
-@Component({})
-export default class DeviceTypeSelector extends Vue {
+@Component({
+ components: {
+ BrandingImage
+ }
+})
+export default class CustomerListEntry extends Vue {
@Prop() readonly customer!: ICustomer;
// Compute style of logo.
- get logoStyle() {
+ get logoStyle(): IStyle {
return {
- "background-color": "#fff",
- "background-image": "url(" + this.customer.imageUrl + ")",
- "background-size": "contain",
- "background-repeat": "no-repeat",
- "background-position": "50% 50%",
- border: "1px solid #eee",
height: "120px",
- width: "100px"
+ width: "120px",
+ "padding-left": "15px"
};
}
// Handle customer clicked.
onCustomerClicked() {
- this.$emit("openCustomer", this.customer);
+ this.$emit("open", this.customer);
}
}
diff --git a/src/renderer/components/customers/CustomerMultifilterChip.vue b/src/renderer/components/customers/CustomerMultifilterChip.vue
new file mode 100644
index 0000000..374325e
--- /dev/null
+++ b/src/renderer/components/customers/CustomerMultifilterChip.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
diff --git a/src/renderer/components/customers/CustomerMultiselect.vue b/src/renderer/components/customers/CustomerMultiselect.vue
new file mode 100644
index 0000000..abffe4a
--- /dev/null
+++ b/src/renderer/components/customers/CustomerMultiselect.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
diff --git a/src/renderer/components/customers/CustomerSelector.vue b/src/renderer/components/customers/CustomerSelector.vue
index 45603a6..3476f42 100644
--- a/src/renderer/components/customers/CustomerSelector.vue
+++ b/src/renderer/components/customers/CustomerSelector.vue
@@ -1,48 +1,82 @@
-
+ v-model="wrapped"
+ icon="settings"
+ />
+
+
+
-
-
-
diff --git a/src/renderer/components/customers/CustomerSubcustomers.vue b/src/renderer/components/customers/CustomerSubcustomers.vue
index 483ed54..6cb2acd 100644
--- a/src/renderer/components/customers/CustomerSubcustomers.vue
+++ b/src/renderer/components/customers/CustomerSubcustomers.vue
@@ -7,11 +7,20 @@
>
-
+
+
+
+ This customer has no subcustomers.
+
+ Click
+ {{custIcon}}in the toolbar to add a subcustomer.
+
+
+
-
+
@@ -21,7 +30,9 @@ import { Component, Prop, ListComponent } from "sitewhere-ide-common";
import CustomerListEntry from "./CustomerListEntry.vue";
import CustomerCreateDialog from "./CustomerCreateDialog.vue";
+import NoResultsPanel from "../common/NoResultsPanel.vue";
+import { NavigationIcon } from "../../libraries/constants";
import { routeTo } from "../common/Utils";
import { AxiosPromise } from "axios";
import { listCustomers } from "../../rest/sitewhere-customers-api";
@@ -35,7 +46,8 @@ import {
@Component({
components: {
CustomerListEntry,
- CustomerCreateDialog
+ CustomerCreateDialog,
+ NoResultsPanel
}
})
export default class CustomerSubcustomers extends ListComponent<
@@ -47,6 +59,8 @@ export default class CustomerSubcustomers extends ListComponent<
@Prop() readonly tabkey!: string;
@Prop() readonly customer!: ICustomer;
+ custIcon: string = NavigationIcon.Customer;
+
/** Build search criteria for list */
buildSearchCriteria(): ICustomerSearchCriteria {
let criteria: ICustomerSearchCriteria = {};
@@ -77,6 +91,3 @@ export default class CustomerSubcustomers extends ListComponent<
}
}
-
-
diff --git a/src/renderer/components/customers/CustomersList.vue b/src/renderer/components/customers/CustomersList.vue
index 969237a..268cd0f 100644
--- a/src/renderer/components/customers/CustomersList.vue
+++ b/src/renderer/components/customers/CustomersList.vue
@@ -9,14 +9,23 @@
>
-
+
+
+
+ No customers have been created for this tenant.
+
+ Click
+ {{addIcon}}in the toolbar to add a customer.
+
+
+
-
+
-
+
@@ -27,6 +36,7 @@ import { Component, ListComponent, Refs } from "sitewhere-ide-common";
import CustomerListEntry from "./CustomerListEntry.vue";
import CustomerCreateDialog from "./CustomerCreateDialog.vue";
import AddButton from "../common/navbuttons/AddButton.vue";
+import NoResultsPanel from "../common/NoResultsPanel.vue";
import { NavigationIcon } from "../../libraries/constants";
import { routeTo } from "../common/Utils";
@@ -43,7 +53,8 @@ import {
components: {
CustomerListEntry,
CustomerCreateDialog,
- AddButton
+ AddButton,
+ NoResultsPanel
}
})
export default class CustomersList extends ListComponent<
@@ -56,6 +67,8 @@ export default class CustomersList extends ListComponent<
add: CustomerCreateDialog;
}>;
+ addIcon: string = NavigationIcon.Add;
+
/** Get page icon */
get icon(): NavigationIcon {
return NavigationIcon.Customer;
diff --git a/src/renderer/components/customertypes/CustomerTypeContentFields.vue b/src/renderer/components/customertypes/CustomerTypeContentFields.vue
new file mode 100644
index 0000000..1f400a8
--- /dev/null
+++ b/src/renderer/components/customertypes/CustomerTypeContentFields.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/customertypes/CustomerTypeCreateDialog.vue b/src/renderer/components/customertypes/CustomerTypeCreateDialog.vue
index f62e85a..84960f6 100644
--- a/src/renderer/components/customertypes/CustomerTypeCreateDialog.vue
+++ b/src/renderer/components/customertypes/CustomerTypeCreateDialog.vue
@@ -58,6 +58,3 @@ export default class CustomerTypeCreateDialog extends CreateDialogComponent<
}
}
-
-
diff --git a/src/renderer/components/customertypes/CustomerTypeCustomers.vue b/src/renderer/components/customertypes/CustomerTypeCustomers.vue
index 0a3f315..7a39821 100644
--- a/src/renderer/components/customertypes/CustomerTypeCustomers.vue
+++ b/src/renderer/components/customertypes/CustomerTypeCustomers.vue
@@ -9,9 +9,14 @@
>
-
+
+
+
+ There are no customers of this type.
+
+
@@ -19,8 +24,10 @@
import { Component, Prop, ListComponent } from "sitewhere-ide-common";
import CustomerListEntry from "../customers/CustomerListEntry.vue";
+import NoResultsPanel from "../common/NoResultsPanel.vue";
import { AxiosPromise } from "axios";
+import { routeTo } from "../common/Utils";
import { listCustomers } from "../../rest/sitewhere-customers-api";
import {
ICustomer,
@@ -31,7 +38,8 @@ import {
@Component({
components: {
- CustomerListEntry
+ CustomerListEntry,
+ NoResultsPanel
}
})
export default class CustomerTypeCustomers extends ListComponent<
@@ -64,8 +72,10 @@ export default class CustomerTypeCustomers extends ListComponent<
): AxiosPromise {
return listCustomers(this.$store, criteria, format);
}
+
+ /** Called to open a customer */
+ onOpenCustomer(customer: ICustomer) {
+ routeTo(this, "/customers/" + customer.token);
+ }
}
-
-
diff --git a/src/renderer/components/customertypes/CustomerTypeDeleteDialog.vue b/src/renderer/components/customertypes/CustomerTypeDeleteDialog.vue
index 6cc72ec..13bb393 100644
--- a/src/renderer/components/customertypes/CustomerTypeDeleteDialog.vue
+++ b/src/renderer/components/customertypes/CustomerTypeDeleteDialog.vue
@@ -1,56 +1,56 @@
-
-
- Are you sure you want to delete this customer type?
-
-
+
+ {{ message }}
+
-
-
+ // Called after create button is clicked.
+ onDelete(e: any) {
+ this.delete();
+ }
+
+ // Called after cancel button is clicked.
+ onCancel(e: any) {
+ this.cancel();
+ }
+}
+
diff --git a/src/renderer/components/customertypes/CustomerTypeDetail.vue b/src/renderer/components/customertypes/CustomerTypeDetail.vue
index 90528d9..b52caa3 100644
--- a/src/renderer/components/customertypes/CustomerTypeDetail.vue
+++ b/src/renderer/components/customertypes/CustomerTypeDetail.vue
@@ -17,7 +17,7 @@
Customers of Type
-
+
-
-
+
+
@@ -47,15 +47,14 @@ import {
Refs
} from "sitewhere-ide-common";
-import NoResultsPanel from "../common/NoResultsPanel.vue";
import CustomerTypeDetailHeader from "./CustomerTypeDetailHeader.vue";
import CustomerTypeCustomers from "./CustomerTypeCustomers.vue";
import CustomerTypeDeleteDialog from "./CustomerTypeDeleteDialog.vue";
import CustomerTypeUpdateDialog from "./CustomerTypeUpdateDialog.vue";
-import CustomerListEntry from "../customers/CustomerListEntry.vue";
import EditButton from "../common/navbuttons/EditButton.vue";
import DeleteButton from "../common/navbuttons/DeleteButton.vue";
+import { Route } from "vue-router";
import { routeTo } from "../common/Utils";
import { AxiosPromise } from "axios";
import { NavigationIcon } from "../../libraries/constants";
@@ -64,10 +63,8 @@ import { ICustomerType, ICustomerTypeResponseFormat } from "sitewhere-rest-api";
@Component({
components: {
- NoResultsPanel,
CustomerTypeDetailHeader,
CustomerTypeCustomers,
- CustomerListEntry,
CustomerTypeDeleteDialog,
CustomerTypeUpdateDialog,
EditButton,
@@ -94,6 +91,12 @@ export default class CustomerTypeDetail extends DetailComponent {
return this.customerType ? this.customerType.name : "";
}
+ /** Called when component is reused */
+ beforeRouteUpdate(to: Route, from: Route, next: any) {
+ this.display(to.params.token);
+ next();
+ }
+
/** Load record */
loadRecord(token: string): AxiosPromise {
let format: ICustomerTypeResponseFormat = {};
@@ -134,6 +137,3 @@ export default class CustomerTypeDetail extends DetailComponent {
}
}
-
-
diff --git a/src/renderer/components/customertypes/CustomerTypeDetailFields.vue b/src/renderer/components/customertypes/CustomerTypeDetailFields.vue
index 931dde1..683a608 100644
--- a/src/renderer/components/customertypes/CustomerTypeDetailFields.vue
+++ b/src/renderer/components/customertypes/CustomerTypeDetailFields.vue
@@ -1,16 +1,13 @@
-
- Token is required.
- Token is not valid.
-
+ :validator="$v"
+ />
-
-
diff --git a/src/renderer/components/customertypes/CustomerTypeDetailHeader.vue b/src/renderer/components/customertypes/CustomerTypeDetailHeader.vue
index 262126b..6bc2000 100644
--- a/src/renderer/components/customertypes/CustomerTypeDetailHeader.vue
+++ b/src/renderer/components/customertypes/CustomerTypeDetailHeader.vue
@@ -1,24 +1,29 @@
+
+
+
-
-
-
-
- {{ customerType.name }}
-
-
- {{ customerType.description }}
-
-
- {{ formatDate(customerType.createdDate) }}
-
-
- {{ formatDate(customerType.updatedDate) }}
-
+
+
+
+
+
+ {{ customerType.name }}
+
+
+ {{ customerType.description }}
+
+
+ {{ formatDate(customerType.createdDate) }}
+
+
+ {{ formatDate(customerType.updatedDate) }}
+
+
-
+
@@ -26,10 +31,13 @@
-
-
diff --git a/src/renderer/components/customertypes/CustomerTypeDialog.vue b/src/renderer/components/customertypes/CustomerTypeDialog.vue
index e73ef4a..a5c8b3a 100644
--- a/src/renderer/components/customertypes/CustomerTypeDialog.vue
+++ b/src/renderer/components/customertypes/CustomerTypeDialog.vue
@@ -19,16 +19,16 @@
-
+
-
+
-
+
-
+
@@ -45,14 +45,14 @@ import {
import { NavigationIcon } from "../../libraries/constants";
import CustomerTypeDetailFields from "./CustomerTypeDetailFields.vue";
-import CustomerTypesMultiselect from "./CustomerTypesMultiselect.vue";
+import CustomerTypeContentFields from "./CustomerTypeContentFields.vue";
import BrandingPanel from "../common/BrandingPanel.vue";
import { ICustomerType } from "sitewhere-rest-api";
@Component({
components: {
CustomerTypeDetailFields,
- CustomerTypesMultiselect,
+ CustomerTypeContentFields,
BrandingPanel
}
})
@@ -60,8 +60,9 @@ export default class CustomerTypeDialog extends DialogComponent {
// References.
$refs!: Refs<{
dialog: ITabbedComponent;
- details: DialogSection;
- branding: DialogSection;
+ details: CustomerTypeDetailFields;
+ content: CustomerTypeContentFields;
+ branding: BrandingPanel;
metadata: DialogSection;
}>;
@@ -76,6 +77,7 @@ export default class CustomerTypeDialog extends DialogComponent {
Object.assign(
payload,
this.$refs.details.save(),
+ this.$refs.content.save(),
this.$refs.branding.save(),
this.$refs.metadata.save()
);
@@ -87,13 +89,16 @@ export default class CustomerTypeDialog extends DialogComponent {
if (this.$refs.details) {
this.$refs.details.reset();
}
+ if (this.$refs.content) {
+ this.$refs.content.reset();
+ }
if (this.$refs.branding) {
this.$refs.branding.reset();
}
if (this.$refs.metadata) {
this.$refs.metadata.reset();
}
- this.$refs.dialog.setActiveTab("details");
+ this.$refs.dialog.setActiveTab(0);
}
// Load dialog from a given payload.
@@ -102,6 +107,9 @@ export default class CustomerTypeDialog extends DialogComponent {
if (this.$refs.details) {
this.$refs.details.load(payload);
}
+ if (this.$refs.content) {
+ this.$refs.content.load(payload);
+ }
if (this.$refs.branding) {
this.$refs.branding.load(payload);
}
@@ -113,21 +121,22 @@ export default class CustomerTypeDialog extends DialogComponent {
// Called after create button is clicked.
onCreateClicked(e: any) {
if (!this.$refs.details.validate()) {
- this.$refs.dialog.setActiveTab("details");
+ this.$refs.dialog.setActiveTab(0);
+ return;
+ }
+
+ if (!this.$refs.content.validate()) {
+ this.$refs.dialog.setActiveTab(0);
return;
}
if (!this.$refs.branding.validate()) {
- this.$refs.dialog.setActiveTab("branding");
+ this.$refs.dialog.setActiveTab(1);
return;
}
var payload = this.generatePayload();
- console.log("Before payload emit:", this);
this.$emit("payload", payload);
}
}
-
-
diff --git a/src/renderer/components/customertypes/CustomerTypeListEntry.vue b/src/renderer/components/customertypes/CustomerTypeListEntry.vue
index 8741efe..6e48198 100644
--- a/src/renderer/components/customertypes/CustomerTypeListEntry.vue
+++ b/src/renderer/components/customertypes/CustomerTypeListEntry.vue
@@ -3,13 +3,11 @@
-
-
-
+
- {{ customerType.name }}
+ {{ customerType.name }}
{{ customerType.description }}
@@ -22,15 +20,30 @@
import Vue from "vue";
import { Component, Prop } from "sitewhere-ide-common";
+import BrandingImage from "../common/BrandingImage.vue";
+
+import { IStyle } from "../common/Style";
import { ICustomerType } from "sitewhere-rest-api";
-@Component({})
-export default class DeviceTypeSelector extends Vue {
+@Component({
+ components: {
+ BrandingImage
+ }
+})
+export default class CustomerTypeListEntry extends Vue {
@Prop() readonly customerType!: ICustomerType;
+ // Compute style of logo.
+ get logoStyle(): IStyle {
+ return {
+ height: "80px",
+ width: "80px"
+ };
+ }
+
// Handle customer type clicked.
onCustomerTypeClicked() {
- this.$emit("openCustomerType", this.customerType);
+ this.$emit("open", this.customerType);
}
}
diff --git a/src/renderer/components/customertypes/CustomerTypeMultiselect.vue b/src/renderer/components/customertypes/CustomerTypeMultiselect.vue
new file mode 100644
index 0000000..2a00d25
--- /dev/null
+++ b/src/renderer/components/customertypes/CustomerTypeMultiselect.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
diff --git a/src/renderer/components/customertypes/CustomerTypeSelector.vue b/src/renderer/components/customertypes/CustomerTypeSelector.vue
index 45e8170..cc4b02d 100644
--- a/src/renderer/components/customertypes/CustomerTypeSelector.vue
+++ b/src/renderer/components/customertypes/CustomerTypeSelector.vue
@@ -65,6 +65,3 @@ export default class CustomerTypeSelector extends Vue {
}
}
-
-
diff --git a/src/renderer/components/customertypes/CustomerTypeUpdateDialog.vue b/src/renderer/components/customertypes/CustomerTypeUpdateDialog.vue
index 684a13d..1cb47c9 100644
--- a/src/renderer/components/customertypes/CustomerTypeUpdateDialog.vue
+++ b/src/renderer/components/customertypes/CustomerTypeUpdateDialog.vue
@@ -74,6 +74,3 @@ export default class CustomerTypeUpdateDialog extends EditDialogComponent<
}
}
-
-
diff --git a/src/renderer/components/customertypes/CustomerTypesList.vue b/src/renderer/components/customertypes/CustomerTypesList.vue
index 47228d3..7cc51e9 100644
--- a/src/renderer/components/customertypes/CustomerTypesList.vue
+++ b/src/renderer/components/customertypes/CustomerTypesList.vue
@@ -9,18 +9,23 @@
>
-
+
+
+
+ No customer types have been created for this tenant.
+
+ Click
+ {{addIcon}}in the toolbar to add a customer type.
+
+
+
-
+
-
+
@@ -31,6 +36,7 @@ import { Component, ListComponent, Refs } from "sitewhere-ide-common";
import CustomerTypeListEntry from "./CustomerTypeListEntry.vue";
import CustomerTypeCreateDialog from "./CustomerTypeCreateDialog.vue";
import AddButton from "../common/navbuttons/AddButton.vue";
+import NoResultsPanel from "../common/NoResultsPanel.vue";
import { routeTo } from "../common/Utils";
import { NavigationIcon } from "../../libraries/constants";
@@ -47,7 +53,8 @@ import {
components: {
CustomerTypeListEntry,
CustomerTypeCreateDialog,
- AddButton
+ AddButton,
+ NoResultsPanel
}
})
export default class CustomerTypesList extends ListComponent<
@@ -60,6 +67,8 @@ export default class CustomerTypesList extends ListComponent<
add: CustomerTypeCreateDialog;
}>;
+ addIcon: string = NavigationIcon.Add;
+
/** Get page icon */
get icon(): NavigationIcon {
return NavigationIcon.CustomerType;
@@ -97,6 +106,3 @@ export default class CustomerTypesList extends ListComponent<
}
}
-
-
diff --git a/src/renderer/components/customertypes/CustomerTypesMultiselect.vue b/src/renderer/components/customertypes/CustomerTypesMultiselect.vue
deleted file mode 100644
index 6083da0..0000000
--- a/src/renderer/components/customertypes/CustomerTypesMultiselect.vue
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- {{ customerType.name }}
-
-
-
-
-
-
- No customer types available.
-
-
-
-
-
-
diff --git a/src/renderer/components/devicegroups/DeviceGroupChooser.vue b/src/renderer/components/devicegroups/DeviceGroupChooser.vue
index 030a336..7d0872d 100644
--- a/src/renderer/components/devicegroups/DeviceGroupChooser.vue
+++ b/src/renderer/components/devicegroups/DeviceGroupChooser.vue
@@ -1,101 +1,60 @@
-
-
- {{ chosenText }}
-
-
-
-
-
-
-
-
-
-
-
- remove_circle
-
-
-
-
-
-
- {{ notChosenText }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
diff --git a/src/renderer/components/devicegroups/DeviceGroupCreateDialog.vue b/src/renderer/components/devicegroups/DeviceGroupCreateDialog.vue
index 1bb63fe..b263737 100644
--- a/src/renderer/components/devicegroups/DeviceGroupCreateDialog.vue
+++ b/src/renderer/components/devicegroups/DeviceGroupCreateDialog.vue
@@ -28,7 +28,7 @@ import { createDeviceGroup } from "../../rest/sitewhere-device-groups-api";
DeviceGroupDialog
}
})
-export default class DeviceTypeCreateDialog extends CreateDialogComponent<
+export default class DeviceGroupCreateDialog extends CreateDialogComponent<
IDeviceGroup,
IDeviceGroupCreateRequest
> {
diff --git a/src/renderer/components/devicegroups/DeviceGroupDetail.vue b/src/renderer/components/devicegroups/DeviceGroupDetail.vue
index 35cd877..8420498 100644
--- a/src/renderer/components/devicegroups/DeviceGroupDetail.vue
+++ b/src/renderer/components/devicegroups/DeviceGroupDetail.vue
@@ -17,20 +17,17 @@
Group Elements
-
+
-
-
+
+
+
-
-
-
+
+
+
@@ -48,6 +45,7 @@ import DeviceGroupUpdateDialog from "./DeviceGroupUpdateDialog.vue";
import DeviceGroupDeleteDialog from "./DeviceGroupDeleteDialog.vue";
import DeviceGroupElements from "./DeviceGroupElements.vue";
import DeviceGroupElementCreateDialog from "./DeviceGroupElementCreateDialog.vue";
+import AddButton from "../common/navbuttons/AddButton.vue";
import EditButton from "../common/navbuttons/EditButton.vue";
import DeleteButton from "../common/navbuttons/DeleteButton.vue";
@@ -64,6 +62,7 @@ import { IDeviceGroup, IDeviceGroupResponseFormat } from "sitewhere-rest-api";
DeviceGroupDeleteDialog,
DeviceGroupElements,
DeviceGroupElementCreateDialog,
+ AddButton,
EditButton,
DeleteButton
}
@@ -73,6 +72,8 @@ export default class DeviceGroupDetail extends DetailComponent {
// References.
$refs!: Refs<{
+ list: DeviceGroupElements;
+ create: DeviceGroupElementCreateDialog;
edit: DeviceGroupUpdateDialog;
delete: DeviceGroupDeleteDialog;
}>;
@@ -130,17 +131,14 @@ export default class DeviceGroupDetail extends DetailComponent {
routeTo(this, "/groups");
}
- // Called when 'add element' button is clicked.
- onAddElement() {
- (this.$refs["create"] as any).onOpenDialog();
+ /** Called when 'add element' button is clicked */
+ onAddGroupElement() {
+ this.$refs.create.open();
}
- // Called when an element is added.
+ /** Called when an element is added */
onElementAdded() {
- (this.$refs["list"] as any).refresh();
+ this.$refs.list.refresh();
}
}
-
-
diff --git a/src/renderer/components/devicegroups/DeviceGroupDetailFields.vue b/src/renderer/components/devicegroups/DeviceGroupDetailFields.vue
index 2dd8137..ad6a927 100644
--- a/src/renderer/components/devicegroups/DeviceGroupDetailFields.vue
+++ b/src/renderer/components/devicegroups/DeviceGroupDetailFields.vue
@@ -1,16 +1,13 @@
-
- Device Type token is required.
- Device Type token is not valid.
-
+ :validator="$v"
+ />
-
+
@@ -44,6 +41,7 @@
import { Component, DialogSection } from "sitewhere-ide-common";
import DialogForm from "../common/form/DialogForm.vue";
+import FormToken from "../common/form/FormToken.vue";
import FormText from "../common/form/FormText.vue";
import FormTextArea from "../common/form/FormTextArea.vue";
import RolesField from "./RolesField.vue";
@@ -56,6 +54,7 @@ const validToken = helpers.regex("validToken", /^[a-zA-Z0-9-_]+$/);
@Component({
components: {
DialogForm,
+ FormToken,
FormText,
FormTextArea,
RolesField
diff --git a/src/renderer/components/devicegroups/DeviceGroupDetailHeader.vue b/src/renderer/components/devicegroups/DeviceGroupDetailHeader.vue
index 761bed9..87cffb7 100644
--- a/src/renderer/components/devicegroups/DeviceGroupDetailHeader.vue
+++ b/src/renderer/components/devicegroups/DeviceGroupDetailHeader.vue
@@ -1,24 +1,29 @@
-
+
+
+
+
-
-
-
-
- {{ deviceGroup.name }}
-
-
- {{ deviceGroup.description }}
-
-
- {{ deviceGroup.imageUrl }}
-
-
- {{ rolesList }}
-
+
+
+
+
+
+ {{ deviceGroup.name }}
+
+
+ {{ deviceGroup.description }}
+
+
+ {{ deviceGroup.imageUrl }}
+
+
+ {{ rolesList }}
+
+
-
+
@@ -26,14 +31,19 @@
-
-
diff --git a/src/renderer/components/devicegroups/DeviceGroupDialog.vue b/src/renderer/components/devicegroups/DeviceGroupDialog.vue
index a1e89ff..283ab0e 100644
--- a/src/renderer/components/devicegroups/DeviceGroupDialog.vue
+++ b/src/renderer/components/devicegroups/DeviceGroupDialog.vue
@@ -18,13 +18,13 @@
-
+
-
+
-
+
@@ -87,7 +87,7 @@ export default class DeviceGroupDialog extends DialogComponent {
if (this.$refs.metadata) {
this.$refs.metadata.reset();
}
- this.$refs.dialog.setActiveTab("details");
+ this.$refs.dialog.setActiveTab(0);
}
// Load dialog from a given payload.
@@ -107,12 +107,12 @@ export default class DeviceGroupDialog extends DialogComponent {
// Called after create button is clicked.
onCreateClicked(e: any) {
if (!this.$refs.details.validate()) {
- this.$refs.dialog.setActiveTab("details");
+ this.$refs.dialog.setActiveTab(0);
return;
}
if (!this.$refs.branding.validate()) {
- this.$refs.dialog.setActiveTab("branding");
+ this.$refs.dialog.setActiveTab(1);
return;
}
@@ -122,6 +122,3 @@ export default class DeviceGroupDialog extends DialogComponent {
}
}
-
-
diff --git a/src/renderer/components/devicegroups/DeviceGroupElementCreateDialog.vue b/src/renderer/components/devicegroups/DeviceGroupElementCreateDialog.vue
index b8654f8..b3d0aa5 100644
--- a/src/renderer/components/devicegroups/DeviceGroupElementCreateDialog.vue
+++ b/src/renderer/components/devicegroups/DeviceGroupElementCreateDialog.vue
@@ -1,60 +1,68 @@
-
-
-
+
-
+})
+export default class DeviceSlotCreateDialog extends CreateDialogComponent<
+ IDeviceGroupElement,
+ IDeviceGroupElementCreateRequest
+> {
+ @Prop() token!: string;
+
+ // References.
+ $refs!: Refs<{
+ dialog: DialogComponent;
+ }>;
-
+ /** Get wrapped dialog */
+ getDialog(): DialogComponent {
+ return this.$refs.dialog;
+ }
+
+ /** Called on payload commit */
+ onCommit(payload: IDeviceGroupElementCreateRequest): void {
+ this.commit(payload);
+ }
+
+ /** Implemented in subclasses to save payload */
+ save(
+ payload: IDeviceGroupElementCreateRequest
+ ): AxiosPromise {
+ let elements: IDeviceGroupElementCreateRequest[] = [];
+ elements.push(payload);
+ return createDeviceGroupElements(this.$store, this.token, elements);
+ }
+
+ /** Implemented in subclasses for after-save */
+ afterSave(payload: IDeviceGroupElement): void {}
+}
+
diff --git a/src/renderer/components/devicegroups/DeviceGroupElementDeleteDialog.vue b/src/renderer/components/devicegroups/DeviceGroupElementDeleteDialog.vue
index 5e39b50..693bf83 100644
--- a/src/renderer/components/devicegroups/DeviceGroupElementDeleteDialog.vue
+++ b/src/renderer/components/devicegroups/DeviceGroupElementDeleteDialog.vue
@@ -1,53 +1,76 @@
-
-
- Are you sure you want to delete this group element?
-
-
+
+ {{ message }}
+
-
-
+ /** Load payload */
+ prepareDelete(
+ item: IDeviceGroupElementSearchResults
+ ): AxiosPromise {
+ deleteDeviceGroupElement(this.$store, this.token, this.deleteId);
+ let criteria: IDeviceGroupElementSearchCriteria = {
+ deviceGroupToken: this.token
+ };
+ let format: IDeviceGroupElementResponseFormat = {};
+ return listDeviceGroupElements(this.$store, this.token, criteria, format);
+ }
+
+ // Called after create button is clicked.
+ onDelete(e: any) {
+ this.delete();
+ }
+
+ // Called after cancel button is clicked.
+ onCancel(e: any) {
+ this.cancel();
+ }
+}
+
diff --git a/src/renderer/components/devicegroups/DeviceGroupElementDetailFields.vue b/src/renderer/components/devicegroups/DeviceGroupElementDetailFields.vue
new file mode 100644
index 0000000..4f20947
--- /dev/null
+++ b/src/renderer/components/devicegroups/DeviceGroupElementDetailFields.vue
@@ -0,0 +1,102 @@
+
+
+
+
+ Element type is required.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/devicegroups/DeviceGroupElementDialog.vue b/src/renderer/components/devicegroups/DeviceGroupElementDialog.vue
index 210dbaa..d061bf2 100644
--- a/src/renderer/components/devicegroups/DeviceGroupElementDialog.vue
+++ b/src/renderer/components/devicegroups/DeviceGroupElementDialog.vue
@@ -1,165 +1,108 @@
-
-
-
- Element details
- Roles
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ Details
+ Roles
+
+
+
+
+
+
+
+
+
+
-
-
-
diff --git a/src/renderer/components/devicegroups/DeviceGroupElementRoleFields.vue b/src/renderer/components/devicegroups/DeviceGroupElementRoleFields.vue
new file mode 100644
index 0000000..06ac5a0
--- /dev/null
+++ b/src/renderer/components/devicegroups/DeviceGroupElementRoleFields.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/devicegroups/DeviceGroupElements.vue b/src/renderer/components/devicegroups/DeviceGroupElements.vue
index c33415f..4b2bb7b 100644
--- a/src/renderer/components/devicegroups/DeviceGroupElements.vue
+++ b/src/renderer/components/devicegroups/DeviceGroupElements.vue
@@ -38,9 +38,9 @@
+ :token="deviceGroup.token"
+ @deleted="refresh"
+ />
@@ -49,6 +49,7 @@
import {
Component,
Prop,
+ Refs,
ListComponent,
IPageSizes,
ITableHeaders
@@ -82,6 +83,12 @@ export default class DeviceGroupElements extends ListComponent<
@Prop() readonly id!: string;
@Prop() readonly deviceGroup!: IDeviceGroup;
+ // References.
+ $refs!: Refs<{
+ list: DeviceGroupElements;
+ delete: DeviceGroupElementDeleteDialog;
+ }>;
+
deviceIcon: NavigationIcon = NavigationIcon.Device;
groupIcon: NavigationIcon = NavigationIcon.DeviceGroup;
@@ -160,8 +167,7 @@ export default class DeviceGroupElements extends ListComponent<
/** Show dialog for deleting element */
showDeleteDialog(element: IDeviceGroupElement) {
- (this.$refs["delete"] as any).elementId = element.id;
- (this.$refs["delete"] as any).showDeleteDialog();
+ this.$refs.delete.open(element.id);
}
}
diff --git a/src/renderer/components/devicegroups/DeviceGroupsList.vue b/src/renderer/components/devicegroups/DeviceGroupsList.vue
index 0e4fa05..17c2dec 100644
--- a/src/renderer/components/devicegroups/DeviceGroupsList.vue
+++ b/src/renderer/components/devicegroups/DeviceGroupsList.vue
@@ -16,11 +16,20 @@
/>
+
+
+ No device groups have been created for this tenant.
+
+ Click
+ {{addIcon}}in the toolbar to add a device group.
+
+
+
-
+
-
+
@@ -31,6 +40,7 @@ import { Component, ListComponent, Refs } from "sitewhere-ide-common";
import DeviceGroupListEntry from "./DeviceGroupListEntry.vue";
import DeviceGroupCreateDialog from "./DeviceGroupCreateDialog.vue";
import AddButton from "../common/navbuttons/AddButton.vue";
+import NoResultsPanel from "../common/NoResultsPanel.vue";
import { NavigationIcon } from "../../libraries/constants";
import { routeTo } from "../common/Utils";
@@ -47,7 +57,8 @@ import {
components: {
DeviceGroupListEntry,
DeviceGroupCreateDialog,
- AddButton
+ AddButton,
+ NoResultsPanel
}
})
export default class DeviceGroupsList extends ListComponent<
@@ -60,6 +71,8 @@ export default class DeviceGroupsList extends ListComponent<
add: DeviceGroupCreateDialog;
}>;
+ addIcon: string = NavigationIcon.Add;
+
/** Icon for page */
get icon(): NavigationIcon {
return NavigationIcon.DeviceGroup;
diff --git a/src/renderer/components/devicegroups/RolesField.vue b/src/renderer/components/devicegroups/RolesField.vue
index 30ccb77..2898e10 100644
--- a/src/renderer/components/devicegroups/RolesField.vue
+++ b/src/renderer/components/devicegroups/RolesField.vue
@@ -38,7 +38,7 @@ import Vue from "vue";
import { Component, Prop, Watch } from "sitewhere-ide-common";
@Component
-export default class Pager extends Vue {
+export default class RolesField extends Vue {
@Prop() readonly value!: string[];
@Prop() readonly icon!: string;
diff --git a/src/renderer/components/devices/DeviceAssignmentHistory.vue b/src/renderer/components/devices/DeviceAssignmentHistory.vue
index 98d6dbd..3c3bb45 100644
--- a/src/renderer/components/devices/DeviceAssignmentHistory.vue
+++ b/src/renderer/components/devices/DeviceAssignmentHistory.vue
@@ -54,7 +54,7 @@ export default class DeviceAssignmentHistory extends ListComponent<
/** Build search criteria for list */
buildSearchCriteria(): IDeviceAssignmentSearchCriteria {
let criteria: IDeviceAssignmentSearchCriteria = {};
- criteria.deviceToken = this.deviceToken;
+ criteria.deviceTokens = [this.deviceToken];
return criteria;
}
diff --git a/src/renderer/components/devices/DeviceChooser.vue b/src/renderer/components/devices/DeviceChooser.vue
index 06bd2a2..61eba87 100644
--- a/src/renderer/components/devices/DeviceChooser.vue
+++ b/src/renderer/components/devices/DeviceChooser.vue
@@ -1,117 +1,62 @@
-
- {{ chosenText }}
-
-
-
-
-
-
-
-
-
-
-
- remove_circle
-
-
-
-
-
-
- {{ notChosenText }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
diff --git a/src/renderer/components/devices/DeviceChooserBase.vue b/src/renderer/components/devices/DeviceChooserBase.vue
new file mode 100644
index 0000000..42e0a11
--- /dev/null
+++ b/src/renderer/components/devices/DeviceChooserBase.vue
@@ -0,0 +1,114 @@
+
+
+
+ {{ chosenText }}
+
+
+
+
+
+
+
+
+
+
+
+ delete
+
+
+
+
+
+
+ {{ notChosenText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/devices/DeviceDeleteDialog.vue b/src/renderer/components/devices/DeviceDeleteDialog.vue
index b95d059..f96e73d 100644
--- a/src/renderer/components/devices/DeviceDeleteDialog.vue
+++ b/src/renderer/components/devices/DeviceDeleteDialog.vue
@@ -49,6 +49,3 @@ export default class DeviceDeleteDialog extends DeleteDialogComponent {
}
}
-
-
diff --git a/src/renderer/components/devices/DeviceDetail.vue b/src/renderer/components/devices/DeviceDetail.vue
index 4e98ee0..08a5127 100644
--- a/src/renderer/components/devices/DeviceDetail.vue
+++ b/src/renderer/components/devices/DeviceDetail.vue
@@ -21,7 +21,7 @@
-
+
diff --git a/src/renderer/components/devices/DeviceDetailFields.vue b/src/renderer/components/devices/DeviceDetailFields.vue
index 9706ab3..1c4cfa5 100644
--- a/src/renderer/components/devices/DeviceDetailFields.vue
+++ b/src/renderer/components/devices/DeviceDetailFields.vue
@@ -1,16 +1,13 @@
-
- Device token is required.
- Device token is not valid.
-
+ :validator="$v"
+ />
@@ -33,6 +30,7 @@
import { Component, DialogSection } from "sitewhere-ide-common";
import DialogForm from "../common/form/DialogForm.vue";
+import FormToken from "../common/form/FormToken.vue";
import FormText from "../common/form/FormText.vue";
import FormTextArea from "../common/form/FormTextArea.vue";
import DeviceTypeSelector from "../devicetypes/DeviceTypeSelector.vue";
@@ -45,6 +43,7 @@ const validToken = helpers.regex("validToken", /^[a-zA-Z0-9-_]+$/);
@Component({
components: {
DialogForm,
+ FormToken,
FormText,
FormTextArea,
DeviceTypeSelector
@@ -95,6 +94,3 @@ export default class DeviceDetailFields extends DialogSection {
}
}
-
-
diff --git a/src/renderer/components/devices/DeviceDetailHeader.vue b/src/renderer/components/devices/DeviceDetailHeader.vue
index abe7b90..5e52c9b 100644
--- a/src/renderer/components/devices/DeviceDetailHeader.vue
+++ b/src/renderer/components/devices/DeviceDetailHeader.vue
@@ -1,29 +1,17 @@
-
+
+
+
+
-
+
-
-
-
- Device is not assigned
-
{{ device.comments }}
@@ -35,7 +23,7 @@
-
+
@@ -43,11 +31,16 @@
+
+
diff --git a/src/renderer/components/devices/DeviceDialog.vue b/src/renderer/components/devices/DeviceDialog.vue
index 32a95c1..538ec8e 100644
--- a/src/renderer/components/devices/DeviceDialog.vue
+++ b/src/renderer/components/devices/DeviceDialog.vue
@@ -17,10 +17,10 @@
-
+
-
+
@@ -76,7 +76,7 @@ export default class DeviceDialog extends DialogComponent {
if (this.$refs.metadata) {
this.$refs.metadata.reset();
}
- this.$refs.dialog.setActiveTab("details");
+ this.$refs.dialog.setActiveTab(0);
}
// Load dialog from a given payload.
@@ -93,7 +93,7 @@ export default class DeviceDialog extends DialogComponent {
// Called after create button is clicked.
onCreateClicked(e: any) {
if (!this.$refs.details.validate()) {
- this.$refs.dialog.setActiveTab("details");
+ this.$refs.dialog.setActiveTab(0);
return;
}
@@ -102,6 +102,3 @@ export default class DeviceDialog extends DialogComponent {
}
}
-
-
diff --git a/src/renderer/components/devices/DeviceListAreaFilter.vue b/src/renderer/components/devices/DeviceListAreaFilter.vue
new file mode 100644
index 0000000..dca2e5b
--- /dev/null
+++ b/src/renderer/components/devices/DeviceListAreaFilter.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/devices/DeviceListEntry.vue b/src/renderer/components/devices/DeviceListEntry.vue
index 4cd70f0..5e534af 100644
--- a/src/renderer/components/devices/DeviceListEntry.vue
+++ b/src/renderer/components/devices/DeviceListEntry.vue
@@ -1,24 +1,36 @@
-
-
-
- {{ device.deviceType.name }}
- {{ device.token }}
-
-
-
-
-
-
-
- Assign Device
-
-
-
+
+
+
+
+
+
+
+
+ {{ device.deviceType.name }}
+ {{ device.token }}
+ {{ device.comments }}
+
+
+
+
+
+
+ link
+
+ Create New Assignment
+
+
+
+
+
+
+
+
+
+
+
+
@@ -26,103 +38,58 @@
import { Component, Prop } from "sitewhere-ide-common";
import Vue from "vue";
-import { IStyle, styleForAssignmentStatus } from "../common/Style";
-import { IDevice, IDeviceAssignment } from "sitewhere-rest-api";
+import BrandingImage from "../common/BrandingImage.vue";
-@Component({})
+import { IStyle } from "../common/Style";
+import { IDevice, IDeviceType, IDeviceAssignment } from "sitewhere-rest-api";
+
+@Component({
+ components: {
+ BrandingImage
+ }
+})
export default class DeviceListEntry extends Vue {
@Prop() readonly device!: IDevice;
- get assignment(): IDeviceAssignment {
- return (this.device as any).assignment;
+ get deviceType(): IDeviceType {
+ return (this.device as any).deviceType;
}
- get styleForStatus(): IStyle {
- return styleForAssignmentStatus(this.assignment);
+ get assignments(): IDeviceAssignment[] {
+ return (this.device as any).activeDeviceAssignments;
}
- get hasAssignedAsset() {
- return this.assignment && this.assignment.assetId;
+ get hasAssignments() {
+ return this.assignments && this.assignments.length > 0;
}
- styleForDevice() {
- let style: IStyle = {};
- style["background-color"] = this.assignment ? "#fff" : "#f0f0ff";
- style["border"] = "1px solid " + (this.assignment ? "#ddd" : "#dde");
- return style;
+ get firstAssignment(): IDeviceAssignment | null {
+ return this.hasAssignments ? this.assignments[0] : null;
}
- // Create background image style.
- backgroundImageStyle(image: string): IStyle {
+ // Compute style of logo.
+ get logoStyle(): IStyle {
return {
- "background-image": "url(" + image + ")",
- "background-size": "contain",
- "background-repeat": "no-repeat",
- "background-position": "50% 50%"
+ height: "110px",
+ width: "110px"
};
}
// Called when a device is clicked.
onOpenDevice() {
- this.$emit("deviceOpened", this.device);
+ this.$emit("open", this.device);
}
// Open device assignment dialog.
onAssignDevice() {
- this.$emit("assignDevice", this.device);
+ this.$emit("assign", this.device);
}
}
diff --git a/src/renderer/components/devices/DeviceListFilterBar.vue b/src/renderer/components/devices/DeviceListFilterBar.vue
index 84eb36b..bd68bb0 100644
--- a/src/renderer/components/devices/DeviceListFilterBar.vue
+++ b/src/renderer/components/devices/DeviceListFilterBar.vue
@@ -1,116 +1,61 @@
-
-
-
-
-
-
-
-
- {{ areaFilter.name }}
-
- Only include devices from this area
-
-
-
-
-
-
- {{ deviceTypeFilter.name }}
-
- Only include devices from this device type
-
-
-
-
- view_module
-
- {{ deviceGroupFilter.name }}
-
- Only include devices from this device group
-
-
-
-
-
-
+
+
+
-
-
+ /** Handle device type filter cleared */
+ onDeviceTypeFilterCleared() {
+ this.$emit("clear");
+ }
+
+ /** Clear criteria */
+ onClearCriteria() {
+ this.$emit("clear");
+ }
+}
+
diff --git a/src/renderer/components/devices/DeviceListFilterDeviceTypeFields.vue b/src/renderer/components/devices/DeviceListFilterDeviceTypeFields.vue
new file mode 100644
index 0000000..4f10d2b
--- /dev/null
+++ b/src/renderer/components/devices/DeviceListFilterDeviceTypeFields.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/devices/DeviceListFilterDialog.vue b/src/renderer/components/devices/DeviceListFilterDialog.vue
index d9fc82f..c387bfa 100644
--- a/src/renderer/components/devices/DeviceListFilterDialog.vue
+++ b/src/renderer/components/devices/DeviceListFilterDialog.vue
@@ -1,253 +1,97 @@
-
-
-
- Area
- Device Type
- Device Group
- Created Date
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Include all devices in search without consideration
- of created date.
-
- Include only devices created within the last hour.
- Include only devices created within the last day.
- Include only devices created within the last week.
-
-
-
-
-
-
-
-
-
-
-
-
+ Choose criteria for filtering which devices are shown
+
+ Device Type
+
+
+
+
+
+
-
-
-
diff --git a/src/renderer/components/devices/DevicesList.vue b/src/renderer/components/devices/DevicesList.vue
index da514ee..b1c179b 100644
--- a/src/renderer/components/devices/DevicesList.vue
+++ b/src/renderer/components/devices/DevicesList.vue
@@ -10,29 +10,35 @@
>
-
+
-
+
+
+
+
+ No devices have been created for this tenant.
+
+ Click
+ {{addIcon}}in the toolbar to add a device.
+
+
-
-
-
+
+
+
+
-
+
-
+
@@ -47,12 +53,14 @@ import {
import DeviceListEntry from "./DeviceListEntry.vue";
import DeviceListFilterBar from "./DeviceListFilterBar.vue";
+import DeviceListFilterDialog from "./DeviceListFilterDialog.vue";
import DeviceCreateDialog from "./DeviceCreateDialog.vue";
import AssignmentCreateDialog from "../assignments/AssignmentCreateDialog.vue";
-import BatchCommandCreateDialog from "../batch/BatchCommandCreateDialog.vue";
+import InvocationByDeviceCriteriaCreateDialog from "../batch/InvocationByDeviceCriteriaCreateDialog.vue";
import AddButton from "../common/navbuttons/AddButton.vue";
import DeviceCommandButton from "../common/navbuttons/DeviceCommandButton.vue";
import FilterButton from "../common/navbuttons/FilterButton.vue";
+import NoResultsPanel from "../common/NoResultsPanel.vue";
import { NavigationIcon } from "../../libraries/constants";
import { routeTo } from "../common/Utils";
@@ -69,12 +77,14 @@ import {
components: {
DeviceListEntry,
DeviceListFilterBar,
+ DeviceListFilterDialog,
DeviceCreateDialog,
AssignmentCreateDialog,
- BatchCommandCreateDialog,
+ InvocationByDeviceCriteriaCreateDialog,
AddButton,
DeviceCommandButton,
- FilterButton
+ FilterButton,
+ NoResultsPanel
}
})
export default class DevicesList extends ListComponent<
@@ -85,9 +95,15 @@ export default class DevicesList extends ListComponent<
> {
$refs!: Refs<{
add: DeviceCreateDialog;
+ assign: AssignmentCreateDialog;
+ filter: DeviceListFilterDialog;
+ batch: InvocationByDeviceCriteriaCreateDialog;
}>;
- filter: {} = {};
+ addIcon: string = NavigationIcon.Add;
+
+ selected: IDevice | null = null;
+ filter: IDeviceSearchCriteria = {};
pageSizes: IPageSizes = [
{
text: "20",
@@ -110,8 +126,7 @@ export default class DevicesList extends ListComponent<
/** Build search criteria for list */
buildSearchCriteria(): IDeviceSearchCriteria {
- let criteria: IDeviceSearchCriteria = {};
- return criteria;
+ return this.filter;
}
/** Build response format for list */
@@ -130,50 +145,54 @@ export default class DevicesList extends ListComponent<
return listDevices(this.$store, criteria, format);
}
- // Called to show filter criteria dialog.
+ /** Called to show filter criteria dialog */
onShowFilterCriteria() {
- (this.$refs.filters as any).showFilterCriteriaDialog();
+ this.$refs.filter.openDialog();
+ }
+
+ /** Clears the filter criteria */
+ onClearFilterCriteria() {
+ this.filter = {};
+ this.$refs.filter.reset();
+ this.refresh();
}
- // Called when filter criteria are updated.
- onFilterUpdated(filter: any) {
- this.$data.filter = filter;
+ /** Called when filter criteria are updated */
+ onFilterUpdated(filter: IDeviceSearchCriteria) {
+ this.$refs.filter.closeDialog();
+ this.filter = filter;
this.refresh();
}
- // Open device assignment dialog.
+ /** Open device assignment dialog */
onAssignDevice(device: IDevice) {
- // let assignDialog = this.$refs["assign"];
- // assignDialog.deviceToken = device.token;
- // assignDialog.onOpenDialog();
+ this.selected = device;
+ this.$refs.assign.open();
}
- // Called after new assignment is created.
+ /** Called after new assignment is created */
onAssignmentCreated() {
this.refresh();
}
- // Called when a new device is added.
+ /** Called when a new device is added */
onDeviceAdded() {
this.refresh();
}
- // Called to open detail page for device.
+ /** Called to open detail page for device */
onOpenDevice(device: IDevice) {
routeTo(this, "/devices/" + device.token);
}
- // Called to open dialog.
+ /** Called to open dialog */
onAddDevice() {
this.$refs.add.open();
}
- // Called to invoke a batch command.
+ /** Called to invoke a batch command */
onBatchCommandInvocation() {
- (this.$refs.batch as any).onOpenDialog();
+ this.$refs.batch.open();
}
}
-
-
diff --git a/src/renderer/components/devicetypes/DeviceTypeChooser.vue b/src/renderer/components/devicetypes/DeviceTypeChooser.vue
index 926df9e..f5c9b69 100644
--- a/src/renderer/components/devicetypes/DeviceTypeChooser.vue
+++ b/src/renderer/components/devicetypes/DeviceTypeChooser.vue
@@ -1,116 +1,60 @@
-
-
- {{ chosenText }}
-
-
-
-
-
-
-
-
-
-
-
- remove_circle
-
-
-
-
-
-
- {{ notChosenText }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
diff --git a/src/renderer/components/devicetypes/DeviceTypeCommands.vue b/src/renderer/components/devicetypes/DeviceTypeCommands.vue
index 87b158a..63f76ea 100644
--- a/src/renderer/components/devicetypes/DeviceTypeCommands.vue
+++ b/src/renderer/components/devicetypes/DeviceTypeCommands.vue
@@ -9,11 +9,12 @@
>
-
+
-
+
+
@@ -21,9 +22,9 @@
diff --git a/src/renderer/components/devicetypes/DeviceTypeDetail.vue b/src/renderer/components/devicetypes/DeviceTypeDetail.vue
index db4995f..ba95333 100644
--- a/src/renderer/components/devicetypes/DeviceTypeDetail.vue
+++ b/src/renderer/components/devicetypes/DeviceTypeDetail.vue
@@ -22,23 +22,23 @@
Composition
-
-
+
+
-
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
- Token is required.
- Token is not valid.
-
+ :validator="$v"
+ />
-
+
+
+
+
-
-
-
-
- {{ deviceType.name }}
-
-
- {{ deviceType.description }}
-
-
- {{ deviceType.imageUrl }}
-
-
- {{ deviceType.containerPolicy }}
-
-
- {{ formatDate(deviceType.createdDate) }}
-
-
- {{ formatDate(deviceType.updatedDate) }}
-
+
+
+
+
+
+ {{ deviceType.name }}
+
+
+ {{ deviceType.description }}
+
+
+ {{ deviceType.imageUrl }}
+
+
+ {{ deviceType.containerPolicy }}
+
+
+ {{ formatDate(deviceType.createdDate) }}
+
+
+ {{ formatDate(deviceType.updatedDate) }}
+
+
-
+
@@ -32,24 +37,24 @@
-
-
diff --git a/src/renderer/components/devicetypes/DeviceTypeFilterChip.vue b/src/renderer/components/devicetypes/DeviceTypeFilterChip.vue
new file mode 100644
index 0000000..af824e6
--- /dev/null
+++ b/src/renderer/components/devicetypes/DeviceTypeFilterChip.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
diff --git a/src/renderer/components/devicetypes/DeviceTypeListEntry.vue b/src/renderer/components/devicetypes/DeviceTypeListEntry.vue
index 290cbff..2311c50 100644
--- a/src/renderer/components/devicetypes/DeviceTypeListEntry.vue
+++ b/src/renderer/components/devicetypes/DeviceTypeListEntry.vue
@@ -1,10 +1,18 @@
-
-
-
- {{deviceType.name}}
- {{deviceType.description}}
-
+
+
+
+
+
+
+
+
+ {{ deviceType.name }}
+ {{ deviceType.description }}
+
+
+
+
@@ -12,21 +20,24 @@
import { Component, Prop } from "sitewhere-ide-common";
import Vue from "vue";
+import BrandingImage from "../common/BrandingImage.vue";
+
+import { IStyle } from "../common/Style";
import { IDeviceType } from "sitewhere-rest-api";
-@Component({})
+@Component({
+ components: {
+ BrandingImage
+ }
+})
export default class DeviceTypeListEntry extends Vue {
@Prop() readonly deviceType!: IDeviceType;
// Compute style of logo.
- get logoStyle() {
+ get logoStyle(): IStyle {
return {
- "background-color": "#fff",
- "background-image": "url(" + this.deviceType.imageUrl + ")",
- "background-size": "contain",
- "background-repeat": "no-repeat",
- "background-position": "50% 50%",
- border: "1px solid #eee"
+ height: "110px",
+ width: "110px"
};
}
@@ -38,37 +49,8 @@ export default class DeviceTypeListEntry extends Vue {
diff --git a/src/renderer/components/devicetypes/DeviceTypeMultifilterChip.vue b/src/renderer/components/devicetypes/DeviceTypeMultifilterChip.vue
new file mode 100644
index 0000000..685a1c0
--- /dev/null
+++ b/src/renderer/components/devicetypes/DeviceTypeMultifilterChip.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
diff --git a/src/renderer/components/devicetypes/DeviceTypeMultiselect.vue b/src/renderer/components/devicetypes/DeviceTypeMultiselect.vue
new file mode 100644
index 0000000..3247bc4
--- /dev/null
+++ b/src/renderer/components/devicetypes/DeviceTypeMultiselect.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
diff --git a/src/renderer/components/devicetypes/DeviceTypeSelector.vue b/src/renderer/components/devicetypes/DeviceTypeSelector.vue
index e3b784b..2c874c2 100644
--- a/src/renderer/components/devicetypes/DeviceTypeSelector.vue
+++ b/src/renderer/components/devicetypes/DeviceTypeSelector.vue
@@ -1,5 +1,15 @@
+
-
+
@@ -17,6 +27,7 @@ import Vue from "vue";
import { Component, Prop } from "sitewhere-ide-common";
import FormSelect from "../common/form/FormSelect.vue";
+import FormSelectCondensed from "../common/form/FormSelectCondensed.vue";
import { handleError } from "../common/Utils";
import { AxiosResponse } from "axios";
@@ -30,13 +41,15 @@ import {
@Component({
components: {
- FormSelect
+ FormSelect,
+ FormSelectCondensed
}
})
export default class DeviceTypeSelector extends Vue {
@Prop(String) readonly value!: string;
@Prop(String) readonly title!: string;
@Prop(String) readonly label!: string;
+ @Prop({ default: false }) readonly dense!: boolean;
deviceTypes: IDeviceType[] = [];
@@ -65,6 +78,3 @@ export default class DeviceTypeSelector extends Vue {
}
}
-
-
diff --git a/src/renderer/components/devicetypes/DeviceTypeStatuses.vue b/src/renderer/components/devicetypes/DeviceTypeStatuses.vue
index 58f65d0..f575a0b 100644
--- a/src/renderer/components/devicetypes/DeviceTypeStatuses.vue
+++ b/src/renderer/components/devicetypes/DeviceTypeStatuses.vue
@@ -19,7 +19,16 @@
-
+
+
@@ -27,14 +36,13 @@
diff --git a/src/renderer/components/devicetypes/DeviceTypesList.vue b/src/renderer/components/devicetypes/DeviceTypesList.vue
index 2c839dd..03192a4 100644
--- a/src/renderer/components/devicetypes/DeviceTypesList.vue
+++ b/src/renderer/components/devicetypes/DeviceTypesList.vue
@@ -9,14 +9,23 @@
>
-
+
+
+
+ No device types have been created for this tenant.
+
+ Click
+ {{addIcon}}in the toolbar to add a device type.
+
+
+
-
+
-
+
@@ -27,6 +36,7 @@ import { Component, ListComponent, Refs } from "sitewhere-ide-common";
import DeviceTypeListEntry from "./DeviceTypeListEntry.vue";
import DeviceTypeCreateDialog from "./DeviceTypeCreateDialog.vue";
import AddButton from "../common/navbuttons/AddButton.vue";
+import NoResultsPanel from "../common/NoResultsPanel.vue";
import { NavigationIcon } from "../../libraries/constants";
import { routeTo } from "../common/Utils";
@@ -43,7 +53,8 @@ import {
components: {
DeviceTypeListEntry,
DeviceTypeCreateDialog,
- AddButton
+ AddButton,
+ NoResultsPanel
}
})
export default class DeviceTypesList extends ListComponent<
@@ -56,6 +67,8 @@ export default class DeviceTypesList extends ListComponent<
add: DeviceTypeCreateDialog;
}>;
+ addIcon: string = NavigationIcon.Add;
+
/** Get page icon */
get icon(): NavigationIcon {
return NavigationIcon.DeviceType;
diff --git a/src/renderer/components/devicetypes/DeviceUnitPanel.vue b/src/renderer/components/devicetypes/DeviceUnitPanel.vue
index 35fc43a..20ca2cf 100644
--- a/src/renderer/components/devicetypes/DeviceUnitPanel.vue
+++ b/src/renderer/components/devicetypes/DeviceUnitPanel.vue
@@ -42,7 +42,7 @@
-
+
Scripts
-
-
-
-
-
-
+
+
-
-
diff --git a/src/renderer/components/global/GlobalMicroservicesList.vue b/src/renderer/components/global/GlobalMicroservicesList.vue
index 1848b71..533342c 100644
--- a/src/renderer/components/global/GlobalMicroservicesList.vue
+++ b/src/renderer/components/global/GlobalMicroservicesList.vue
@@ -71,6 +71,3 @@ export default class GlobalMicroservicesList extends DetailComponent<
}
}
-
-
diff --git a/src/renderer/components/login/RemoteConnectionDetails.vue b/src/renderer/components/login/RemoteConnectionDetails.vue
new file mode 100644
index 0000000..0c5c543
--- /dev/null
+++ b/src/renderer/components/login/RemoteConnectionDetails.vue
@@ -0,0 +1,139 @@
+
+
+
+
+ Name is required.
+
+
+
+
+
+
+
+ Hostname is required.
+
+
+
+
+ Port is required.
+
+
+
+
+ add
+
+
+
+
+
+
diff --git a/src/renderer/components/login/RemoteConnectionsList.vue b/src/renderer/components/login/RemoteConnectionsList.vue
new file mode 100644
index 0000000..3b4ac02
--- /dev/null
+++ b/src/renderer/components/login/RemoteConnectionsList.vue
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+ {{ props.item.protocol === 'https' ? 'lock' : 'lock_open' }}
+ |
+ {{ getNameAndUrl(props.item) }} |
+
+ star
+ |
+
+
+
+
+
+
+
+ arrow_upwardUp
+
+
+
+
+ arrow_downwardDown
+
+
+
+
+ starDefault
+
+
+
+
+ deleteDelete
+
+
+
+
+
+
+
diff --git a/src/renderer/components/login/RemotesDialog.vue b/src/renderer/components/login/RemotesDialog.vue
new file mode 100644
index 0000000..743a4f9
--- /dev/null
+++ b/src/renderer/components/login/RemotesDialog.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/components/login/RemotesDropdown.vue b/src/renderer/components/login/RemotesDropdown.vue
new file mode 100644
index 0000000..d218139
--- /dev/null
+++ b/src/renderer/components/login/RemotesDropdown.vue
@@ -0,0 +1,49 @@
+
+
+
+ router
+ {{ getNameAndUrl(data.item) }}
+
+
+ router
+ {{ getNameAndUrl(data.item) }}
+
+
+
+
+
diff --git a/src/renderer/components/microservice/AttributeFieldPanel.vue b/src/renderer/components/microservice/AttributeFieldPanel.vue
index 154d3fd..a7146f7 100644
--- a/src/renderer/components/microservice/AttributeFieldPanel.vue
+++ b/src/renderer/components/microservice/AttributeFieldPanel.vue
@@ -13,7 +13,7 @@
{{ attribute.description }}
-
+
Attribute is required
@@ -57,14 +57,20 @@
hide-details
>
-
+
-
+
@@ -20,7 +20,6 @@ import {
Component,
Prop,
DialogComponent,
- DialogSection,
ITabbedComponent,
Refs
} from "sitewhere-ide-common";
@@ -64,7 +63,7 @@ export default class ScriptsDialog extends DialogComponent {
if (this.$refs.details) {
this.$refs.details.reset();
}
- this.$refs.dialog.setActiveTab("details");
+ this.$refs.dialog.setActiveTab(0);
}
// Load dialog from a given payload.
@@ -78,7 +77,7 @@ export default class ScriptsDialog extends DialogComponent {
// Called after create button is clicked.
onCreateClicked(e: any) {
if (!this.$refs.details.validate()) {
- this.$refs.dialog.setActiveTab("details");
+ this.$refs.dialog.setActiveTab(0);
return;
}
diff --git a/src/renderer/components/microservice/ScriptContentFields.vue b/src/renderer/components/microservice/ScriptContentFields.vue
index 1c70c00..dcfb620 100644
--- a/src/renderer/components/microservice/ScriptContentFields.vue
+++ b/src/renderer/components/microservice/ScriptContentFields.vue
@@ -65,7 +65,6 @@ export default class ScriptContentFields extends DialogSection {
/** Load list of script templates */
async loadTemplates() {
- var component = this;
let response: AxiosResponse = await listScriptTemplates(
this.$store,
this.identifier
diff --git a/src/renderer/components/microservice/ScriptCreateCloneDialog.vue b/src/renderer/components/microservice/ScriptCreateCloneDialog.vue
index 48ed627..588e47b 100644
--- a/src/renderer/components/microservice/ScriptCreateCloneDialog.vue
+++ b/src/renderer/components/microservice/ScriptCreateCloneDialog.vue
@@ -22,13 +22,7 @@ import {
import ScriptCloneDialog from "./ScriptCloneDialog.vue";
-import { AxiosPromise } from "axios";
import { IScriptVersion } from "sitewhere-rest-api";
-import { createDevice } from "../../rest/sitewhere-devices-api";
-import {
- createGlobalScript,
- createTenantScript
-} from "../../rest/sitewhere-scripting-api";
@Component({
components: {
diff --git a/src/renderer/components/microservice/ScriptsContentEditor.vue b/src/renderer/components/microservice/ScriptsContentEditor.vue
index ea142f8..fe58134 100644
--- a/src/renderer/components/microservice/ScriptsContentEditor.vue
+++ b/src/renderer/components/microservice/ScriptsContentEditor.vue
@@ -18,7 +18,7 @@
-
-
diff --git a/src/renderer/components/schedules/ScheduleCreateDialog.vue b/src/renderer/components/schedules/ScheduleCreateDialog.vue
index 926db45..eb94a50 100644
--- a/src/renderer/components/schedules/ScheduleCreateDialog.vue
+++ b/src/renderer/components/schedules/ScheduleCreateDialog.vue
@@ -2,7 +2,7 @@
-
+})
+export default class ScheduleCreateDialog extends CreateDialogComponent<
+ ISchedule,
+ IScheduleCreateRequest
+> {
+ // References.
+ $refs!: Refs<{
+ dialog: DialogComponent;
+ }>;
+
+ /** Get wrapped dialog */
+ getDialog(): DialogComponent {
+ return this.$refs.dialog;
+ }
-
+ /** Called on payload commit */
+ onCommit(payload: IScheduleCreateRequest): void {
+ this.commit(payload);
+ }
+
+ /** Implemented in subclasses to save payload */
+ save(payload: IScheduleCreateRequest): AxiosPromise {
+ return createSchedule(this.$store, payload);
+ }
+
+ /** Implemented in subclasses for after-save */
+ afterSave(payload: ISchedule): void {}
+}
+
diff --git a/src/renderer/components/schedules/ScheduleDeleteDialog.vue b/src/renderer/components/schedules/ScheduleDeleteDialog.vue
index 42e6f6e..510fe14 100644
--- a/src/renderer/components/schedules/ScheduleDeleteDialog.vue
+++ b/src/renderer/components/schedules/ScheduleDeleteDialog.vue
@@ -3,52 +3,54 @@
ref="dialog"
title="Delete Schedule"
width="400"
- :error="error"
- @delete="onDeleteConfirmed"
+ :visible="visible"
+ @delete="onDelete"
+ @cancel="onCancel"
>
- Are you sure you want to delete this schedule?
+ {{ message }}
-
-
+ /** Called after create button is clicked */
+ onDelete(e: any) {
+ this.delete();
+ }
+
+ /** Called after cancel button is clicked */
+ onCancel(e: any) {
+ this.cancel();
+ }
+}
+
diff --git a/src/renderer/components/schedules/ScheduleDetailFields.vue b/src/renderer/components/schedules/ScheduleDetailFields.vue
new file mode 100644
index 0000000..8383b63
--- /dev/null
+++ b/src/renderer/components/schedules/ScheduleDetailFields.vue
@@ -0,0 +1,225 @@
+
+
+
+
+
+
+
+ Name is required.
+
+
+
+
+
+
+
+
+
+
+ Trigger type is required.
+
+
+
+
+ Cron expression is required.
+
+
+
+
+ Repeat interval is required.
+
+
+
+
+ Repeat count is required.
+
+
+
+
+
+
diff --git a/src/renderer/components/schedules/ScheduleDialog.vue b/src/renderer/components/schedules/ScheduleDialog.vue
index c732c13..a522997 100644
--- a/src/renderer/components/schedules/ScheduleDialog.vue
+++ b/src/renderer/components/schedules/ScheduleDialog.vue
@@ -1,281 +1,104 @@
-
-
-
- Schedule Details
- Metadata
-
-
-
-
-
-
-
-
-
-
- Schedule token is required.
- Schedule token is not valid.
-
-
-
-
-
- Name is required.
-
-
-
-
-
-
-
-
-
-
-
- Trigger type is required.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ Details
+ Metadata
+
+
+
+
+
+
+
+
+
-
-
-
diff --git a/src/renderer/components/schedules/ScheduleUpdateDialog.vue b/src/renderer/components/schedules/ScheduleUpdateDialog.vue
index a255c51..ed60b5a 100644
--- a/src/renderer/components/schedules/ScheduleUpdateDialog.vue
+++ b/src/renderer/components/schedules/ScheduleUpdateDialog.vue
@@ -1,73 +1,74 @@
-
-
-
+
-
-
+ /** Implemented in subclasses for after-save */
+ afterSave(payload: ISchedule): void {}
+}
+
diff --git a/src/renderer/components/schedules/SchedulesList.vue b/src/renderer/components/schedules/SchedulesList.vue
index 4821f4b..cbdd6f4 100644
--- a/src/renderer/components/schedules/SchedulesList.vue
+++ b/src/renderer/components/schedules/SchedulesList.vue
@@ -2,7 +2,7 @@
{{ formatDate(props.item.createdDate) }} |
-
-
-
-
+
|
+