65 changed files with 1037 additions and 159 deletions
@ -0,0 +1,108 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.mqtt.rpc; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.paho.client.mqttv3.*; |
|||
import org.junit.Assert; |
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
import org.thingsboard.client.tools.RestClient; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
import org.thingsboard.server.mqtt.AbstractFeatureIntegrationTest; |
|||
|
|||
import static org.junit.Assert.assertEquals; |
|||
import static org.junit.Assert.assertNotNull; |
|||
|
|||
/** |
|||
* @author Valerii Sosliuk |
|||
*/ |
|||
@Slf4j |
|||
public class MqttServerSideRpcIntegrationTest extends AbstractFeatureIntegrationTest { |
|||
|
|||
private static final String MQTT_URL = "tcp://localhost:1883"; |
|||
private static final String BASE_URL = "http://localhost:8080"; |
|||
|
|||
private static final String USERNAME = "tenant@thingsboard.org"; |
|||
private static final String PASSWORD = "tenant"; |
|||
|
|||
private Device savedDevice; |
|||
|
|||
private String accessToken; |
|||
private RestClient restClient; |
|||
|
|||
@Before |
|||
public void beforeTest() throws Exception { |
|||
restClient = new RestClient(BASE_URL); |
|||
restClient.login(USERNAME, PASSWORD); |
|||
|
|||
Device device = new Device(); |
|||
device.setName("Test Server-Side RPC Device"); |
|||
savedDevice = restClient.getRestTemplate().postForEntity(BASE_URL + "/api/device", device, Device.class).getBody(); |
|||
DeviceCredentials deviceCredentials = |
|||
restClient.getRestTemplate().getForEntity(BASE_URL + "/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class).getBody(); |
|||
assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); |
|||
accessToken = deviceCredentials.getCredentialsId(); |
|||
assertNotNull(accessToken); |
|||
} |
|||
|
|||
@Test |
|||
public void testServerMqttTwoWayRpc() throws Exception { |
|||
String clientId = MqttAsyncClient.generateClientId(); |
|||
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId); |
|||
|
|||
MqttConnectOptions options = new MqttConnectOptions(); |
|||
options.setUserName(accessToken); |
|||
client.connect(options); |
|||
Thread.sleep(3000); |
|||
client.subscribe("v1/devices/me/rpc/request/+",1); |
|||
client.setCallback(new TestMqttCallback(client)); |
|||
|
|||
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; |
|||
String deviceId = savedDevice.getId().getId().toString(); |
|||
String result = restClient.getRestTemplate().postForEntity(BASE_URL + "api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class).getBody(); |
|||
log.info("Result: " + result); |
|||
Assert.assertEquals("{\"value1\":\"A\",\"value2\":\"B\"}", result); |
|||
} |
|||
|
|||
private static class TestMqttCallback implements MqttCallback { |
|||
|
|||
private final MqttAsyncClient client; |
|||
|
|||
TestMqttCallback(MqttAsyncClient client) { |
|||
this.client = client; |
|||
} |
|||
|
|||
@Override |
|||
public void connectionLost(Throwable throwable) { |
|||
} |
|||
|
|||
@Override |
|||
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception { |
|||
log.info("Message Arrived: " + mqttMessage.getPayload().toString()); |
|||
MqttMessage message = new MqttMessage(); |
|||
String responseTopic = requestTopic.replace("request", "response"); |
|||
message.setPayload("{\"value1\":\"A\", \"value2\":\"B\"}".getBytes()); |
|||
client.publish(responseTopic, message); |
|||
} |
|||
|
|||
@Override |
|||
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.msg.core; |
|||
|
|||
import lombok.ToString; |
|||
import org.thingsboard.server.common.msg.kv.AttributesKVMsg; |
|||
import org.thingsboard.server.common.msg.session.MsgType; |
|||
import org.thingsboard.server.common.msg.session.ToDeviceMsg; |
|||
|
|||
@ToString |
|||
public class SessionCloseNotification implements ToDeviceMsg { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Override |
|||
public boolean isSuccess() { |
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public MsgType getMsgType() { |
|||
return MsgType.SESSION_CLOSE; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.msg.core; |
|||
|
|||
import org.thingsboard.server.common.msg.session.FromDeviceMsg; |
|||
import org.thingsboard.server.common.msg.session.MsgType; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public class SessionOpenMsg implements FromDeviceMsg { |
|||
@Override |
|||
public MsgType getMsgType() { |
|||
return MsgType.SESSION_OPEN; |
|||
} |
|||
} |
|||
File diff suppressed because one or more lines are too long
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.extensions.api.device; |
|||
|
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.ToString; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.kv.AttributeKey; |
|||
|
|||
import java.util.Set; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
public class DeviceCredentialsUpdateNotificationMsg implements ToDeviceActorNotificationMsg { |
|||
|
|||
private final TenantId tenantId; |
|||
private final DeviceId deviceId; |
|||
|
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
restUrl=http://localhost:8080 |
|||
mqttUrls=tcp://localhost:1883 |
|||
deviceCount=1 |
|||
durationMs=60000 |
|||
iterationIntervalMs=1000 |
|||
@ -0,0 +1,105 @@ |
|||
/* |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
import './json-form-image.scss'; |
|||
|
|||
import React from 'react'; |
|||
import ThingsboardBaseComponent from './json-form-base-component.jsx'; |
|||
import Dropzone from 'react-dropzone'; |
|||
import IconButton from 'material-ui/IconButton'; |
|||
|
|||
class ThingsboardImage extends React.Component { |
|||
|
|||
constructor(props) { |
|||
super(props); |
|||
this.onValueChanged = this.onValueChanged.bind(this); |
|||
this.onDrop = this.onDrop.bind(this); |
|||
this.onClear = this.onClear.bind(this); |
|||
var value = props.value ? props.value + '' : null; |
|||
this.state = { |
|||
imageUrl: value |
|||
}; |
|||
} |
|||
|
|||
onValueChanged(value) { |
|||
this.setState({ |
|||
imageUrl: value |
|||
}); |
|||
this.props.onChangeValidate({ |
|||
target: { |
|||
value: value |
|||
} |
|||
}); |
|||
} |
|||
|
|||
onDrop(files) { |
|||
var reader = new FileReader(); |
|||
reader.onload = (function(tImg) { |
|||
return function(event) { |
|||
tImg.onValueChanged(event.target.result); |
|||
}; |
|||
})(this); |
|||
reader.readAsDataURL(files[0]); |
|||
} |
|||
|
|||
onClear(event) { |
|||
if (event) { |
|||
event.stopPropagation(); |
|||
} |
|||
this.onValueChanged(null); |
|||
} |
|||
|
|||
render() { |
|||
|
|||
var labelClass = "tb-label"; |
|||
if (this.props.form.required) { |
|||
labelClass += " tb-required"; |
|||
} |
|||
if (this.props.form.readonly) { |
|||
labelClass += " tb-readonly"; |
|||
} |
|||
if (this.state.focused) { |
|||
labelClass += " tb-focused"; |
|||
} |
|||
|
|||
var previewComponent; |
|||
if (this.state.imageUrl) { |
|||
previewComponent = <img className="tb-image-preview" src={this.state.imageUrl} />; |
|||
} else { |
|||
previewComponent = <div>No image selected</div>; |
|||
} |
|||
|
|||
return ( |
|||
<div className="tb-container"> |
|||
<label className={labelClass}>{this.props.form.title}</label> |
|||
<div className="tb-image-select-container"> |
|||
<div className="tb-image-preview-container">{previewComponent}</div> |
|||
<div className="tb-image-clear-container"> |
|||
<IconButton className="tb-image-clear-btn" iconClassName="material-icons" tooltip="Clear" onTouchTap={this.onClear}>clear</IconButton> |
|||
</div> |
|||
<Dropzone className="tb-dropzone" |
|||
onDrop={this.onDrop} |
|||
multiple={false} |
|||
accept="image/*"> |
|||
<div>Drop an image or click to select a file to upload.</div> |
|||
</Dropzone> |
|||
</div> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
export default ThingsboardBaseComponent(ThingsboardImage); |
|||
@ -0,0 +1,79 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
$previewSize: 100px; |
|||
|
|||
.tb-image-select-container { |
|||
position: relative; |
|||
height: $previewSize; |
|||
width: 100%; |
|||
} |
|||
|
|||
.tb-image-preview { |
|||
max-width: $previewSize; |
|||
max-height: $previewSize; |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
.tb-image-preview-container { |
|||
position: relative; |
|||
width: $previewSize; |
|||
height: $previewSize; |
|||
margin-right: 12px; |
|||
border: solid 1px; |
|||
vertical-align: top; |
|||
float: left; |
|||
div { |
|||
width: 100%; |
|||
font-size: 18px; |
|||
text-align: center; |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
transform: translate(-50%,-50%); |
|||
} |
|||
} |
|||
|
|||
.tb-dropzone { |
|||
position: relative; |
|||
border: dashed 2px; |
|||
height: $previewSize; |
|||
vertical-align: top; |
|||
padding: 0 8px; |
|||
overflow: hidden; |
|||
div { |
|||
width: 100%; |
|||
font-size: 24px; |
|||
text-align: center; |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
transform: translate(-50%,-50%); |
|||
} |
|||
} |
|||
|
|||
.tb-image-clear-container { |
|||
width: 48px; |
|||
height: $previewSize; |
|||
position: relative; |
|||
float: right; |
|||
} |
|||
.tb-image-clear-btn { |
|||
position: absolute !important; |
|||
top: 50%; |
|||
transform: translate(0%,-50%) !important; |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
/* |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
import './dashboard-settings.scss'; |
|||
|
|||
/*@ngInject*/ |
|||
export default function DashboardSettingsController($scope, $mdDialog, gridSettings) { |
|||
|
|||
var vm = this; |
|||
|
|||
vm.cancel = cancel; |
|||
vm.save = save; |
|||
vm.imageAdded = imageAdded; |
|||
vm.clearImage = clearImage; |
|||
|
|||
vm.gridSettings = gridSettings || {}; |
|||
|
|||
vm.gridSettings.backgroundColor = vm.gridSettings.backgroundColor || 'rgba(0,0,0,0)'; |
|||
vm.gridSettings.columns = vm.gridSettings.columns || 24; |
|||
vm.gridSettings.margins = vm.gridSettings.margins || [10, 10]; |
|||
vm.hMargin = vm.gridSettings.margins[0]; |
|||
vm.vMargin = vm.gridSettings.margins[1]; |
|||
|
|||
function cancel() { |
|||
$mdDialog.cancel(); |
|||
} |
|||
|
|||
function imageAdded($file) { |
|||
var reader = new FileReader(); |
|||
reader.onload = function(event) { |
|||
$scope.$apply(function() { |
|||
if (event.target.result && event.target.result.startsWith('data:image/')) { |
|||
$scope.theForm.$setDirty(); |
|||
vm.gridSettings.backgroundImageUrl = event.target.result; |
|||
} |
|||
}); |
|||
}; |
|||
reader.readAsDataURL($file.file); |
|||
} |
|||
|
|||
function clearImage() { |
|||
$scope.theForm.$setDirty(); |
|||
vm.gridSettings.backgroundImageUrl = null; |
|||
} |
|||
|
|||
function save() { |
|||
$scope.theForm.$setPristine(); |
|||
vm.gridSettings.margins = [vm.hMargin, vm.vMargin]; |
|||
$mdDialog.hide(vm.gridSettings); |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
/** |
|||
* Copyright © 2016 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
$previewSize: 100px; |
|||
|
|||
.file-input { |
|||
display: none; |
|||
} |
|||
|
|||
.tb-container { |
|||
position: relative; |
|||
margin-top: 32px; |
|||
padding: 10px 0; |
|||
} |
|||
|
|||
.tb-image-select-container { |
|||
position: relative; |
|||
height: $previewSize; |
|||
width: 100%; |
|||
} |
|||
|
|||
.tb-image-preview { |
|||
max-width: $previewSize; |
|||
max-height: $previewSize; |
|||
width: auto; |
|||
height: auto; |
|||
} |
|||
|
|||
.tb-image-preview-container { |
|||
position: relative; |
|||
width: $previewSize; |
|||
height: $previewSize; |
|||
margin-right: 12px; |
|||
border: solid 1px; |
|||
vertical-align: top; |
|||
float: left; |
|||
div { |
|||
width: 100%; |
|||
font-size: 18px; |
|||
text-align: center; |
|||
} |
|||
div, .tb-image-preview { |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
transform: translate(-50%,-50%); |
|||
} |
|||
} |
|||
|
|||
.tb-flow-drop { |
|||
position: relative; |
|||
border: dashed 2px; |
|||
height: $previewSize; |
|||
vertical-align: top; |
|||
padding: 0 8px; |
|||
overflow: hidden; |
|||
min-width: 300px; |
|||
label { |
|||
width: 100%; |
|||
font-size: 24px; |
|||
text-align: center; |
|||
position: absolute; |
|||
top: 50%; |
|||
left: 50%; |
|||
transform: translate(-50%,-50%); |
|||
} |
|||
} |
|||
|
|||
.tb-image-clear-container { |
|||
width: 48px; |
|||
height: $previewSize; |
|||
position: relative; |
|||
float: right; |
|||
} |
|||
.tb-image-clear-btn { |
|||
position: absolute !important; |
|||
top: 50%; |
|||
transform: translate(0%,-50%) !important; |
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<md-dialog aria-label="{{ 'dashboard.settings' | translate }}"> |
|||
<form name="theForm" ng-submit="vm.save()"> |
|||
<md-toolbar> |
|||
<div class="md-toolbar-tools"> |
|||
<h2 translate>dashboard.settings</h2> |
|||
<span flex></span> |
|||
<md-button class="md-icon-button" ng-click="vm.cancel()"> |
|||
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
|||
</md-button> |
|||
</div> |
|||
</md-toolbar> |
|||
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-show="loading"></md-progress-linear> |
|||
<span style="min-height: 5px;" flex="" ng-show="!loading"></span> |
|||
<md-dialog-content> |
|||
<div class="md-dialog-content"> |
|||
<fieldset ng-disabled="loading"> |
|||
<md-input-container class="md-block"> |
|||
<label translate>dashboard.columns-count</label> |
|||
<input required type="number" step="any" name="columns" ng-model="vm.gridSettings.columns" min="10" |
|||
max="1000" /> |
|||
<div ng-messages="theForm.columns.$error" multiple md-auto-hide="false"> |
|||
<div ng-message="required" translate>dashboard.columns-count-required</div> |
|||
<div ng-message="min" translate>dashboard.min-columns-count-message</div> |
|||
<div ng-message="max">dashboard.max-columns-count-message</div> |
|||
</div> |
|||
</md-input-container> |
|||
<small translate>dashboard.widgets-margins</small> |
|||
<div flex layout="row"> |
|||
<md-input-container flex class="md-block"> |
|||
<label translate>dashboard.horizontal-margin</label> |
|||
<input required type="number" step="any" name="hMargin" ng-model="vm.hMargin" min="0" |
|||
max="50" /> |
|||
<div ng-messages="theForm.hMargin.$error" multiple md-auto-hide="false"> |
|||
<div ng-message="required" translate>dashboard.horizontal-margin-required</div> |
|||
<div ng-message="min" translate>dashboard.min-horizontal-margin-message</div> |
|||
<div ng-message="max" translate>dashboard.max-horizontal-margin-message</div> |
|||
</div> |
|||
</md-input-container> |
|||
<md-input-container flex class="md-block"> |
|||
<label translate>dashboard.vertical-margin</label> |
|||
<input required type="number" step="any" name="vMargin" ng-model="vm.vMargin" min="0" |
|||
max="50" /> |
|||
<div ng-messages="theForm.vMargin.$error" multiple md-auto-hide="false"> |
|||
<div ng-message="required" translate>dashboard.vertical-margin-required</div> |
|||
<div ng-message="min" translate>dashboard.min-vertical-margin-message</div> |
|||
<div ng-message="max" translate>dashboard.max-vertical-margin-message</div> |
|||
</div> |
|||
</md-input-container> |
|||
</div> |
|||
<div flex |
|||
ng-required="false" |
|||
md-color-picker |
|||
ng-model="vm.gridSettings.backgroundColor" |
|||
label="{{ 'dashboard.background-color' | translate }}" |
|||
icon="format_color_fill" |
|||
default="rgba(0,0,0,0)" |
|||
md-color-clear-button="false" |
|||
open-on-input="true" |
|||
md-color-generic-palette="false" |
|||
md-color-history="false" |
|||
></div> |
|||
<div class="tb-container"> |
|||
<label class="tb-label" translate>dashboard.background-image</label> |
|||
<div flow-init="{singleFile:true}" |
|||
flow-file-added="vm.imageAdded( $file )" class="tb-image-select-container"> |
|||
<div class="tb-image-preview-container"> |
|||
<div ng-show="!vm.gridSettings.backgroundImageUrl" translate>dashboard.no-image</div> |
|||
<img ng-show="vm.gridSettings.backgroundImageUrl" class="tb-image-preview" src="{{vm.gridSettings.backgroundImageUrl}}" /> |
|||
</div> |
|||
<div class="tb-image-clear-container"> |
|||
<md-button ng-click="vm.clearImage()" |
|||
class="tb-image-clear-btn md-icon-button md-primary" aria-label="{{ 'action.remove' | translate }}"> |
|||
<md-tooltip md-direction="top"> |
|||
{{ 'action.remove' | translate }} |
|||
</md-tooltip> |
|||
<md-icon aria-label="{{ 'action.remove' | translate }}" class="material-icons"> |
|||
close |
|||
</md-icon> |
|||
</md-button> |
|||
</div> |
|||
<div class="alert tb-flow-drop" flow-drop> |
|||
<label for="select" translate>dashboard.drop-image</label> |
|||
<input class="file-input" flow-btn flow-attrs="{accept:'image/*'}" id="select"> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</fieldset> |
|||
</div> |
|||
</md-dialog-content> |
|||
<md-dialog-actions layout="row"> |
|||
<span flex></span> |
|||
<md-button ng-disabled="loading || !theForm.$dirty || !theForm.$valid" type="submit" class="md-raised md-primary"> |
|||
{{ 'action.save' | translate }} |
|||
</md-button> |
|||
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button> |
|||
</md-dialog-actions> |
|||
</form> |
|||
</md-dialog> |
|||
Loading…
Reference in new issue