Edit Tags and Properties
This commit is contained in:
Родитель
768d78db9e
Коммит
d77bd3d023
|
@ -204,4 +204,5 @@ VendingMachineSample/packages/*
|
|||
# Merge file remains
|
||||
*.orig
|
||||
/RemoteMonitoring.VC.db
|
||||
/RemoteMonitoring.VC.VC.opendb
|
||||
/RemoteMonitoring.VC.VC.opendb
|
||||
/Simulator/Simulator.WebJob/DMSimulator.exe
|
||||
|
|
|
@ -175,6 +175,7 @@
|
|||
<Compile Include="Helpers\BlobStorageClient.cs" />
|
||||
<Compile Include="Helpers\BlobStorageClientFactory.cs" />
|
||||
<Compile Include="Helpers\BlobStorageReader.cs" />
|
||||
<Compile Include="Helpers\SupportedMethodsHelper.cs" />
|
||||
<Compile Include="Helpers\IAzureTableStorageClient.cs" />
|
||||
<Compile Include="Helpers\IAzureTableStorageClientFactory.cs" />
|
||||
<Compile Include="Helpers\IBlobStorageClientFactory.cs" />
|
||||
|
@ -188,6 +189,7 @@
|
|||
<Compile Include="Models\DeviceModel.cs" />
|
||||
<Compile Include="Models\DeviceProperties.cs" />
|
||||
<Compile Include="Models\IoTHub.cs" />
|
||||
<Compile Include="Models\SupportedMethod.cs" />
|
||||
<Compile Include="Models\SystemProperties.cs" />
|
||||
<Compile Include="Models\Telemetry.cs" />
|
||||
<Compile Include="Schema\SchemaHelper.cs" />
|
||||
|
|
|
@ -167,7 +167,7 @@ if ($cloudDeploy)
|
|||
$webSku = GetResourceObject $suitename $suitename Microsoft.Web/sites
|
||||
$params += @{webSku=$($webSku.Properties.Sku)}
|
||||
$webPlan = GetResourceObject $suiteName ("{0}-plan" -f $suiteName) Microsoft.Web/serverfarms
|
||||
$params += @{webWorkerSize=$($webPlan.Properties.WorkerSize)}
|
||||
$params += @{webWorkerSize=$($webPlan.Properties.WorkerSizeId)}
|
||||
$params += @{webWorkerCount=$($webPlan.Properties.NumberOfWorkers)}
|
||||
$jobName = "{0}-jobhost" -f $suitename
|
||||
if (ResourceObjectExists $suitename $jobName Microsoft.Web/sites)
|
||||
|
@ -175,7 +175,7 @@ if ($cloudDeploy)
|
|||
$webJobSku = GetResourceObject $suitename $jobName Microsoft.Web/sites
|
||||
$params += @{webJobSku=$($webJobSku.Properties.Sku)}
|
||||
$webJobPlan = GetResourceObject $suiteName ("{0}-jobsplan" -f $suiteName) Microsoft.Web/serverfarms
|
||||
$params += @{webJobWorkerSize=$($webJobPlan.Properties.WorkerSize)}
|
||||
$params += @{webJobWorkerSize=$($webJobPlan.Properties.WorkerSizeId)}
|
||||
$params += @{webJobWorkerCount=$($webJobPlan.Properties.NumberOfWorkers)}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,10 +154,16 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Factory
|
|||
));
|
||||
device.Commands.Add(new Command(
|
||||
"ChangeDeviceState",
|
||||
DeliveryType.Method,
|
||||
DeliveryType.Message,
|
||||
"Sets the device state metadata property that the device reports. This is useful for testing back-end logic.",
|
||||
new[] { new Parameter("DeviceState", "string") }
|
||||
));
|
||||
device.Commands.Add(new Command(
|
||||
"ChangeDeviceState",
|
||||
DeliveryType.Method,
|
||||
"Sets the device state metadata property that the device reports. This is useful for testing back-end logic.",
|
||||
new[] { new Parameter("DeviceState", "string") }
|
||||
));
|
||||
}
|
||||
|
||||
public static List<string> GetDefaultDeviceNames()
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Models;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Models.Commands;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Helpers
|
||||
{
|
||||
|
||||
public static class SupportedMethodsHelper
|
||||
{
|
||||
private static string SupportedMethodsKey = "SupportedMethods";
|
||||
|
||||
public static JObject GenerateSupportedMethodsReportedProperty(List<Command> commands)
|
||||
{
|
||||
var methods = new Dictionary<string, SupportedMethod>();
|
||||
foreach (var command in commands.Where(c => c.DeliveryType == DeliveryType.Method))
|
||||
{
|
||||
string normalizedMethodName = NormalizeMethodName(command);
|
||||
methods[normalizedMethodName] = Convert(command);
|
||||
}
|
||||
|
||||
var obj = new JObject();
|
||||
obj[SupportedMethodsKey] = JObject.FromObject(methods);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static void AddSupportedMethodsFromReportedProperty(DeviceModel device, Twin twin)
|
||||
{
|
||||
if (!twin.Properties.Reported.Contains(SupportedMethodsKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The property could be either TwinColltion or JObject that depends on where the property is generated from
|
||||
// Convert the property to JObject to unify the type.
|
||||
var methods = JObject.FromObject(twin.Properties.Reported[SupportedMethodsKey]).ToObject<Dictionary<string, SupportedMethod>>();
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var command = Convert(method.Value);
|
||||
|
||||
if (command != null && !device.Commands.Any(c => c.Name == command.Name && c.DeliveryType == DeliveryType.Method))
|
||||
{
|
||||
device.Commands.Add(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSupportedMethodProperty(string propertyName)
|
||||
{
|
||||
return propertyName == SupportedMethodsKey || propertyName.StartsWith(SupportedMethodsKey + ".");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert Command to SupportedMethod. Array will be convert to an object since the Twin doesn't support array.
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <returns></returns>
|
||||
private static SupportedMethod Convert(Command command)
|
||||
{
|
||||
var method = new SupportedMethod();
|
||||
method.Name = command.Name;
|
||||
method.Description = command.Description;
|
||||
command.Parameters.ForEach(p => method.Parameters.Add(p.Name, p));
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert SupportedMethod back to Command.
|
||||
/// </summary>
|
||||
/// <param name="method"></param>
|
||||
/// <returns></returns>
|
||||
private static Command Convert(SupportedMethod method)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(method.Name))
|
||||
{
|
||||
throw new ArgumentNullException("Method Name");
|
||||
}
|
||||
|
||||
var command = new Command();
|
||||
command.Name = method.Name;
|
||||
command.Description = method.Description;
|
||||
command.DeliveryType = DeliveryType.Method;
|
||||
|
||||
foreach (var parameter in method.Parameters)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(parameter.Value.Type))
|
||||
{
|
||||
throw new ArgumentNullException("Parameter Type");
|
||||
}
|
||||
|
||||
command.Parameters.Add(new Parameter(parameter.Key, parameter.Value.Type));
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceWarning("Failed to covert supported method from reported property : {0}, message: {1}", JsonConvert.SerializeObject(method), ex.Message);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeMethodName(Command command)
|
||||
{
|
||||
var parts = new List<string> { command.Name.Replace("_", "__") };
|
||||
parts.AddRange(command.Parameters.Select(p => p.Type).ToList());
|
||||
|
||||
return string.Join("_", parts);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Models
|
||||
{
|
||||
public class SupportedMethod
|
||||
{
|
||||
[JsonConstructor]
|
||||
public SupportedMethod()
|
||||
{
|
||||
Parameters = new Dictionary<string, Parameter>();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public Dictionary<string, Parameter> Parameters { get; set; }
|
||||
public string Description { get; set; }
|
||||
}
|
||||
}
|
|
@ -226,6 +226,8 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infr
|
|||
// Get original device document
|
||||
DeviceModel existingDevice = await this.GetDeviceAsync(device.IoTHub.ConnectionDeviceId);
|
||||
|
||||
SupportedMethodsHelper.AddSupportedMethodsFromReportedProperty(device, existingDevice.Twin);
|
||||
|
||||
// Save the command history, original created date, and system properties (if any) of the existing device
|
||||
if (existingDevice.DeviceProperties != null)
|
||||
{
|
||||
|
@ -907,7 +909,7 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infr
|
|||
await _nameCacheLogic.AddNameAsync("desired." + p.Key);
|
||||
}
|
||||
|
||||
foreach (var p in twin.Properties.Reported.AsEnumerableFlatten())
|
||||
foreach (var p in twin.Properties.Reported.AsEnumerableFlatten().Where(pair => !SupportedMethodsHelper.IsSupportedMethodProperty(pair.Key)))
|
||||
{
|
||||
await _nameCacheLogic.AddNameAsync("reported." + p.Key);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Configurations;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Extensions;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Helpers;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Models;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Repository;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infrastructure.Models;
|
||||
|
@ -87,7 +88,7 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infr
|
|||
LastUpdatedUtc = pair.Value.LastUpdated
|
||||
});
|
||||
|
||||
var reportedProperties = device.Twin.Properties.Reported.AsEnumerableFlatten().OrderBy(pair => pair.Key).Select(pair => new DevicePropertyValueModel
|
||||
var reportedProperties = device.Twin.Properties.Reported.AsEnumerableFlatten().Where(pair => !SupportedMethodsHelper.IsSupportedMethodProperty(pair.Key)).OrderBy(pair => pair.Key).Select(pair => new DevicePropertyValueModel
|
||||
{
|
||||
DisplayOrder = 3,
|
||||
IsEditable = false,
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
<HintPath>..\..\packages\Microsoft.Azure.Devices.Client.1.0.16\lib\net45\Microsoft.Azure.Devices.Client.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Azure.Documents.Client, Version=1.9.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
|
||||
<Reference Include="Microsoft.Data.Edm, Version=5.6.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\packages\Microsoft.Data.Edm.5.6.4\lib\net40\Microsoft.Data.Edm.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
|
@ -201,7 +202,6 @@
|
|||
<Compile Include="Models\DeviceTelemetryFieldModel.cs" />
|
||||
<Compile Include="Models\DeviceTelemetrySummaryModel.cs" />
|
||||
<Compile Include="Models\DeviceListColumns.cs" />
|
||||
<Compile Include="Models\Job.cs" />
|
||||
<Compile Include="Models\JobSummary.cs" />
|
||||
<Compile Include="Models\NameCacheEntity.cs" />
|
||||
<Compile Include="Models\NameCacheEntityType.cs" />
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infrastructure.Models
|
||||
{
|
||||
public class Job : JobResponse
|
||||
{
|
||||
// TODO: mock code to replace JobId for testing
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string QueryName { get; set; }
|
||||
|
||||
public JobOperationType OperationType { get; set; }
|
||||
|
||||
public enum JobOperationType
|
||||
{
|
||||
EditPropertyOrTag,
|
||||
InvokeMethod
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infr
|
|||
{
|
||||
public class DeviceRegistryRepository : IDeviceRegistryCrudRepository, IDeviceRegistryListRepository
|
||||
{
|
||||
private readonly IDocumentDBClient<DeviceModel> _documentClient;
|
||||
protected readonly IDocumentDBClient<DeviceModel> _documentClient;
|
||||
|
||||
public DeviceRegistryRepository(IDocumentDBClient<DeviceModel> documentClient)
|
||||
{
|
||||
|
|
|
@ -71,27 +71,38 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infr
|
|||
|
||||
public override async Task<DeviceListQueryResult> GetDeviceList(DeviceListQuery query)
|
||||
{
|
||||
// Kick-off DocDB query initializing
|
||||
var queryTask = this._documentClient.QueryAsync();
|
||||
|
||||
// Considering all the device properties was copied to IoT Hub twin as tag or
|
||||
// reported property, we will only query on the IoT Hub twins. The DocumentDB
|
||||
// will not be touched.
|
||||
var twins = await this._deviceManager.QueryDevicesAsync(query);
|
||||
|
||||
var tasks = twins.Select(async twin =>
|
||||
{
|
||||
var device = await base.GetDeviceAsync(twin.DeviceId);
|
||||
device.Twin = twin;
|
||||
return device;
|
||||
});
|
||||
|
||||
var filteredDevices = await Task.WhenAll(tasks);
|
||||
var filteredDevices = await this._deviceManager.QueryDevicesAsync(query);
|
||||
|
||||
var sortedDevices = this.SortDeviceList(filteredDevices.AsQueryable(), query.SortColumn, query.SortOrder);
|
||||
|
||||
var pagedDeviceList = sortedDevices.Skip(query.Skip).Take(query.Take).ToList();
|
||||
|
||||
// Query on DocDB for traditional device properties, commands and so on
|
||||
var deviceIds = pagedDeviceList.Select(twin => twin.DeviceId);
|
||||
var devicesFromDocDB = (await queryTask).Where(x => deviceIds.Contains(x.DeviceProperties.DeviceID))
|
||||
.ToDictionary(d => d.DeviceProperties.DeviceID);
|
||||
|
||||
return new DeviceListQueryResult
|
||||
{
|
||||
Results = pagedDeviceList,
|
||||
Results = pagedDeviceList.Select(twin =>
|
||||
{
|
||||
DeviceModel deviceModel;
|
||||
if (devicesFromDocDB.TryGetValue(twin.DeviceId, out deviceModel))
|
||||
{
|
||||
deviceModel.Twin = twin;
|
||||
return deviceModel;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}).Where(model => model != null).ToList(),
|
||||
TotalDeviceCount = (int)await this._deviceManager.GetDeviceCountAsync(),
|
||||
TotalFilteredCount = filteredDevices.Count()
|
||||
};
|
||||
|
@ -107,7 +118,7 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infr
|
|||
await this._deviceManager.UpdateTwinAsync(deviceId, twin);
|
||||
}
|
||||
|
||||
private IQueryable<DeviceModel> SortDeviceList(IQueryable<DeviceModel> deviceList, string sortColumn, QuerySortOrder sortOrder)
|
||||
private IQueryable<Twin> SortDeviceList(IQueryable<Twin> deviceList, string sortColumn, QuerySortOrder sortOrder)
|
||||
{
|
||||
// if a sort column was not provided then return the full device list in its original sort
|
||||
if (string.IsNullOrWhiteSpace(sortColumn))
|
||||
|
@ -115,7 +126,7 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infr
|
|||
return deviceList;
|
||||
}
|
||||
|
||||
Func<DeviceModel, dynamic> keySelector = item => item.Twin.Get(sortColumn);
|
||||
Func<Twin, dynamic> keySelector = twin => twin.Get(sortColumn);
|
||||
|
||||
if (sortOrder == QuerySortOrder.Ascending)
|
||||
{
|
||||
|
|
|
@ -23,6 +23,9 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infr
|
|||
Task<long> GetDeviceCountAsync();
|
||||
Task<IEnumerable<DeviceJob>> GetDeviceJobsByDeviceIdAsync(string deviceId);
|
||||
Task<IEnumerable<DeviceJob>> GetDeviceJobsByJobIdAsync(string jobId);
|
||||
Task<IEnumerable<JobResponse>> GetJobResponsesAsync();
|
||||
Task<JobResponse> GetJobResponseByJobIdAsync(string jobId);
|
||||
Task<IEnumerable<JobResponse>> GetJobResponsesByStatus(JobStatus status);
|
||||
Task<JobResponse> CancelJobByJobIdAsync(string jobId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Configurations;
|
||||
|
@ -78,6 +79,12 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infr
|
|||
await this._deviceManager.UpdateTwinAsync(deviceId, twin, twin.ETag);
|
||||
}
|
||||
|
||||
public async Task ScheduleTwinUpdate(string deviceId,Twin twin, DeviceListQuery query,DateTime startDateUtc, long maxExecutionTimeInSeconds)
|
||||
{
|
||||
var sqlQuery = query.GetSQLQuery();
|
||||
await this._jobClient.ScheduleTwinUpdateAsync(Guid.NewGuid().ToString(), sqlQuery, twin, startDateUtc, maxExecutionTimeInSeconds);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Twin>> QueryDevicesAsync(DeviceListQuery query)
|
||||
{
|
||||
var sqlQuery = query.GetSQLQuery();
|
||||
|
@ -107,11 +114,51 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infr
|
|||
return await this.QueryDeviceJobs($"SELECT * FROM devices.jobs WHERE devices.jobs.jobId='{jobId}'");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<JobResponse>> GetJobResponsesAsync()
|
||||
{
|
||||
var jobQuery = _jobClient.CreateQuery();
|
||||
|
||||
var results = new List<JobResponse>();
|
||||
while (jobQuery.HasMoreResults)
|
||||
{
|
||||
results.AddRange(await jobQuery.GetNextAsJobResponseAsync());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public async Task<JobResponse> GetJobResponseByJobIdAsync(string jobId)
|
||||
{
|
||||
return await this._jobClient.GetJobAsync(jobId);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<JobResponse>> GetJobResponsesByStatus(JobStatus status)
|
||||
{
|
||||
JobStatus? queryStatus = status;
|
||||
|
||||
// [WORDAROUND] 'Scheduled' is not available for query. Query all jobs then filter at application level as workaround
|
||||
if (status == JobStatus.Scheduled)
|
||||
{
|
||||
queryStatus = null;
|
||||
}
|
||||
|
||||
var jobs = new List<JobResponse>();
|
||||
|
||||
var query = this._jobClient.CreateQuery(null, queryStatus);
|
||||
while (query.HasMoreResults)
|
||||
{
|
||||
var result = await query.GetNextAsJobResponseAsync();
|
||||
jobs.AddRange(result.Where(j => j.Status == status));
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
public async Task<JobResponse> CancelJobByJobIdAsync(string jobId)
|
||||
{
|
||||
return await this._jobClient.CancelJobAsync(jobId);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<DeviceJob>> QueryDeviceJobs(string sqlQueryString)
|
||||
{
|
||||
var jobQuery = this._deviceManager.CreateQuery(sqlQueryString);
|
||||
|
|
|
@ -115,6 +115,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Active Jobs.
|
||||
/// </summary>
|
||||
public static string ActiveJobs {
|
||||
get {
|
||||
return ResourceManager.GetString("ActiveJobs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Add a {0}.
|
||||
/// </summary>
|
||||
|
@ -124,6 +133,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Add Desired Property.
|
||||
/// </summary>
|
||||
public static string AddDesiredProperty {
|
||||
get {
|
||||
return ResourceManager.GetString("AddDesiredProperty", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Add a Device.
|
||||
/// </summary>
|
||||
|
@ -169,6 +187,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Add Tag.
|
||||
/// </summary>
|
||||
public static string AddTag {
|
||||
get {
|
||||
return ResourceManager.GetString("AddTag", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Administrator.
|
||||
/// </summary>
|
||||
|
@ -475,6 +502,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cancel.
|
||||
/// </summary>
|
||||
public static string CancelJobAction {
|
||||
get {
|
||||
return ResourceManager.GetString("CancelJobAction", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cellular Conn.
|
||||
/// </summary>
|
||||
|
@ -565,6 +601,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Clone Job.
|
||||
/// </summary>
|
||||
public static string CloneJobAction {
|
||||
get {
|
||||
return ResourceManager.GetString("CloneJobAction", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Closed.
|
||||
/// </summary>
|
||||
|
@ -1015,6 +1060,24 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to NO. OF DEVICES.
|
||||
/// </summary>
|
||||
public static string DeviceCountHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("DeviceCountHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to DEVICE IN JOB.
|
||||
/// </summary>
|
||||
public static string DeviceCountSubHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("DeviceCountSubHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to DEVICE DETAILS.
|
||||
/// </summary>
|
||||
|
@ -1168,6 +1231,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Methods.
|
||||
/// </summary>
|
||||
public static string DeviceMethods {
|
||||
get {
|
||||
return ResourceManager.GetString("DeviceMethods", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invoke Method for {0}.
|
||||
/// </summary>
|
||||
|
@ -1339,6 +1411,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit Desired Properties for {0}.
|
||||
/// </summary>
|
||||
public static string EditDesiredPropertiesFor {
|
||||
get {
|
||||
return ResourceManager.GetString("EditDesiredPropertiesFor", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit Configuration for {0}.
|
||||
/// </summary>
|
||||
|
@ -1375,6 +1456,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit Tags for {0}.
|
||||
/// </summary>
|
||||
public static string EditTagsFor {
|
||||
get {
|
||||
return ResourceManager.GetString("EditTagsFor", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to (empty).
|
||||
/// </summary>
|
||||
|
@ -1420,6 +1510,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to END TIME.
|
||||
/// </summary>
|
||||
public static string EndTimeHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("EndTimeHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Enter a Device ID.
|
||||
/// </summary>
|
||||
|
@ -1492,6 +1591,51 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Exported Devices.
|
||||
/// </summary>
|
||||
public static string ExportDevicesJobType {
|
||||
get {
|
||||
return ResourceManager.GetString("ExportDevicesJobType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to FAILED COUNT.
|
||||
/// </summary>
|
||||
public static string FailedCountHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedCountHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to DEVICES FAILED.
|
||||
/// </summary>
|
||||
public static string FailedCountSubHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedCountSubHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed Jobs in last 24 hours.
|
||||
/// </summary>
|
||||
public static string FailedJobsInLast24Hours {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedJobsInLast24Hours", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to cancel the job..
|
||||
/// </summary>
|
||||
public static string FailedToCancelJob {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToCancelJob", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to delete query..
|
||||
/// </summary>
|
||||
|
@ -1555,6 +1699,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failed to retrieve jobs.
|
||||
/// </summary>
|
||||
public static string FailedToRetrieveJobs {
|
||||
get {
|
||||
return ResourceManager.GetString("FailedToRetrieveJobs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An error occurred while retrieving the list of rules..
|
||||
/// </summary>
|
||||
|
@ -2041,6 +2194,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Imported Devices.
|
||||
/// </summary>
|
||||
public static string ImportDevicesJobType {
|
||||
get {
|
||||
return ResourceManager.GetString("ImportDevicesJobType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to IMSI: .
|
||||
/// </summary>
|
||||
|
@ -2257,6 +2419,42 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to JOB ID.
|
||||
/// </summary>
|
||||
public static string JobId {
|
||||
get {
|
||||
return ResourceManager.GetString("JobId", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to JOB NAME.
|
||||
/// </summary>
|
||||
public static string JobNameHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("JobNameHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Job Properties.
|
||||
/// </summary>
|
||||
public static string JobProperties {
|
||||
get {
|
||||
return ResourceManager.GetString("JobProperties", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to JOB DETAILS.
|
||||
/// </summary>
|
||||
public static string JobPropertiesPaneLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("JobPropertiesPaneLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Jobs.
|
||||
/// </summary>
|
||||
|
@ -2266,6 +2464,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Devices.
|
||||
/// </summary>
|
||||
public static string JobStatistics {
|
||||
get {
|
||||
return ResourceManager.GetString("JobStatistics", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Jobs.
|
||||
/// </summary>
|
||||
|
@ -2653,6 +2860,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Jobs.
|
||||
/// </summary>
|
||||
public static string NavigationMenuItemJobs {
|
||||
get {
|
||||
return ResourceManager.GetString("NavigationMenuItemJobs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Advanced.
|
||||
/// </summary>
|
||||
|
@ -2698,6 +2914,24 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No job selected.
|
||||
/// </summary>
|
||||
public static string NoJobSelected {
|
||||
get {
|
||||
return ResourceManager.GetString("NoJobSelected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No Job Selected.
|
||||
/// </summary>
|
||||
public static string NoJobSelectedLabel {
|
||||
get {
|
||||
return ResourceManager.GetString("NoJobSelectedLabel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Device {0} has no available methods.
|
||||
/// </summary>
|
||||
|
@ -2752,6 +2986,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to n/a.
|
||||
/// </summary>
|
||||
public static string NotAvilable {
|
||||
get {
|
||||
return ResourceManager.GetString("NotAvilable", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Data type: {0} (an integer between {1} and {2}).
|
||||
/// </summary>
|
||||
|
@ -2806,6 +3049,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to OPERATIONS.
|
||||
/// </summary>
|
||||
public static string OperationsHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("OperationsHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Operator.
|
||||
/// </summary>
|
||||
|
@ -2860,6 +3112,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to DEVICE PENDING.
|
||||
/// </summary>
|
||||
public static string PendingCountSubHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("PendingCountSubHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Platform.
|
||||
/// </summary>
|
||||
|
@ -3004,6 +3265,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to QUERY NAME.
|
||||
/// </summary>
|
||||
public static string QueryNameHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("QueryNameHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Read-Only.
|
||||
/// </summary>
|
||||
|
@ -3400,6 +3670,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to DEVICE RUNNING.
|
||||
/// </summary>
|
||||
public static string RunningCountSubHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("RunningCountSubHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Save.
|
||||
/// </summary>
|
||||
|
@ -3472,6 +3751,24 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invoke Method.
|
||||
/// </summary>
|
||||
public static string ScheduleDeviceMethodJobType {
|
||||
get {
|
||||
return ResourceManager.GetString("ScheduleDeviceMethodJobType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Scheduled Jobs.
|
||||
/// </summary>
|
||||
public static string ScheduledJobs {
|
||||
get {
|
||||
return ResourceManager.GetString("ScheduledJobs", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit Properties or Tags.
|
||||
/// </summary>
|
||||
|
@ -3562,6 +3859,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit Properties and Tags.
|
||||
/// </summary>
|
||||
public static string ScheduleUpdateTwinJobType {
|
||||
get {
|
||||
return ResourceManager.GetString("ScheduleUpdateTwinJobType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to SEARCH DEVICES.
|
||||
/// </summary>
|
||||
|
@ -3886,6 +4192,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to START TIME.
|
||||
/// </summary>
|
||||
public static string StartTimeHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("StartTimeHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Status.
|
||||
/// </summary>
|
||||
|
@ -3940,6 +4255,24 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to SUCCEEDED COUNT.
|
||||
/// </summary>
|
||||
public static string SucceedCountHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("SucceedCountHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to DEVICES SUCCEEDED.
|
||||
/// </summary>
|
||||
public static string SucceededCountSubHeader {
|
||||
get {
|
||||
return ResourceManager.GetString("SucceededCountSubHeader", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to There was a conflict while saving the data. Please verify the data and try again..
|
||||
/// </summary>
|
||||
|
@ -4120,6 +4453,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to retrieve job from service.
|
||||
/// </summary>
|
||||
public static string UnableToRetrieveJobFromService {
|
||||
get {
|
||||
return ResourceManager.GetString("UnableToRetrieveJobFromService", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to retrieve rule from service.
|
||||
/// </summary>
|
||||
|
@ -4138,6 +4480,15 @@ namespace GlobalResources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unknown Job Type.
|
||||
/// </summary>
|
||||
public static string UnknownJobType {
|
||||
get {
|
||||
return ResourceManager.GetString("UnknownJobType", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Update.
|
||||
/// </summary>
|
||||
|
|
|
@ -2016,4 +2016,160 @@
|
|||
<value>Select A Method</value>
|
||||
<comment>Dropdown unselected option label.</comment>
|
||||
</data>
|
||||
<data name="DeviceCountHeader" xml:space="preserve">
|
||||
<value>NO. OF DEVICES</value>
|
||||
<comment>Table column header.</comment>
|
||||
</data>
|
||||
<data name="EndTimeHeader" xml:space="preserve">
|
||||
<value>END TIME</value>
|
||||
<comment>Table column header.</comment>
|
||||
</data>
|
||||
<data name="FailedCountHeader" xml:space="preserve">
|
||||
<value>FAILED COUNT</value>
|
||||
<comment>Table column header.</comment>
|
||||
</data>
|
||||
<data name="JobNameHeader" xml:space="preserve">
|
||||
<value>JOB NAME</value>
|
||||
<comment>Table column header.</comment>
|
||||
</data>
|
||||
<data name="JobPropertiesPaneLabel" xml:space="preserve">
|
||||
<value>JOB DETAILS</value>
|
||||
<comment>Section heading.</comment>
|
||||
</data>
|
||||
<data name="NoJobSelectedLabel" xml:space="preserve">
|
||||
<value>No Job Selected</value>
|
||||
<comment>Informational text block.</comment>
|
||||
</data>
|
||||
<data name="OperationsHeader" xml:space="preserve">
|
||||
<value>OPERATIONS</value>
|
||||
<comment>Table column header.</comment>
|
||||
</data>
|
||||
<data name="QueryNameHeader" xml:space="preserve">
|
||||
<value>QUERY NAME</value>
|
||||
<comment>Table column header.</comment>
|
||||
</data>
|
||||
<data name="StartTimeHeader" xml:space="preserve">
|
||||
<value>START TIME</value>
|
||||
<comment>Table column header.</comment>
|
||||
</data>
|
||||
<data name="SucceedCountHeader" xml:space="preserve">
|
||||
<value>SUCCEEDED COUNT</value>
|
||||
<comment>Table column header.</comment>
|
||||
</data>
|
||||
<data name="NavigationMenuItemJobs" xml:space="preserve">
|
||||
<value>Jobs</value>
|
||||
<comment>Navigation menu item label.</comment>
|
||||
</data>
|
||||
<data name="ExportDevicesJobType" xml:space="preserve">
|
||||
<value>Exported Devices</value>
|
||||
<comment>Table cell.</comment>
|
||||
</data>
|
||||
<data name="ImportDevicesJobType" xml:space="preserve">
|
||||
<value>Imported Devices</value>
|
||||
<comment>Table cell.</comment>
|
||||
</data>
|
||||
<data name="ScheduleDeviceMethodJobType" xml:space="preserve">
|
||||
<value>Invoke Method</value>
|
||||
<comment>Table cell.</comment>
|
||||
</data>
|
||||
<data name="ScheduleUpdateTwinJobType" xml:space="preserve">
|
||||
<value>Edit Properties and Tags</value>
|
||||
<comment>Table cell.</comment>
|
||||
</data>
|
||||
<data name="UnknownJobType" xml:space="preserve">
|
||||
<value>Unknown Job Type</value>
|
||||
<comment>Table cell.</comment>
|
||||
</data>
|
||||
<data name="CancelJobAction" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
<comment>Button label.</comment>
|
||||
</data>
|
||||
<data name="CloneJobAction" xml:space="preserve">
|
||||
<value>Clone Job</value>
|
||||
<comment>Button label.</comment>
|
||||
</data>
|
||||
<data name="DeviceCountSubHeader" xml:space="preserve">
|
||||
<value>DEVICE IN JOB</value>
|
||||
<comment>Section subheader.</comment>
|
||||
</data>
|
||||
<data name="FailedCountSubHeader" xml:space="preserve">
|
||||
<value>DEVICES FAILED</value>
|
||||
<comment>Section subheader.</comment>
|
||||
</data>
|
||||
<data name="FailedToRetrieveJobs" xml:space="preserve">
|
||||
<value>Failed to retrieve jobs</value>
|
||||
<comment>Error message.</comment>
|
||||
</data>
|
||||
<data name="JobId" xml:space="preserve">
|
||||
<value>JOB ID</value>
|
||||
<comment>Read-only field label.</comment>
|
||||
</data>
|
||||
<data name="JobProperties" xml:space="preserve">
|
||||
<value>Job Properties</value>
|
||||
<comment>Section heading.</comment>
|
||||
</data>
|
||||
<data name="JobStatistics" xml:space="preserve">
|
||||
<value>Devices</value>
|
||||
<comment>Section heading.</comment>
|
||||
</data>
|
||||
<data name="NoJobSelected" xml:space="preserve">
|
||||
<value>No job selected</value>
|
||||
<comment>Error message.</comment>
|
||||
</data>
|
||||
<data name="PendingCountSubHeader" xml:space="preserve">
|
||||
<value>DEVICE PENDING</value>
|
||||
<comment>Section subheader.</comment>
|
||||
</data>
|
||||
<data name="RunningCountSubHeader" xml:space="preserve">
|
||||
<value>DEVICE RUNNING</value>
|
||||
<comment>Section subheader.</comment>
|
||||
</data>
|
||||
<data name="SucceededCountSubHeader" xml:space="preserve">
|
||||
<value>DEVICES SUCCEEDED</value>
|
||||
<comment>Section subheader.</comment>
|
||||
</data>
|
||||
<data name="UnableToRetrieveJobFromService" xml:space="preserve">
|
||||
<value>Unable to retrieve job from service</value>
|
||||
<comment>Error message.</comment>
|
||||
</data>
|
||||
<data name="FailedToCancelJob" xml:space="preserve">
|
||||
<value>Failed to cancel the job.</value>
|
||||
<comment>Error message.</comment>
|
||||
</data>
|
||||
<data name="ActiveJobs" xml:space="preserve">
|
||||
<value>Active Jobs</value>
|
||||
<comment>Read-only field label.</comment>
|
||||
</data>
|
||||
<data name="FailedJobsInLast24Hours" xml:space="preserve">
|
||||
<value>Failed Jobs in last 24 hours</value>
|
||||
<comment>Read-only field label.</comment>
|
||||
</data>
|
||||
<data name="NotAvilable" xml:space="preserve">
|
||||
<value>n/a</value>
|
||||
<comment>Error message.</comment>
|
||||
</data>
|
||||
<data name="ScheduledJobs" xml:space="preserve">
|
||||
<value>Scheduled Jobs</value>
|
||||
<comment>Read-only field label.</comment>
|
||||
</data>
|
||||
<data name="DeviceMethods" xml:space="preserve">
|
||||
<value>Methods</value>
|
||||
<comment>Hyperlink label.</comment>
|
||||
</data>
|
||||
<data name="AddDesiredProperty" xml:space="preserve">
|
||||
<value>Add Desired Property</value>
|
||||
<comment>Button label</comment>
|
||||
</data>
|
||||
<data name="AddTag" xml:space="preserve">
|
||||
<value>Add Tag</value>
|
||||
<comment>Button label</comment>
|
||||
</data>
|
||||
<data name="EditDesiredPropertiesFor" xml:space="preserve">
|
||||
<value>Edit Desired Properties for {0}</value>
|
||||
<comment>Page heading.</comment>
|
||||
</data>
|
||||
<data name="EditTagsFor" xml:space="preserve">
|
||||
<value>Edit Tags for {0}</value>
|
||||
<comment>Page heading.</comment>
|
||||
</data>
|
||||
</root>
|
|
@ -36,7 +36,8 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web
|
|||
"~/Scripts/bootstrap-datetimepicker.min.js"));
|
||||
|
||||
bundles.Add(new ScriptBundle("~/bundles/knockout").Include(
|
||||
"~/Scripts/knockout-{version}.js"));
|
||||
"~/Scripts/knockout-{version}.js",
|
||||
"~/Scripts/knockout.mapping-{version}.js"));
|
||||
|
||||
bundles.Add(new StyleBundle("~/content/css/vendor")
|
||||
.Include(
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="22.677px" height="22.677px" viewBox="0 0 22.677 22.677" enable-background="new 0 0 22.677 22.677" xml:space="preserve">
|
||||
<g>
|
||||
<polygon fill="#FFFFFF" points="12.108,22.535 2.5,19.041 2.5,4.774 12.108,8.268 "/>
|
||||
<polygon fill="#FFFFFF" points="6.552,2.457 6.552,5.495 7.483,5.833 7.483,3.788 15.229,6.604 15.229,18.888 12.812,18.009
|
||||
12.812,19 16.16,20.219 16.16,5.951 "/>
|
||||
<polygon fill="#FFFFFF" points="10.604,0.142 10.604,3.18 11.535,3.518 11.535,1.473 19.281,4.288 19.281,16.572 16.864,15.692
|
||||
16.864,16.685 20.212,17.902 20.212,3.636 "/>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 929 B |
|
@ -200,3 +200,47 @@ table#alertHistoryTable {
|
|||
font-weight: 400;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.dashboard_job_indicators {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.dashboard_job_indicator_container {
|
||||
background-color: @background-color;
|
||||
width: 33.333%;
|
||||
box-sizing: border-box;
|
||||
padding: 6px;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.dashboard_job_indicator_container--left {
|
||||
border-right: 6px solid @body-background-color;
|
||||
}
|
||||
|
||||
.dashboard_job_indicator_container--center {
|
||||
border-right: 6px solid @body-background-color;
|
||||
border-left: 6px solid @body-background-color;
|
||||
}
|
||||
|
||||
.dashboard_job_indicator_container--right {
|
||||
border-left: 6px solid @body-background-color;
|
||||
}
|
||||
|
||||
.dashboard_job_indicator_title {
|
||||
font-size: 0.9em;
|
||||
text-align: left;
|
||||
color: @text-color;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.dashboard_job_indicator_value {
|
||||
font-size: 3em;
|
||||
text-align: center;
|
||||
color: @text-color;
|
||||
padding-top: 0.2em;
|
||||
}
|
|
@ -67,6 +67,7 @@
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: @text-devicecolumnlist-color;
|
||||
}
|
||||
.device_list_columns_item:hover {
|
||||
background-color: @menu-item-hover-background-color;
|
||||
|
|
|
@ -13,25 +13,48 @@
|
|||
clear: both;
|
||||
}
|
||||
|
||||
.edit_form__labelHeader{
|
||||
.edit_form__labelsmall{
|
||||
color:gray;
|
||||
font-size:0.2em;
|
||||
display:block;
|
||||
line-height:100%;
|
||||
}
|
||||
|
||||
.primary__label {
|
||||
color: @primary-color;
|
||||
text-transform: none;
|
||||
margin: 10px 4px;
|
||||
}
|
||||
|
||||
.edit_form__labelHeader {
|
||||
color: @form-fieldset-textbox-label;
|
||||
font-size: .6em;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.values_editor__group input[type=checkbox]{
|
||||
.values_editor__group input[type=checkbox] {
|
||||
border: 1px solid @form-textbox-border;
|
||||
background-color: @form-textbox-background;
|
||||
margin: .25em 1em 0em 0;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
.values_editor__group td{
|
||||
text-align:center
|
||||
|
||||
.values_editor {
|
||||
margin: 15px 4px;
|
||||
}
|
||||
|
||||
.values_editor__group>span{
|
||||
font-size:.70em
|
||||
.values_editor__group{
|
||||
margin: 4px 0px;
|
||||
}
|
||||
|
||||
.values_editor__group td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.values_editor__group > span {
|
||||
font-size: .70em;
|
||||
}
|
||||
|
||||
.edit_form__text {
|
||||
|
@ -60,7 +83,7 @@
|
|||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.edit_form__textsmall{
|
||||
.edit_form__textsmall {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 50px;
|
||||
|
@ -72,9 +95,18 @@
|
|||
min-height: 30px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.non_float{
|
||||
float:none;
|
||||
display:inline;
|
||||
|
||||
.edit_form__link{
|
||||
font-size:.8em;
|
||||
}
|
||||
|
||||
.non_float {
|
||||
float: none;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.less_margin{
|
||||
margin:5px 0px;
|
||||
}
|
||||
|
||||
.edit_form__text:disabled {
|
||||
|
@ -96,3 +128,7 @@
|
|||
padding: 0 5px;
|
||||
width: 460px;
|
||||
}
|
||||
|
||||
.text_small{
|
||||
font-size:.8em;
|
||||
}
|
||||
|
|
|
@ -56,6 +56,11 @@
|
|||
background-size: 30px;
|
||||
}
|
||||
|
||||
.navigation__link--jobs {
|
||||
background-image: url("@{base-image-path}/icon_jobs.svg");
|
||||
background-size: 30px;
|
||||
}
|
||||
|
||||
.nav_advanced {
|
||||
background-image: url("@{base-image-path}/icon_advanced.svg");
|
||||
background-size:30px 30px;
|
||||
|
@ -69,6 +74,20 @@
|
|||
background-size: 20px 20px;
|
||||
}
|
||||
|
||||
.icon-add{
|
||||
filter: invert(100%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: black;
|
||||
z-index: 200;
|
||||
background-image: url(/Content/img/icon_add.svg);
|
||||
background-size: 20px 20px;
|
||||
display: inline-block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.nav_subnav {
|
||||
left: 95px;
|
||||
border-left-color: @background-color;
|
||||
|
|
|
@ -10,4 +10,5 @@
|
|||
@import "navbar.less";
|
||||
@import "navs.less";
|
||||
@import "grid.less";
|
||||
@import "component-animations.less";
|
||||
@import "component-animations.less";
|
||||
@import "popovers.less";
|
|
@ -18,7 +18,49 @@
|
|||
|
||||
|
||||
.glyphicon-time {
|
||||
&:before {
|
||||
&:before {
|
||||
content: "Time";
|
||||
}
|
||||
}
|
||||
|
||||
.glyphicon-chevron-up {
|
||||
|
||||
background-position:center;
|
||||
background-image: url("@{base-image-path}/button_page_next.svg");
|
||||
background-size: 20px 20px;
|
||||
background-repeat:no-repeat;
|
||||
-ms-transform: rotate(270deg); /* IE 9 */
|
||||
-webkit-transform: rotate(270deg); /* Chrome, Safari, Opera */
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
|
||||
.glyphicon-chevron-down {
|
||||
|
||||
background-position:center;
|
||||
background-image: url("@{base-image-path}/button_page_next.svg");
|
||||
background-size: 20px 20px;
|
||||
background-repeat:no-repeat;
|
||||
-ms-transform: rotate(90deg); /* IE 9 */
|
||||
-webkit-transform: rotate(90deg); /* Chrome, Safari, Opera */
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.glyphicon-chevron-left {
|
||||
|
||||
background-position:center;
|
||||
background-image: url("@{base-image-path}/button_page_next.svg");
|
||||
background-size: 20px 20px;
|
||||
background-repeat:no-repeat;
|
||||
-ms-transform: rotate(180deg); /* IE 9 */
|
||||
-webkit-transform: rotate(180deg); /* Chrome, Safari, Opera */
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.glyphicon-chevron-right {
|
||||
|
||||
background-position:center;
|
||||
background-repeat:no-repeat;
|
||||
background-image: url("@{base-image-path}/button_page_next.svg");
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
//
|
||||
// Popovers
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
.popover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: @zindex-popover;
|
||||
display: none;
|
||||
max-width: @popover-max-width;
|
||||
padding: 1px;
|
||||
// Our parent element can be arbitrary since popovers are by default inserted as a sibling of their target element.
|
||||
// So reset our font and text properties to avoid inheriting weird values.
|
||||
.reset-text();
|
||||
font-size: @font-size-base;
|
||||
|
||||
background-color: @popover-bg;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid @popover-fallback-border-color;
|
||||
border: 1px solid @popover-border-color;
|
||||
border-radius: @border-radius-large;
|
||||
.box-shadow(0 5px 10px rgba(0,0,0,.2));
|
||||
|
||||
// Offset the popover to account for the popover arrow
|
||||
&.top { margin-top: -@popover-arrow-width; }
|
||||
&.right { margin-left: @popover-arrow-width; }
|
||||
&.bottom { margin-top: @popover-arrow-width; }
|
||||
&.left { margin-left: -@popover-arrow-width; }
|
||||
}
|
||||
|
||||
.popover-title {
|
||||
margin: 0; // reset heading margin
|
||||
padding: 8px 14px;
|
||||
font-size: @font-size-base;
|
||||
background-color: @popover-title-bg;
|
||||
border-bottom: 1px solid darken(@popover-title-bg, 5%);
|
||||
border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0;
|
||||
}
|
||||
|
||||
.popover-content {
|
||||
padding: 9px 14px;
|
||||
}
|
||||
|
||||
// Arrows
|
||||
//
|
||||
// .arrow is outer, .arrow:after is inner
|
||||
|
||||
.popover > .arrow {
|
||||
&,
|
||||
&:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
}
|
||||
}
|
||||
.popover > .arrow {
|
||||
border-width: @popover-arrow-outer-width;
|
||||
}
|
||||
.popover > .arrow:after {
|
||||
border-width: @popover-arrow-width;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.popover {
|
||||
&.top > .arrow {
|
||||
left: 50%;
|
||||
margin-left: -@popover-arrow-outer-width;
|
||||
border-bottom-width: 0;
|
||||
border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback
|
||||
border-top-color: @popover-arrow-outer-color;
|
||||
bottom: -@popover-arrow-outer-width;
|
||||
&:after {
|
||||
content: " ";
|
||||
bottom: 1px;
|
||||
margin-left: -@popover-arrow-width;
|
||||
border-bottom-width: 0;
|
||||
border-top-color: @popover-arrow-color;
|
||||
}
|
||||
}
|
||||
&.right > .arrow {
|
||||
top: 50%;
|
||||
left: -@popover-arrow-outer-width;
|
||||
margin-top: -@popover-arrow-outer-width;
|
||||
border-left-width: 0;
|
||||
border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback
|
||||
border-right-color: @popover-arrow-outer-color;
|
||||
&:after {
|
||||
content: " ";
|
||||
left: 1px;
|
||||
bottom: -@popover-arrow-width;
|
||||
border-left-width: 0;
|
||||
border-right-color: @popover-arrow-color;
|
||||
}
|
||||
}
|
||||
&.bottom > .arrow {
|
||||
left: 50%;
|
||||
margin-left: -@popover-arrow-outer-width;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback
|
||||
border-bottom-color: @popover-arrow-outer-color;
|
||||
top: -@popover-arrow-outer-width;
|
||||
&:after {
|
||||
content: " ";
|
||||
top: 1px;
|
||||
margin-left: -@popover-arrow-width;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: @popover-arrow-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.left > .arrow {
|
||||
top: 50%;
|
||||
right: -@popover-arrow-outer-width;
|
||||
margin-top: -@popover-arrow-outer-width;
|
||||
border-right-width: 0;
|
||||
border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback
|
||||
border-left-color: @popover-arrow-outer-color;
|
||||
&:after {
|
||||
content: " ";
|
||||
right: 1px;
|
||||
border-right-width: 0;
|
||||
border-left-color: @popover-arrow-color;
|
||||
bottom: -@popover-arrow-width;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -596,6 +596,38 @@ table.dataTable thead {
|
|||
background-image:url(/Content/img/icon_status_null.svg);
|
||||
}
|
||||
|
||||
.table_status.status_unknown {
|
||||
background-image:url(/Content/img/icon_status_disabled.svg);
|
||||
}
|
||||
|
||||
.table_status.status_enqueued {
|
||||
background-image:url(/Content/img/icon_status_enabled.svg);
|
||||
}
|
||||
|
||||
.table_status.status_running {
|
||||
background-image:url(/Content/img/icon_status_enabled.svg);
|
||||
}
|
||||
|
||||
.table_status.status_completed {
|
||||
background-image:url(/Content/img/icon_status_enabled.svg);
|
||||
}
|
||||
|
||||
.table_status.status_failed {
|
||||
background-image:url(/Content/img/icon_status_critical.svg);
|
||||
}
|
||||
|
||||
.table_status.status_cancelled {
|
||||
background-image:url(/Content/img/icon_status_disabled.svg);
|
||||
}
|
||||
|
||||
.table_status.status_scheduled {
|
||||
background-image:url(/Content/img/icon_status_enabled.svg);
|
||||
}
|
||||
|
||||
.table_status.status_queued {
|
||||
background-image:url(/Content/img/icon_status_enabled.svg);
|
||||
}
|
||||
|
||||
.table_alertHistory_issueType{
|
||||
min-width: 90px;
|
||||
}
|
||||
|
|
|
@ -216,6 +216,10 @@ main.main_no_nav {
|
|||
background-image: url("/Content/img/icon_actions.svg");
|
||||
background-size: 30px;
|
||||
}
|
||||
.navigation__link--jobs {
|
||||
background-image: url("/Content/img/icon_jobs.svg");
|
||||
background-size: 30px;
|
||||
}
|
||||
.nav_advanced {
|
||||
background-image: url("/Content/img/icon_advanced.svg");
|
||||
background-size: 30px 30px;
|
||||
|
@ -227,6 +231,16 @@ main.main_no_nav {
|
|||
background-image: url("/Content/img/icon_add.svg");
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
.icon-add {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: darkgray;
|
||||
z-index: 200;
|
||||
background-image: url("/Content/img/icon_add.svg");
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
.nav_subnav {
|
||||
left: 95px;
|
||||
border-left-color: white;
|
||||
|
@ -654,10 +668,22 @@ nav .selected {
|
|||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
.edit_form__labelsmall {
|
||||
color: gray;
|
||||
font-size: 0.2em;
|
||||
display: block;
|
||||
line-height: 100%;
|
||||
}
|
||||
.primary__label {
|
||||
color: #212528;
|
||||
text-transform: none;
|
||||
margin: 10px 4px;
|
||||
}
|
||||
.edit_form__labelHeader {
|
||||
color: gray;
|
||||
font-size: 0.6em;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
}
|
||||
.values_editor__group input[type=checkbox] {
|
||||
border: 1px solid #999999;
|
||||
|
@ -666,6 +692,12 @@ nav .selected {
|
|||
height: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
.values_editor {
|
||||
margin: 15px 4px;
|
||||
}
|
||||
.values_editor__group {
|
||||
margin: 4px 0px;
|
||||
}
|
||||
.values_editor__group td {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -708,10 +740,16 @@ nav .selected {
|
|||
min-height: 30px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.edit_form__link {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.non_float {
|
||||
float: none;
|
||||
display: inline;
|
||||
}
|
||||
.less_margin {
|
||||
margin: 5px 0px;
|
||||
}
|
||||
.edit_form__text:disabled {
|
||||
color: #b3b3b3;
|
||||
background-color: #f2f2f2;
|
||||
|
@ -730,6 +768,9 @@ nav .selected {
|
|||
padding: 0 5px;
|
||||
width: 460px;
|
||||
}
|
||||
.text_small {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.remove_form {
|
||||
padding: 15px 30px;
|
||||
background-color: white;
|
||||
|
@ -1052,6 +1093,43 @@ table#alertHistoryTable tr.selectedAlert {
|
|||
font-weight: 400;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.dashboard_job_indicators {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
padding-top: 6px;
|
||||
}
|
||||
.dashboard_job_indicator_container {
|
||||
background-color: white;
|
||||
width: 33.333%;
|
||||
box-sizing: border-box;
|
||||
padding: 6px;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
height: 150px;
|
||||
}
|
||||
.dashboard_job_indicator_container--left {
|
||||
border-right: 6px solid #f2f2f2;
|
||||
}
|
||||
.dashboard_job_indicator_container--center {
|
||||
border-right: 6px solid #f2f2f2;
|
||||
border-left: 6px solid #f2f2f2;
|
||||
}
|
||||
.dashboard_job_indicator_container--right {
|
||||
border-left: 6px solid #f2f2f2;
|
||||
}
|
||||
.dashboard_job_indicator_title {
|
||||
font-size: 0.9em;
|
||||
text-align: left;
|
||||
color: #333333;
|
||||
height: 2em;
|
||||
}
|
||||
.dashboard_job_indicator_value {
|
||||
font-size: 3em;
|
||||
text-align: center;
|
||||
color: #333333;
|
||||
padding-top: 0.2em;
|
||||
}
|
||||
.search_container_closed {
|
||||
position: relative;
|
||||
float: left;
|
||||
|
@ -1384,6 +1462,7 @@ table#alertHistoryTable tr.selectedAlert {
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #606060;
|
||||
}
|
||||
.device_list_columns_item:hover {
|
||||
background-color: #b2ebfc;
|
||||
|
@ -4329,6 +4408,137 @@ tbody.collapse.in {
|
|||
-webkit-transition-timing-function: ease;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
.popover {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1060;
|
||||
display: none;
|
||||
max-width: 276px;
|
||||
padding: 1px;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
letter-spacing: normal;
|
||||
line-break: auto;
|
||||
line-height: 1.428571429;
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
text-decoration: none;
|
||||
text-shadow: none;
|
||||
text-transform: none;
|
||||
white-space: normal;
|
||||
word-break: normal;
|
||||
word-spacing: normal;
|
||||
word-wrap: normal;
|
||||
font-size: 14px;
|
||||
background-color: white;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #cccccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 6px;
|
||||
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.popover.top {
|
||||
margin-top: -10px;
|
||||
}
|
||||
.popover.right {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.popover.bottom {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.popover.left {
|
||||
margin-left: -10px;
|
||||
}
|
||||
.popover-title {
|
||||
margin: 0;
|
||||
padding: 8px 14px;
|
||||
font-size: 14px;
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
.popover-content {
|
||||
padding: 9px 14px;
|
||||
}
|
||||
.popover > .arrow,
|
||||
.popover > .arrow:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
}
|
||||
.popover > .arrow {
|
||||
border-width: 11px;
|
||||
}
|
||||
.popover > .arrow:after {
|
||||
border-width: 10px;
|
||||
content: "";
|
||||
}
|
||||
.popover.top > .arrow {
|
||||
left: 50%;
|
||||
margin-left: -11px;
|
||||
border-bottom-width: 0;
|
||||
border-top-color: #999999;
|
||||
border-top-color: rgba(0, 0, 0, 0.25);
|
||||
bottom: -11px;
|
||||
}
|
||||
.popover.top > .arrow:after {
|
||||
content: " ";
|
||||
bottom: 1px;
|
||||
margin-left: -10px;
|
||||
border-bottom-width: 0;
|
||||
border-top-color: white;
|
||||
}
|
||||
.popover.right > .arrow {
|
||||
top: 50%;
|
||||
left: -11px;
|
||||
margin-top: -11px;
|
||||
border-left-width: 0;
|
||||
border-right-color: #999999;
|
||||
border-right-color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.popover.right > .arrow:after {
|
||||
content: " ";
|
||||
left: 1px;
|
||||
bottom: -10px;
|
||||
border-left-width: 0;
|
||||
border-right-color: white;
|
||||
}
|
||||
.popover.bottom > .arrow {
|
||||
left: 50%;
|
||||
margin-left: -11px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #999999;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.25);
|
||||
top: -11px;
|
||||
}
|
||||
.popover.bottom > .arrow:after {
|
||||
content: " ";
|
||||
top: 1px;
|
||||
margin-left: -10px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: white;
|
||||
}
|
||||
.popover.left > .arrow {
|
||||
top: 50%;
|
||||
right: -11px;
|
||||
margin-top: -11px;
|
||||
border-right-width: 0;
|
||||
border-left-color: #999999;
|
||||
border-left-color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.popover.left > .arrow:after {
|
||||
content: " ";
|
||||
right: 1px;
|
||||
border-right-width: 0;
|
||||
border-left-color: white;
|
||||
bottom: -10px;
|
||||
}
|
||||
/*!
|
||||
* Datetimepicker for Bootstrap 3
|
||||
* version : 4.17.43
|
||||
|
@ -4719,6 +4929,45 @@ tbody.collapse.in {
|
|||
.glyphicon-time:before {
|
||||
content: "Time";
|
||||
}
|
||||
.glyphicon-chevron-up {
|
||||
background-position: center;
|
||||
background-image: url("/Content/img/button_page_next.svg");
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
-ms-transform: rotate(270deg);
|
||||
/* IE 9 */
|
||||
-webkit-transform: rotate(270deg);
|
||||
/* Chrome, Safari, Opera */
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
.glyphicon-chevron-down {
|
||||
background-position: center;
|
||||
background-image: url("/Content/img/button_page_next.svg");
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
-ms-transform: rotate(90deg);
|
||||
/* IE 9 */
|
||||
-webkit-transform: rotate(90deg);
|
||||
/* Chrome, Safari, Opera */
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.glyphicon-chevron-left {
|
||||
background-position: center;
|
||||
background-image: url("/Content/img/button_page_next.svg");
|
||||
background-size: 20px 20px;
|
||||
background-repeat: no-repeat;
|
||||
-ms-transform: rotate(180deg);
|
||||
/* IE 9 */
|
||||
-webkit-transform: rotate(180deg);
|
||||
/* Chrome, Safari, Opera */
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.glyphicon-chevron-right {
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("/Content/img/button_page_next.svg");
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
.navbar {
|
||||
border-radius: 0px;
|
||||
margin-bottom: 0px;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
@text-devicedetail-head-background-color:#999;
|
||||
@text-divider-fieldset-color:#808080;
|
||||
@text-job-lastupdated-color:lightgray;
|
||||
@text-devicecolumnlist-color:#606060;
|
||||
|
||||
@font-size-property-value: 1em;
|
||||
@font-size-grid-subhead-detail-label: 0.75em;
|
||||
|
|
|
@ -346,6 +346,18 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.
|
|||
}));
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.EditDeviceMetadata)]
|
||||
public ActionResult EditDesiredProperties(string deviceId)
|
||||
{
|
||||
return View(new EditDevicePropertiesModel() { DeviceId = deviceId });
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.EditDeviceMetadata)]
|
||||
public ActionResult EditTags(string deviceId)
|
||||
{
|
||||
return View(new EditDevicePropertiesModel() { DeviceId = deviceId });
|
||||
}
|
||||
|
||||
private static IEnumerable<DevicePropertyValueModel> ApplyDevicePropertyOrdering(IEnumerable<DevicePropertyValueModel> devicePropertyModels)
|
||||
{
|
||||
Debug.Assert(
|
||||
|
@ -414,5 +426,7 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.
|
|||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Mvc;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infrastructure.Repository;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Models;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Security;
|
||||
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Controllers
|
||||
{
|
||||
public class JobController : Controller
|
||||
|
@ -18,6 +20,20 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.
|
|||
_iotHubDeviceManager = iotHubDeviceManager;
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.ViewJobs)]
|
||||
public ActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[RequirePermission(Permission.ViewJobs)]
|
||||
public async Task<ActionResult> GetJobProperties(string jobId)
|
||||
{
|
||||
var jobResponse = await _iotHubDeviceManager.GetJobResponseByJobIdAsync(jobId);
|
||||
return PartialView("_JobProperties", new DeviceJobModel(jobResponse));
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.ManageJobs)]
|
||||
public async Task<ActionResult> ScheduleJob(string queryName)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
using GlobalResources;
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Extensions
|
||||
{
|
||||
static public class JobTypeExtension
|
||||
{
|
||||
static public string LocalizedString(this JobType jobType)
|
||||
{
|
||||
switch (jobType)
|
||||
{
|
||||
case JobType.ExportDevices:
|
||||
return Strings.ExportDevicesJobType;
|
||||
case JobType.ImportDevices:
|
||||
return Strings.ImportDevicesJobType;
|
||||
case JobType.ScheduleDeviceMethod:
|
||||
return Strings.ScheduleDeviceMethodJobType;
|
||||
case JobType.ScheduleUpdateTwin:
|
||||
return Strings.ScheduleUpdateTwinJobType;
|
||||
default:
|
||||
return Strings.UnknownJobType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Helpers;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Models
|
||||
{
|
||||
public class DeviceJobModel
|
||||
{
|
||||
public DeviceJobModel(JobResponse jobResponse)
|
||||
{
|
||||
Status = jobResponse.Status;
|
||||
JobId = jobResponse.JobId;
|
||||
DeviceCount = jobResponse.DeviceJobStatistics?.DeviceCount;
|
||||
SucceededCount = jobResponse.DeviceJobStatistics?.SucceededCount;
|
||||
FailedCount = jobResponse.DeviceJobStatistics?.FailedCount;
|
||||
PendingCount = jobResponse.DeviceJobStatistics?.PendingCount;
|
||||
RunningCount = jobResponse.DeviceJobStatistics?.RunningCount;
|
||||
OperationType = jobResponse.Type.LocalizedString();
|
||||
StartTime = jobResponse.StartTimeUtc;
|
||||
EndTime = jobResponse.EndTimeUtc;
|
||||
}
|
||||
|
||||
public string JobId { get; set; }
|
||||
public string JobName { get; set; }
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public JobStatus Status { get; set; }
|
||||
public string QueryName { get; set; }
|
||||
public string OperationType { get; set; }
|
||||
public DateTime? StartTime { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public int? DeviceCount { get; set; }
|
||||
public int? SucceededCount { get; set; }
|
||||
public int? FailedCount { get; set; }
|
||||
public int? PendingCount { get; set; }
|
||||
public int? RunningCount { get; set; }
|
||||
}
|
||||
}
|
|
@ -52,6 +52,15 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.
|
|||
MinimumPermission = Permission.ViewActions,
|
||||
},
|
||||
new NavigationMenuItem
|
||||
{
|
||||
Text = Strings.NavigationMenuItemJobs,
|
||||
Action = "Index",
|
||||
Controller = "Job",
|
||||
Selected = false,
|
||||
Class = "navigation__link--jobs",
|
||||
MinimumPermission = Permission.ViewJobs,
|
||||
},
|
||||
new NavigationMenuItem
|
||||
{
|
||||
Text = Strings.NavigationMenuItemsAdvanced,
|
||||
Action = "CellularConn",
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
IoTApp.createModule(
|
||||
'IoTApp.Dashboard.JobIndicators',
|
||||
function initJobIndicators() {
|
||||
'use strict';
|
||||
|
||||
var init = function init(jobIndicatorsSettings) {
|
||||
loadJobDefinitions();
|
||||
};
|
||||
|
||||
var loadJobDefinitions = function () {
|
||||
$.ajax({
|
||||
url: '/api/v1/jobIndicators/definitions',
|
||||
type: 'GET',
|
||||
cache: false,
|
||||
success: function (result) {
|
||||
var $container = $('.dashboard_job_indicators');
|
||||
var $div;
|
||||
|
||||
$.each(result, function (index, indicator) {
|
||||
if (index == 0) {
|
||||
$div = $('<div>', {
|
||||
'class': 'dashboard_job_indicator_container dashboard_job_indicator_container--left'
|
||||
});
|
||||
} else if (index == result.length - 1) {
|
||||
$div = $('<div>', {
|
||||
'class': 'dashboard_job_indicator_container dashboard_job_indicator_container--right'
|
||||
});
|
||||
} else {
|
||||
$div = $('<div>', {
|
||||
'class': 'dashboard_job_indicator_container dashboard_job_indicator_container--center'
|
||||
});
|
||||
}
|
||||
|
||||
$('<p>', {
|
||||
'class': 'dashboard_job_indicator_title',
|
||||
text: indicator.title
|
||||
}).appendTo($div);
|
||||
|
||||
$('<p>', {
|
||||
'data-indicator': indicator.id,
|
||||
'class': 'dashboard_job_indicator_value',
|
||||
text: '0'
|
||||
}).appendTo($div);
|
||||
|
||||
$container.append($div);
|
||||
});
|
||||
|
||||
updateJobIndicatorsData();
|
||||
},
|
||||
error: function () {
|
||||
setTimeout(loadJobDefinitions, 60000)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var updateJobIndicatorsData = function updateJobIndicatorsData() {
|
||||
var indicators = $(".dashboard_job_indicator_value").map(function () {
|
||||
return $(this).attr('data-indicator');
|
||||
}).get();
|
||||
|
||||
$.ajax({
|
||||
url: '/api/v1/jobIndicators/values',
|
||||
type: 'GET',
|
||||
data: { 'indicators': indicators },
|
||||
dataType: 'json',
|
||||
cache: false,
|
||||
success: function (result) {
|
||||
if (result.length == $(".dashboard_job_indicator_value").length) {
|
||||
$(".dashboard_job_indicator_value").each(function (index) {
|
||||
$(this).text(result[index]);
|
||||
});
|
||||
}
|
||||
setTimeout(updateJobIndicatorsData, 600000);
|
||||
},
|
||||
error: function () {
|
||||
setTimeout(updateJobIndicatorsData, 600000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
init: init
|
||||
};
|
||||
},
|
||||
[jQuery, resources]);
|
|
@ -379,6 +379,7 @@
|
|||
"class": 'button_base devicelist_toolbar_button devicelist_toolbar_button_gray',
|
||||
text: resources.editColumns,
|
||||
click: function () {
|
||||
unselectAllRows();
|
||||
showDetails();
|
||||
IoTApp.DeviceListColumns.init();
|
||||
}
|
||||
|
@ -386,9 +387,10 @@
|
|||
|
||||
$('<button/>', {
|
||||
id: 'scheduleJobButton',
|
||||
"class": 'button_base devicelist_toolbar_button',
|
||||
"class": 'button_base devicelist_toolbar_button devicelist_toolbar_button_gray',
|
||||
text: resources.scheduleJob,
|
||||
click: function () {
|
||||
unselectAllRows();
|
||||
showDetails();
|
||||
self.deviceDetails.scheduleJob($('#queryNameBox').val());
|
||||
}
|
||||
|
@ -897,6 +899,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
var unselectAllRows = function () {
|
||||
self.dataTable.$(".selected").removeClass("selected");
|
||||
IoTApp.Helpers.DeviceIdState.saveDeviceIdToCookie('');
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
toggleDetails: toggleDetails,
|
||||
|
|
|
@ -85,7 +85,10 @@
|
|||
|
||||
$('.device_list_columns_button_container').appendTo($('.details_grid'));
|
||||
|
||||
IoTApp.Controls.NameSelector.create($('.name_selector__text'), { type: IoTApp.Controls.NameSelector.NameListType.tag | IoTApp.Controls.NameSelector.NameListType.property });
|
||||
IoTApp.Controls.NameSelector.create($('.name_selector__text'), {
|
||||
type: IoTApp.Controls.NameSelector.NameListType.tag | IoTApp.Controls.NameSelector.NameListType.property,
|
||||
position: IoTApp.Controls.NameSelector.Position.rightBottom
|
||||
});
|
||||
applyFilters();
|
||||
$('.name_add__button').click(addColumn);
|
||||
$('.name_selector__text').keyup(function (e) {
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
IoTApp.createModule('IoTApp.EditDesiredProperties', (function () {
|
||||
"use strict";
|
||||
|
||||
var self = this;
|
||||
|
||||
var init = function (deviceId) {
|
||||
self.viewModel = new viewModel(deviceId)
|
||||
IoTApp.Controls.NameSelector.loadNameList({ type: IoTApp.Controls.NameSelector.NameListType.desiredProperty }, self.viewModel.cachepropertyList);
|
||||
|
||||
ko.applyBindings(self.viewModel, $("#content").get(0));
|
||||
}
|
||||
|
||||
var viewModel = function (deviceId) {
|
||||
var self = this;
|
||||
var defaultData = [
|
||||
{
|
||||
"key": "",
|
||||
"value": {
|
||||
"value": "",
|
||||
"lastUpdated": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
this.properties = ko.mapping.fromJS(defaultData);
|
||||
this.backButtonClicked = function () {
|
||||
location.href = resources.redirectUrl;
|
||||
}
|
||||
this.propertieslist = {};
|
||||
|
||||
this.createEmptyPropertyIfNeeded = function (property) {
|
||||
if (self.properties.indexOf(property) == self.properties().length - 1 && property.value.value != "") {
|
||||
self.properties.push({ 'key': "",'value':{'lastUpdated':"",'value': ""}})
|
||||
}
|
||||
}
|
||||
|
||||
this.makeproplist = function (elem, index, data) {
|
||||
self.refreshnamecontrol();
|
||||
}
|
||||
|
||||
this.cachepropertyList = function (namelist) {
|
||||
self.propertieslist = namelist;
|
||||
self.refreshnamecontrol();
|
||||
}
|
||||
|
||||
this.refreshnamecontrol = function () {
|
||||
jQuery('.edit_form__texthalf.edit_form__propertiesComboBox').each(function () {
|
||||
IoTApp.Controls.NameSelector.create(jQuery(this), { type: IoTApp.Controls.NameSelector.NameListType.properties }, self.propertieslist);
|
||||
});
|
||||
}
|
||||
|
||||
this.fromNowValue = function (lastupdate,locale) {
|
||||
return moment(lastupdate()).locale(locale).fromNow();
|
||||
}
|
||||
|
||||
this.formSubmit = function () {
|
||||
$("#loadingElement").show();
|
||||
$.ajax({
|
||||
url: '/api/v1/devices/' + deviceId + '/twin/desired',
|
||||
type: 'PUT',
|
||||
data: ko.mapping.toJS(self.properties),
|
||||
contentType:"application/json",
|
||||
success: function (result) {
|
||||
location.href = resources.redirectUrl;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/api/v1/devices/' + deviceId + '/twin/desired',
|
||||
type: 'GET',
|
||||
success: function (result) {
|
||||
//self.properties = ko.mapping.fromJS(result.data);
|
||||
ko.mapping.fromJS(result.data, self.properties);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
init:init
|
||||
}
|
||||
}), [jQuery, resources]);
|
|
@ -0,0 +1,30 @@
|
|||
IoTApp.createModule('IoTApp.EditDeviceProperties', (function () {
|
||||
"use strict";
|
||||
|
||||
var self = this;
|
||||
|
||||
var init = function () {
|
||||
self.backButton = $(".header_main__button_back");
|
||||
self.backButton.show();
|
||||
self.backButton.off("click").click(backButtonClicked);
|
||||
}
|
||||
|
||||
var backButtonClicked = function() {
|
||||
location.href = resources.redirectUrl;
|
||||
}
|
||||
|
||||
$("form").submit(function () {
|
||||
$("#loadingElement").show();
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
init: init
|
||||
}
|
||||
}), [jQuery, resources]);
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
|
||||
IoTApp.EditDeviceProperties.init();
|
||||
});
|
|
@ -74,4 +74,19 @@ ko.bindingHandlers.dateTimePicker = {
|
|||
picker.date(koDate);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function getCulture() {
|
||||
var name = "_culture=";
|
||||
var ca = document.cookie.split(';');
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return window.navigator.language;
|
||||
}
|
|
@ -0,0 +1,321 @@
|
|||
IoTApp.createModule('IoTApp.JobIndex', function () {
|
||||
"use strict";
|
||||
|
||||
var self = this;
|
||||
var init = function (jobProperties) {
|
||||
self.jobProperties = jobProperties;
|
||||
self.dataTableContainer = $('#jobTable');
|
||||
self.jobGrid = $(".details_grid");
|
||||
self.jobGridClosed = $(".details_grid_closed");
|
||||
self.jobGridContainer = $(".grid_container");
|
||||
self.buttonDetailsGrid = $(".button_details_grid");
|
||||
self.reloadGrid = this.reloadGrid;
|
||||
|
||||
_initializeDatatable();
|
||||
|
||||
self.buttonDetailsGrid.on("click", function () {
|
||||
toggleProperties();
|
||||
fixHeights();
|
||||
});
|
||||
|
||||
$(window).on("load", function () {
|
||||
fixHeights();
|
||||
setGridWidth();
|
||||
});
|
||||
|
||||
$(window).on("resize", function () {
|
||||
fixHeights();
|
||||
setGridWidth();
|
||||
});
|
||||
}
|
||||
|
||||
var _selectRowFromDataTable = function (row) {
|
||||
var rowData = row.data();
|
||||
if (rowData != null) {
|
||||
self.dataTable.$(".selected").removeClass("selected");
|
||||
row.nodes().to$().addClass("selected");
|
||||
self.selectedRow = row.index();
|
||||
self.selectedJobId = rowData["jobId"];
|
||||
self.jobProperties.init(rowData["jobId"], self.reloadGrid);
|
||||
}
|
||||
}
|
||||
|
||||
var _setDefaultRowAndPage = function () {
|
||||
if (self.isDefaultJobAvailable === true) {
|
||||
var node = self.dataTable.row(self.defaultSelectedRow);
|
||||
_selectRowFromDataTable(node);
|
||||
} else {
|
||||
// if selected job is no longer displayed in grid, then close the details pane
|
||||
closeAndClearProperties();
|
||||
}
|
||||
}
|
||||
|
||||
var changeJobStatus = function () {
|
||||
var tableStatus = self.dataTable;
|
||||
resources.allJobStatus.forEach(function (status) {
|
||||
var selector = ".table_status:contains('" + status.toLowerCase() + "')";
|
||||
var cells_status = tableStatus.cells(selector).nodes();
|
||||
var className = 'status_' + status.toLowerCase();
|
||||
$(cells_status).addClass(className);
|
||||
$(cells_status).html(status);
|
||||
});
|
||||
}
|
||||
|
||||
var _initializeDatatable = function () {
|
||||
var onTableDrawn = function () {
|
||||
changeJobStatus();
|
||||
_setDefaultRowAndPage();
|
||||
|
||||
var pagingDiv = $('#jobTable_paginate');
|
||||
if (pagingDiv) {
|
||||
if (self.dataTable.page.info().pages > 1) {
|
||||
$(pagingDiv).show();
|
||||
} else {
|
||||
$(pagingDiv).hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var onTableRowClicked = function () {
|
||||
_selectRowFromDataTable(self.dataTable.row(this));
|
||||
}
|
||||
|
||||
var htmlEncode = function (data) {
|
||||
// "trick" to HTML encode data from JS--essentially dip it in a <div> and pull it out again
|
||||
return data ? $('<div/>').text(data).html() : null;
|
||||
}
|
||||
|
||||
//$.fn.dataTable.ext.legacy.ajax = true;
|
||||
self.dataTable = self.dataTableContainer.DataTable({
|
||||
"autoWidth": false,
|
||||
"pageLength": 20,
|
||||
"displayStart": 0,
|
||||
"pagingType": "simple",
|
||||
"paging": true,
|
||||
"lengthChange": false,
|
||||
"processing": false,
|
||||
"serverSide": false,
|
||||
"dom": "<'dataTables_header'ip>lrt?",
|
||||
"ajax": onDataTableAjaxCalled,
|
||||
"language": {
|
||||
"info": resources.jobsList + " (_TOTAL_)",
|
||||
"paginate": {
|
||||
"previous": resources.previousPaging,
|
||||
"next": resources.nextPaging
|
||||
}
|
||||
},
|
||||
"columns": [
|
||||
{
|
||||
"data": "status",
|
||||
"mRender": function (data) {
|
||||
return htmlEncode(data);
|
||||
},
|
||||
"name": "status"
|
||||
},
|
||||
{
|
||||
"data": "jobName",
|
||||
"mRender": function (data) {
|
||||
return htmlEncode(data);
|
||||
},
|
||||
"name": "jobName"
|
||||
},
|
||||
{
|
||||
"data": "queryName",
|
||||
"mRender": function (data) {
|
||||
return htmlEncode(data);
|
||||
},
|
||||
"name": "queryName"
|
||||
},
|
||||
{
|
||||
"data": "operationType",
|
||||
"mRender": function (data) {
|
||||
return htmlEncode(data);
|
||||
},
|
||||
"name": "operations"
|
||||
},
|
||||
{
|
||||
"data": "startTime",
|
||||
"mRender": function (data) {
|
||||
return IoTApp.Helpers.Dates.localizeDate(data, 'L LTS');
|
||||
},
|
||||
"name": "startTime"
|
||||
},
|
||||
{
|
||||
"data": "endTime",
|
||||
"mRender": function (data) {
|
||||
return IoTApp.Helpers.Dates.localizeDate(data, 'L LTS');
|
||||
},
|
||||
"name": "endTime"
|
||||
},
|
||||
{
|
||||
"data": "deviceCount",
|
||||
"mRender": function (data) {
|
||||
return htmlEncode(data);
|
||||
},
|
||||
"name": "deviceCount"
|
||||
},
|
||||
{
|
||||
"data": "succeededCount",
|
||||
"mRender": function (data) {
|
||||
return htmlEncode(data);
|
||||
},
|
||||
"name": "succeededCount"
|
||||
},
|
||||
{
|
||||
"data": "failedCount",
|
||||
"mRender": function (data) {
|
||||
return htmlEncode(data);
|
||||
},
|
||||
"name": "failedCount"
|
||||
}
|
||||
],
|
||||
"columnDefs": [
|
||||
{ className: "table_status", targets: [0] },
|
||||
{ searchable: true, targets: [1] }
|
||||
],
|
||||
"order": [[2, "asc"]]
|
||||
});
|
||||
|
||||
self.dataTableContainer.on("draw.dt", onTableDrawn);
|
||||
self.dataTableContainer.on("error.dt", function (e, settings, techNote, message) {
|
||||
IoTApp.Helpers.Dialog.displayError(resources.unableToRetrieveJobFromService);
|
||||
});
|
||||
|
||||
self.dataTableContainer.find("tbody").delegate("tr", "click", onTableRowClicked);
|
||||
|
||||
/* DataTables workaround - reset progress animation display for use with DataTables api */
|
||||
$('.loader_container').css('display', 'block');
|
||||
$('.loader_container').css('background-color', '#ffffff');
|
||||
self.dataTableContainer.on('processing.dt', function (e, settings, processing) {
|
||||
$('.loader_container').css('display', processing ? 'block' : 'none');
|
||||
_setGridContainerScrollPositionIfRowIsSelected();
|
||||
});
|
||||
|
||||
var _setGridContainerScrollPositionIfRowIsSelected = function () {
|
||||
if ($("tbody .selected").length > 0) {
|
||||
$('.grid_container')[0].scrollTop = $("tbody .selected").offset().top - $('.grid_container').offset().top - 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var onDataTableAjaxCalled = function (data, fnCallback) {
|
||||
|
||||
// create a success callback to track the selected row, and then call the DataTables callback
|
||||
var successCallback = function (json, a, b) {
|
||||
if (self.selectedJobId) {
|
||||
// iterate through the data before passing it on to grid, and try to
|
||||
// find and save the selected JobId value
|
||||
|
||||
// reset this value each time
|
||||
self.isDefaultJobAvailable = false;
|
||||
|
||||
for (var i = 0, len = json.data.length; i < len; ++i) {
|
||||
var data = json.data[i];
|
||||
if (data &&
|
||||
data.jobId === self.selectedJobId) {
|
||||
self.defaultSelectedRow = i;
|
||||
self.isDefaultJobAvailable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pass data on to grid (otherwise grid will spin forever)
|
||||
fnCallback(json, a, b);
|
||||
};
|
||||
|
||||
self.getJobList = $.ajax({
|
||||
"dataType": 'json',
|
||||
'type': 'GET',
|
||||
'url': '/api/v1/jobs',
|
||||
'cache': false,
|
||||
'data': data,
|
||||
'success': successCallback
|
||||
});
|
||||
|
||||
self.getJobList.fail(function () {
|
||||
$('.loader_container').hide();
|
||||
IoTApp.Helpers.Dialog.displayError(resources.failedToRetrieveJobs);
|
||||
});
|
||||
}
|
||||
|
||||
/* Set the heights of scrollable elements for correct overflow behavior */
|
||||
function fixHeights() {
|
||||
// set height of device details pane
|
||||
var fixedHeightVal = $(window).height() - $(".navbar").height();
|
||||
$(".height_fixed").height(fixedHeightVal);
|
||||
}
|
||||
|
||||
/* Hide/show the Device Details pane */
|
||||
var toggleProperties = function () {
|
||||
self.jobGrid.toggle();
|
||||
self.jobGridClosed.toggle();
|
||||
setGridWidth();
|
||||
}
|
||||
|
||||
// close the device details pane (called when device is no longer shown)
|
||||
var closeAndClearProperties = function () {
|
||||
// only toggle if we are already open!
|
||||
if (self.jobGrid.is(":visible")) {
|
||||
toggleProperties();
|
||||
}
|
||||
|
||||
// clear the details pane (so it's clean!)
|
||||
// Even though we're working with jobs, we still use the no_device_selected class
|
||||
// So we don't have to duplicate a bunch of styling for now
|
||||
var noJobSelected = resources.noJobSelected;
|
||||
$('#details_grid_container').html('<div class="details_grid__no_selection">' + noJobSelected + '</div>');
|
||||
}
|
||||
|
||||
var setGridWidth = function () {
|
||||
var gridContainer = $(".grid_container");
|
||||
|
||||
// Set the grid VERY NARROW initially--otherwise if panels are expanding,
|
||||
// the existing grid will be too wide to fit, and it will be pushed *below* the
|
||||
// side panes--roughly doubling the height of the content. In this case,
|
||||
// the browser will add a vertical scrollbar on the window.
|
||||
//
|
||||
// If this happens, the code in this function will collect data
|
||||
// with the grid pushed below and a scrollbar on the right--so
|
||||
// $(window).width() will be too narrow (by the width of the scrollbar).
|
||||
// When the grid is correctly sized, it will move back up, and the
|
||||
// browser will remove the scrollbar. But at that point there will be a gap
|
||||
// the width of the scrollbar, as the final measurement will be off by
|
||||
// the width of the scrollbar.
|
||||
|
||||
// set grid container to 1 px width--see comment block above
|
||||
gridContainer.width(1);
|
||||
|
||||
var jobGridVisible = $(".details_grid").is(':visible');
|
||||
|
||||
var jobGridWidth = jobGridVisible ? self.jobGrid.width() : self.jobGridClosed.width();
|
||||
|
||||
var windowWidth = $(window).width();
|
||||
|
||||
// check for min width (otherwise we over-shrink the grid)
|
||||
if (windowWidth < 800) {
|
||||
windowWidth = 800;
|
||||
}
|
||||
|
||||
var gridWidth = windowWidth - jobGridWidth - 98;
|
||||
gridContainer.width(gridWidth);
|
||||
}
|
||||
|
||||
var reloadGrid = function () {
|
||||
self.dataTable.ajax.reload();
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
toggleProperties: toggleProperties,
|
||||
reloadGrid: reloadGrid
|
||||
}
|
||||
}, [jQuery, resources]);
|
||||
|
||||
|
||||
$(function () {
|
||||
"use strict";
|
||||
|
||||
IoTApp.JobIndex.init(IoTApp.JobProperties);
|
||||
});
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
IoTApp.createModule('IoTApp.JobProperties', function () {
|
||||
"use strict";
|
||||
|
||||
var self = this;
|
||||
|
||||
var init = function (jobId, updateCallback) {
|
||||
self.jobId = jobId;
|
||||
self.updateCallback = updateCallback;
|
||||
getJobPropertiesView();
|
||||
}
|
||||
|
||||
var getJobPropertiesView = function () {
|
||||
$('#loadingElement').show();
|
||||
|
||||
$.ajaxSetup({ cache: false });
|
||||
$.get('/Job/GetJobProperties', { jobId: self.jobId }, function (response) {
|
||||
if (!$(".details_grid").is(':visible')) {
|
||||
IoTApp.JobIndex.toggleProperties();
|
||||
}
|
||||
onJobPropertiesDone(response);
|
||||
}).fail(function (response) {
|
||||
$('#loadingElement').hide();
|
||||
IoTApp.Helpers.RenderRetryError(resources.unableToRetrieveRuleFromService, $('#details_grid_container'), function () { getJobPropertiesView(); });
|
||||
});
|
||||
}
|
||||
|
||||
var onJobPropertiesDone = function (html) {
|
||||
$('#loadingElement').hide();
|
||||
$('#details_grid_container').empty();
|
||||
$('#details_grid_container').html(html);
|
||||
|
||||
var cancelButton = $('#cancelJobAction');
|
||||
if (cancelButton != null) {
|
||||
cancelButton.on("click", function () {
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
type: 'PUT',
|
||||
url: '/api/v1/jobs/' + self.jobId + '/cancel',
|
||||
cache: false,
|
||||
success: self.updateCallback,
|
||||
error: function () {
|
||||
IoTApp.Helpers.Dialog.displayError(resources.failedToCancelJob);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setDetailsPaneLoaderHeight();
|
||||
}
|
||||
|
||||
var setDetailsPaneLoaderHeight = function () {
|
||||
/* Set the height of the Device Details progress animation background to accommodate scrolling */
|
||||
var progressAnimationHeight = $("#details_grid_container").height() + $(".details_grid__grid_subhead.button_details_grid").outerHeight();
|
||||
|
||||
$(".loader_container_details").height(progressAnimationHeight);
|
||||
};
|
||||
|
||||
var onBegin = function () {
|
||||
$('#button_job_status').attr("disabled", "disabled");
|
||||
}
|
||||
|
||||
var onSuccess = function (result) {
|
||||
$('#button_job_status').removeAttr("disabled");
|
||||
if (result.success) {
|
||||
self.updateCallback();
|
||||
} else if (result.error) {
|
||||
IoTApp.Helpers.Dialog.displayError(result.error);
|
||||
} else {
|
||||
IoTApp.Helpers.Dialog.displayError(resources.jobUpdateError);
|
||||
}
|
||||
}
|
||||
|
||||
var onFailure = function (result) {
|
||||
$('#button_job_status').removeAttr("disabled");
|
||||
IoTApp.Helpers.Dialog.displayError(resources.jobUpdateError);
|
||||
}
|
||||
|
||||
var onComplete = function () {
|
||||
$('#loadingElement').hide();
|
||||
}
|
||||
|
||||
return {
|
||||
init: init,
|
||||
onBegin: onBegin,
|
||||
onSuccess: onSuccess,
|
||||
onFailure: onFailure,
|
||||
onComplete: onComplete
|
||||
}
|
||||
}, [jQuery, resources]);
|
|
@ -30,6 +30,10 @@
|
|||
}
|
||||
}, this);
|
||||
|
||||
this.backButtonClicked = function () {
|
||||
location.href = resources.redirectUrl;
|
||||
}
|
||||
|
||||
this.startDate = ko.observable(moment());
|
||||
this.isParameterLoading = true;
|
||||
this.maxExecutionTime = ko.observable(30);
|
||||
|
@ -55,7 +59,7 @@
|
|||
|
||||
this.cacheNameList = function (namelist) {
|
||||
self.methods = namelist;
|
||||
IoTApp.Controls.NameSelector.create(jQuery('.name_selector__text'), { type: IoTApp.Controls.NameSelector.NameListType.method }, self.methods);
|
||||
IoTApp.Controls.NameSelector.create(jQuery('.edit_form__methodComboBox'), { type: IoTApp.Controls.NameSelector.NameListType.method }, self.methods);
|
||||
}
|
||||
|
||||
this.init = function (queryName) {
|
||||
|
|
|
@ -4,16 +4,22 @@
|
|||
var self = this;
|
||||
function PropertiesEditItem(name, value, isDeleted) {
|
||||
var self = this;
|
||||
self.PropertyName = name;
|
||||
self.PropertyValue = value;
|
||||
self.isDeleted = isDeleted;
|
||||
self.PropertyName = ko.observable(name);
|
||||
self.PropertyValue = ko.observable(value);
|
||||
self.isDeleted = ko.observable(isDeleted);
|
||||
self.isEmptyValue = ko.computed(function () {
|
||||
return self.PropertyValue() == "" || self.PropertyValue() == null;
|
||||
})
|
||||
}
|
||||
|
||||
function TagsEditItem(name, value, isDeleted) {
|
||||
var self = this;
|
||||
self.TagName = name;
|
||||
self.TagValue = value;
|
||||
self.isDeleted = isDeleted;
|
||||
self.TagName = ko.observable(name);
|
||||
self.TagValue = ko.observable(value);
|
||||
self.isDeleted = ko.observable(isDeleted);
|
||||
self.isEmptyValue = ko.computed(function () {
|
||||
return self.TagValue() == "" || self.TagValue() == null;
|
||||
})
|
||||
}
|
||||
|
||||
function viewModel() {
|
||||
|
@ -33,14 +39,14 @@
|
|||
this.tagslist = {};
|
||||
|
||||
this.createEmptyPropertyIfNeeded = function (property) {
|
||||
if (self.properties.indexOf(property) == self.properties().length - 1) {
|
||||
if (self.properties.indexOf(property) == self.properties().length - 1 && !property.isEmptyValue()) {
|
||||
self.properties.push(new PropertiesEditItem("", "", false, false))
|
||||
self.onepropleft(false);
|
||||
}
|
||||
}
|
||||
|
||||
this.createEmptyTagIfNeeded = function (tag) {
|
||||
if (self.tags.indexOf(tag) == self.tags().length - 1) {
|
||||
if (self.tags.indexOf(tag) == self.tags().length - 1 && !tag.isEmptyValue()) {
|
||||
self.tags.push(new TagsEditItem("", "", false, false))
|
||||
self.onetagleft(false);
|
||||
}
|
||||
|
@ -53,6 +59,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
this.backButtonClicked = function () {
|
||||
location.href = resources.redirectUrl;
|
||||
}
|
||||
|
||||
this.removeProperty = function (prop) {
|
||||
self.properties.remove(prop);
|
||||
if (self.properties().length <= 1) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
/// <reference path="jquery-ui-1.11.4.min.js" />
|
||||
/// <reference path="jquery-ui-i18n.min.js" />
|
||||
/// <reference path="js.cookie-1.5.1.min.js" />
|
||||
/// <reference path="knockout.mapping-2.0.js" />
|
||||
/// <reference path="knockout-3.4.0.js" />
|
||||
/// <reference path="moment.min.js" />
|
||||
/// <reference path="moment-with-locales.min.js" />
|
||||
|
@ -25,6 +26,7 @@
|
|||
/// <reference path="views/advanced/advanced.js" />
|
||||
/// <reference path="views/dashboard/alerthistory.js" />
|
||||
/// <reference path="views/dashboard/dashboarddevicepane.js" />
|
||||
/// <reference path="views/dashboard/jobindicators.js" />
|
||||
/// <reference path="views/dashboard/mappane.js" />
|
||||
/// <reference path="views/dashboard/telemetryhistory.js" />
|
||||
/// <reference path="views/dashboard/telemetryhistorysummary.js" />
|
||||
|
@ -35,7 +37,9 @@
|
|||
/// <reference path="Views/Device/DeviceIndex.js" />
|
||||
/// <reference path="views/device/devicelistcolumns.js" />
|
||||
/// <reference path="views/device/deviceselecttype.js" />
|
||||
/// <reference path="views/device/editdesiredproperties.js" />
|
||||
/// <reference path="views/device/editdeviceproperties.js" />
|
||||
/// <reference path="views/device/edittags.js" />
|
||||
/// <reference path="views/device/removedevice.js" />
|
||||
/// <reference path="Views/DeviceCommand/DeviceCommandIndex.js" />
|
||||
/// <reference path="views/devicerules/allrulesassigned.js" />
|
||||
|
@ -46,3 +50,5 @@
|
|||
/// <reference path="views/iotapp.js" />
|
||||
/// <reference path="views/job/scheduledevicemethod.js" />
|
||||
/// <reference path="Views/Job/ScheduleTwinUpdate.js" />
|
||||
/// <reference path="views/job/jobindex.js" />
|
||||
/// <reference path="views/job/jobproperties.js" />
|
||||
|
|
|
@ -375,4 +375,628 @@
|
|||
Plugin.call($target, option)
|
||||
})
|
||||
|
||||
}(jQuery);
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: tooltip.js v3.3.6
|
||||
* http://getbootstrap.com/javascript/#tooltip
|
||||
* Inspired by the original jQuery.tipsy by Jason Frame
|
||||
* ========================================================================
|
||||
* Copyright 2011-2015 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// TOOLTIP PUBLIC CLASS DEFINITION
|
||||
// ===============================
|
||||
|
||||
var Tooltip = function (element, options) {
|
||||
this.type = null
|
||||
this.options = null
|
||||
this.enabled = null
|
||||
this.timeout = null
|
||||
this.hoverState = null
|
||||
this.$element = null
|
||||
this.inState = null
|
||||
|
||||
this.init('tooltip', element, options)
|
||||
}
|
||||
|
||||
Tooltip.VERSION = '3.3.6'
|
||||
|
||||
Tooltip.TRANSITION_DURATION = 150
|
||||
|
||||
Tooltip.DEFAULTS = {
|
||||
animation: true,
|
||||
placement: 'top',
|
||||
selector: false,
|
||||
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
|
||||
trigger: 'hover focus',
|
||||
title: '',
|
||||
delay: 0,
|
||||
html: false,
|
||||
container: false,
|
||||
viewport: {
|
||||
selector: 'body',
|
||||
padding: 0
|
||||
}
|
||||
}
|
||||
|
||||
Tooltip.prototype.init = function (type, element, options) {
|
||||
this.enabled = true
|
||||
this.type = type
|
||||
this.$element = $(element)
|
||||
this.options = this.getOptions(options)
|
||||
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
|
||||
this.inState = { click: false, hover: false, focus: false }
|
||||
|
||||
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
|
||||
throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
|
||||
}
|
||||
|
||||
var triggers = this.options.trigger.split(' ')
|
||||
|
||||
for (var i = triggers.length; i--;) {
|
||||
var trigger = triggers[i]
|
||||
|
||||
if (trigger == 'click') {
|
||||
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
|
||||
} else if (trigger != 'manual') {
|
||||
var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
|
||||
var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
|
||||
|
||||
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
|
||||
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
|
||||
}
|
||||
}
|
||||
|
||||
this.options.selector ?
|
||||
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
|
||||
this.fixTitle()
|
||||
}
|
||||
|
||||
Tooltip.prototype.getDefaults = function () {
|
||||
return Tooltip.DEFAULTS
|
||||
}
|
||||
|
||||
Tooltip.prototype.getOptions = function (options) {
|
||||
options = $.extend({}, this.getDefaults(), this.$element.data(), options)
|
||||
|
||||
if (options.delay && typeof options.delay == 'number') {
|
||||
options.delay = {
|
||||
show: options.delay,
|
||||
hide: options.delay
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
Tooltip.prototype.getDelegateOptions = function () {
|
||||
var options = {}
|
||||
var defaults = this.getDefaults()
|
||||
|
||||
this._options && $.each(this._options, function (key, value) {
|
||||
if (defaults[key] != value) options[key] = value
|
||||
})
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
Tooltip.prototype.enter = function (obj) {
|
||||
var self = obj instanceof this.constructor ?
|
||||
obj : $(obj.currentTarget).data('bs.' + this.type)
|
||||
|
||||
if (!self) {
|
||||
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
|
||||
$(obj.currentTarget).data('bs.' + this.type, self)
|
||||
}
|
||||
|
||||
if (obj instanceof $.Event) {
|
||||
self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
|
||||
}
|
||||
|
||||
if (self.tip().hasClass('in') || self.hoverState == 'in') {
|
||||
self.hoverState = 'in'
|
||||
return
|
||||
}
|
||||
|
||||
clearTimeout(self.timeout)
|
||||
|
||||
self.hoverState = 'in'
|
||||
|
||||
if (!self.options.delay || !self.options.delay.show) return self.show()
|
||||
|
||||
self.timeout = setTimeout(function () {
|
||||
if (self.hoverState == 'in') self.show()
|
||||
}, self.options.delay.show)
|
||||
}
|
||||
|
||||
Tooltip.prototype.isInStateTrue = function () {
|
||||
for (var key in this.inState) {
|
||||
if (this.inState[key]) return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Tooltip.prototype.leave = function (obj) {
|
||||
var self = obj instanceof this.constructor ?
|
||||
obj : $(obj.currentTarget).data('bs.' + this.type)
|
||||
|
||||
if (!self) {
|
||||
self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
|
||||
$(obj.currentTarget).data('bs.' + this.type, self)
|
||||
}
|
||||
|
||||
if (obj instanceof $.Event) {
|
||||
self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
|
||||
}
|
||||
|
||||
if (self.isInStateTrue()) return
|
||||
|
||||
clearTimeout(self.timeout)
|
||||
|
||||
self.hoverState = 'out'
|
||||
|
||||
if (!self.options.delay || !self.options.delay.hide) return self.hide()
|
||||
|
||||
self.timeout = setTimeout(function () {
|
||||
if (self.hoverState == 'out') self.hide()
|
||||
}, self.options.delay.hide)
|
||||
}
|
||||
|
||||
Tooltip.prototype.show = function () {
|
||||
var e = $.Event('show.bs.' + this.type)
|
||||
|
||||
if (this.hasContent() && this.enabled) {
|
||||
this.$element.trigger(e)
|
||||
|
||||
var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
|
||||
if (e.isDefaultPrevented() || !inDom) return
|
||||
var that = this
|
||||
|
||||
var $tip = this.tip()
|
||||
|
||||
var tipId = this.getUID(this.type)
|
||||
|
||||
this.setContent()
|
||||
$tip.attr('id', tipId)
|
||||
this.$element.attr('aria-describedby', tipId)
|
||||
|
||||
if (this.options.animation) $tip.addClass('fade')
|
||||
|
||||
var placement = typeof this.options.placement == 'function' ?
|
||||
this.options.placement.call(this, $tip[0], this.$element[0]) :
|
||||
this.options.placement
|
||||
|
||||
var autoToken = /\s?auto?\s?/i
|
||||
var autoPlace = autoToken.test(placement)
|
||||
if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
|
||||
|
||||
$tip
|
||||
.detach()
|
||||
.css({ top: 0, left: 0, display: 'block' })
|
||||
.addClass(placement)
|
||||
.data('bs.' + this.type, this)
|
||||
|
||||
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
|
||||
this.$element.trigger('inserted.bs.' + this.type)
|
||||
|
||||
var pos = this.getPosition()
|
||||
var actualWidth = $tip[0].offsetWidth
|
||||
var actualHeight = $tip[0].offsetHeight
|
||||
|
||||
if (autoPlace) {
|
||||
var orgPlacement = placement
|
||||
var viewportDim = this.getPosition(this.$viewport)
|
||||
|
||||
placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
|
||||
placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
|
||||
placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
|
||||
placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
|
||||
placement
|
||||
|
||||
$tip
|
||||
.removeClass(orgPlacement)
|
||||
.addClass(placement)
|
||||
}
|
||||
|
||||
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
|
||||
|
||||
this.applyPlacement(calculatedOffset, placement)
|
||||
|
||||
var complete = function () {
|
||||
var prevHoverState = that.hoverState
|
||||
that.$element.trigger('shown.bs.' + that.type)
|
||||
that.hoverState = null
|
||||
|
||||
if (prevHoverState == 'out') that.leave(that)
|
||||
}
|
||||
|
||||
$.support.transition && this.$tip.hasClass('fade') ?
|
||||
$tip
|
||||
.one('bsTransitionEnd', complete)
|
||||
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
|
||||
complete()
|
||||
}
|
||||
}
|
||||
|
||||
Tooltip.prototype.applyPlacement = function (offset, placement) {
|
||||
var $tip = this.tip()
|
||||
var width = $tip[0].offsetWidth
|
||||
var height = $tip[0].offsetHeight
|
||||
|
||||
// manually read margins because getBoundingClientRect includes difference
|
||||
var marginTop = parseInt($tip.css('margin-top'), 10)
|
||||
var marginLeft = parseInt($tip.css('margin-left'), 10)
|
||||
|
||||
// we must check for NaN for ie 8/9
|
||||
if (isNaN(marginTop)) marginTop = 0
|
||||
if (isNaN(marginLeft)) marginLeft = 0
|
||||
|
||||
offset.top += marginTop
|
||||
offset.left += marginLeft
|
||||
|
||||
// $.fn.offset doesn't round pixel values
|
||||
// so we use setOffset directly with our own function B-0
|
||||
$.offset.setOffset($tip[0], $.extend({
|
||||
using: function (props) {
|
||||
$tip.css({
|
||||
top: Math.round(props.top),
|
||||
left: Math.round(props.left)
|
||||
})
|
||||
}
|
||||
}, offset), 0)
|
||||
|
||||
$tip.addClass('in')
|
||||
|
||||
// check to see if placing tip in new offset caused the tip to resize itself
|
||||
var actualWidth = $tip[0].offsetWidth
|
||||
var actualHeight = $tip[0].offsetHeight
|
||||
|
||||
if (placement == 'top' && actualHeight != height) {
|
||||
offset.top = offset.top + height - actualHeight
|
||||
}
|
||||
|
||||
var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
|
||||
|
||||
if (delta.left) offset.left += delta.left
|
||||
else offset.top += delta.top
|
||||
|
||||
var isVertical = /top|bottom/.test(placement)
|
||||
var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
|
||||
var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
|
||||
|
||||
$tip.offset(offset)
|
||||
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
|
||||
}
|
||||
|
||||
Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
|
||||
this.arrow()
|
||||
.css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
|
||||
.css(isVertical ? 'top' : 'left', '')
|
||||
}
|
||||
|
||||
Tooltip.prototype.setContent = function () {
|
||||
var $tip = this.tip()
|
||||
var title = this.getTitle()
|
||||
|
||||
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
|
||||
$tip.removeClass('fade in top bottom left right')
|
||||
}
|
||||
|
||||
Tooltip.prototype.hide = function (callback) {
|
||||
var that = this
|
||||
var $tip = $(this.$tip)
|
||||
var e = $.Event('hide.bs.' + this.type)
|
||||
|
||||
function complete() {
|
||||
if (that.hoverState != 'in') $tip.detach()
|
||||
that.$element
|
||||
.removeAttr('aria-describedby')
|
||||
.trigger('hidden.bs.' + that.type)
|
||||
callback && callback()
|
||||
}
|
||||
|
||||
this.$element.trigger(e)
|
||||
|
||||
if (e.isDefaultPrevented()) return
|
||||
|
||||
$tip.removeClass('in')
|
||||
|
||||
$.support.transition && $tip.hasClass('fade') ?
|
||||
$tip
|
||||
.one('bsTransitionEnd', complete)
|
||||
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
|
||||
complete()
|
||||
|
||||
this.hoverState = null
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Tooltip.prototype.fixTitle = function () {
|
||||
var $e = this.$element
|
||||
if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
|
||||
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
|
||||
}
|
||||
}
|
||||
|
||||
Tooltip.prototype.hasContent = function () {
|
||||
return this.getTitle()
|
||||
}
|
||||
|
||||
Tooltip.prototype.getPosition = function ($element) {
|
||||
$element = $element || this.$element
|
||||
|
||||
var el = $element[0]
|
||||
var isBody = el.tagName == 'BODY'
|
||||
|
||||
var elRect = el.getBoundingClientRect()
|
||||
if (elRect.width == null) {
|
||||
// width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
|
||||
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
|
||||
}
|
||||
var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
|
||||
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
|
||||
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
|
||||
|
||||
return $.extend({}, elRect, scroll, outerDims, elOffset)
|
||||
}
|
||||
|
||||
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
|
||||
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
||||
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
||||
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
|
||||
/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
|
||||
|
||||
}
|
||||
|
||||
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
|
||||
var delta = { top: 0, left: 0 }
|
||||
if (!this.$viewport) return delta
|
||||
|
||||
var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
|
||||
var viewportDimensions = this.getPosition(this.$viewport)
|
||||
|
||||
if (/right|left/.test(placement)) {
|
||||
var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
|
||||
var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
|
||||
if (topEdgeOffset < viewportDimensions.top) { // top overflow
|
||||
delta.top = viewportDimensions.top - topEdgeOffset
|
||||
} else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
|
||||
delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
|
||||
}
|
||||
} else {
|
||||
var leftEdgeOffset = pos.left - viewportPadding
|
||||
var rightEdgeOffset = pos.left + viewportPadding + actualWidth
|
||||
if (leftEdgeOffset < viewportDimensions.left) { // left overflow
|
||||
delta.left = viewportDimensions.left - leftEdgeOffset
|
||||
} else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
|
||||
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
|
||||
}
|
||||
}
|
||||
|
||||
return delta
|
||||
}
|
||||
|
||||
Tooltip.prototype.getTitle = function () {
|
||||
var title
|
||||
var $e = this.$element
|
||||
var o = this.options
|
||||
|
||||
title = $e.attr('data-original-title')
|
||||
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
Tooltip.prototype.getUID = function (prefix) {
|
||||
do prefix += ~~(Math.random() * 1000000)
|
||||
while (document.getElementById(prefix))
|
||||
return prefix
|
||||
}
|
||||
|
||||
Tooltip.prototype.tip = function () {
|
||||
if (!this.$tip) {
|
||||
this.$tip = $(this.options.template)
|
||||
if (this.$tip.length != 1) {
|
||||
throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
|
||||
}
|
||||
}
|
||||
return this.$tip
|
||||
}
|
||||
|
||||
Tooltip.prototype.arrow = function () {
|
||||
return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
|
||||
}
|
||||
|
||||
Tooltip.prototype.enable = function () {
|
||||
this.enabled = true
|
||||
}
|
||||
|
||||
Tooltip.prototype.disable = function () {
|
||||
this.enabled = false
|
||||
}
|
||||
|
||||
Tooltip.prototype.toggleEnabled = function () {
|
||||
this.enabled = !this.enabled
|
||||
}
|
||||
|
||||
Tooltip.prototype.toggle = function (e) {
|
||||
var self = this
|
||||
if (e) {
|
||||
self = $(e.currentTarget).data('bs.' + this.type)
|
||||
if (!self) {
|
||||
self = new this.constructor(e.currentTarget, this.getDelegateOptions())
|
||||
$(e.currentTarget).data('bs.' + this.type, self)
|
||||
}
|
||||
}
|
||||
|
||||
if (e) {
|
||||
self.inState.click = !self.inState.click
|
||||
if (self.isInStateTrue()) self.enter(self)
|
||||
else self.leave(self)
|
||||
} else {
|
||||
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
|
||||
}
|
||||
}
|
||||
|
||||
Tooltip.prototype.destroy = function () {
|
||||
var that = this
|
||||
clearTimeout(this.timeout)
|
||||
this.hide(function () {
|
||||
that.$element.off('.' + that.type).removeData('bs.' + that.type)
|
||||
if (that.$tip) {
|
||||
that.$tip.detach()
|
||||
}
|
||||
that.$tip = null
|
||||
that.$arrow = null
|
||||
that.$viewport = null
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// TOOLTIP PLUGIN DEFINITION
|
||||
// =========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.tooltip')
|
||||
var options = typeof option == 'object' && option
|
||||
|
||||
if (!data && /destroy|hide/.test(option)) return
|
||||
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.tooltip
|
||||
|
||||
$.fn.tooltip = Plugin
|
||||
$.fn.tooltip.Constructor = Tooltip
|
||||
|
||||
|
||||
// TOOLTIP NO CONFLICT
|
||||
// ===================
|
||||
|
||||
$.fn.tooltip.noConflict = function () {
|
||||
$.fn.tooltip = old
|
||||
return this
|
||||
}
|
||||
|
||||
}(jQuery);
|
||||
|
||||
/* ========================================================================
|
||||
* Bootstrap: popover.js v3.3.6
|
||||
* http://getbootstrap.com/javascript/#popovers
|
||||
* ========================================================================
|
||||
* Copyright 2011-2015 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
|
||||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
// POPOVER PUBLIC CLASS DEFINITION
|
||||
// ===============================
|
||||
|
||||
var Popover = function (element, options) {
|
||||
this.init('popover', element, options)
|
||||
}
|
||||
|
||||
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
|
||||
|
||||
Popover.VERSION = '3.3.6'
|
||||
|
||||
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
|
||||
placement: 'right',
|
||||
trigger: 'click',
|
||||
content: '',
|
||||
template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
|
||||
})
|
||||
|
||||
|
||||
// NOTE: POPOVER EXTENDS tooltip.js
|
||||
// ================================
|
||||
|
||||
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
|
||||
|
||||
Popover.prototype.constructor = Popover
|
||||
|
||||
Popover.prototype.getDefaults = function () {
|
||||
return Popover.DEFAULTS
|
||||
}
|
||||
|
||||
Popover.prototype.setContent = function () {
|
||||
var $tip = this.tip()
|
||||
var title = this.getTitle()
|
||||
var content = this.getContent()
|
||||
|
||||
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
|
||||
$tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
|
||||
this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
|
||||
](content)
|
||||
|
||||
$tip.removeClass('fade top bottom left right in')
|
||||
|
||||
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
|
||||
// this manually by checking the contents.
|
||||
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
|
||||
}
|
||||
|
||||
Popover.prototype.hasContent = function () {
|
||||
return this.getTitle() || this.getContent()
|
||||
}
|
||||
|
||||
Popover.prototype.getContent = function () {
|
||||
var $e = this.$element
|
||||
var o = this.options
|
||||
|
||||
return $e.attr('data-content')
|
||||
|| (typeof o.content == 'function' ?
|
||||
o.content.call($e[0]) :
|
||||
o.content)
|
||||
}
|
||||
|
||||
Popover.prototype.arrow = function () {
|
||||
return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
|
||||
}
|
||||
|
||||
|
||||
// POPOVER PLUGIN DEFINITION
|
||||
// =========================
|
||||
|
||||
function Plugin(option) {
|
||||
return this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('bs.popover')
|
||||
var options = typeof option == 'object' && option
|
||||
|
||||
if (!data && /destroy|hide/.test(option)) return
|
||||
if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
|
||||
if (typeof option == 'string') data[option]()
|
||||
})
|
||||
}
|
||||
|
||||
var old = $.fn.popover
|
||||
|
||||
$.fn.popover = Plugin
|
||||
$.fn.popover.Constructor = Popover
|
||||
|
||||
|
||||
// POPOVER NO CONFLICT
|
||||
// ===================
|
||||
|
||||
$.fn.popover.noConflict = function () {
|
||||
$.fn.popover = old
|
||||
return this
|
||||
}
|
||||
|
||||
}(jQuery);
|
||||
|
|
|
@ -13,11 +13,29 @@
|
|||
NameListType.property = NameListType.desiredProperty | NameListType.reportedProperty;
|
||||
NameListType.all = NameListType.deviceInfo | NameListType.tag | NameListType.desiredProperty | NameListType.reportedProperty | NameListType.method;
|
||||
|
||||
var PositionType = {
|
||||
leftBottom: { my: "left top", at: "left bottom" },
|
||||
rightBottom: { my: "right top", at: "right bottom" },
|
||||
leftTop: { my: "left bottom", at: "left top" },
|
||||
rightTop: { my: "right bottom", at: "right top" },
|
||||
}
|
||||
|
||||
var create = function ($element, options, data) {
|
||||
options = $.extend({
|
||||
type: NameListType.all
|
||||
type: NameListType.all,
|
||||
position: PositionType.leftBottom
|
||||
}, options);
|
||||
|
||||
$element.autocomplete({
|
||||
select: function (event, ui) {
|
||||
$(this).val(ui.item.value).change();
|
||||
},
|
||||
minLength: 0,
|
||||
position: options.position
|
||||
}).focus(function () {
|
||||
$(this).autocomplete("search", $(this).val());
|
||||
});
|
||||
|
||||
if (data) {
|
||||
bindData($element, data);
|
||||
}
|
||||
|
@ -37,15 +55,7 @@
|
|||
var filters = $element.data('nameListFilters');
|
||||
var items = dataToStringArray(data, filters);
|
||||
|
||||
$element.autocomplete({
|
||||
source: items,
|
||||
select: function(event,ui){
|
||||
$(this).val(ui.item.value).change();
|
||||
},
|
||||
minLength: 0
|
||||
}).focus(function () {
|
||||
$(this).autocomplete("search", $(this).val());
|
||||
});
|
||||
$element.autocomplete('option', 'source', items);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -106,6 +116,7 @@
|
|||
loadNameList: loadNameList,
|
||||
getSelectedItem: getSelectedItem,
|
||||
NameListType: NameListType,
|
||||
Position: PositionType,
|
||||
applyFilters: applyFilters
|
||||
};
|
||||
}, [jQuery]);
|
|
@ -0,0 +1,70 @@
|
|||
/// Knockout Mapping plugin v2.4.1
|
||||
/// (c) 2013 Steven Sanderson, Roy Jacobs - http://knockoutjs.com/
|
||||
/// License: MIT (http://www.opensource.org/licenses/mit-license.php)
|
||||
(function (e) { "function" === typeof require && "object" === typeof exports && "object" === typeof module ? e(require("knockout"), exports) : "function" === typeof define && define.amd ? define(["knockout", "exports"], e) : e(ko, ko.mapping = {}) })(function (e, f) {
|
||||
function y(b, c) {
|
||||
var a, d; for (d in c) if (c.hasOwnProperty(d) && c[d]) if (a = f.getType(b[d]), d && b[d] && "array" !== a && "string" !== a) y(b[d], c[d]); else if ("array" === f.getType(b[d]) && "array" === f.getType(c[d])) {
|
||||
a = b; for (var e = d, l = b[d], n = c[d], t = {}, g = l.length - 1; 0 <= g; --g) t[l[g]] = l[g]; for (g =
|
||||
n.length - 1; 0 <= g; --g) t[n[g]] = n[g]; l = []; n = void 0; for (n in t) l.push(t[n]); a[e] = l
|
||||
} else b[d] = c[d]
|
||||
} function E(b, c) { var a = {}; y(a, b); y(a, c); return a } function z(b, c) {
|
||||
for (var a = E({}, b), e = L.length - 1; 0 <= e; e--) { var f = L[e]; a[f] && (a[""] instanceof Object || (a[""] = {}), a[""][f] = a[f], delete a[f]) } c && (a.ignore = h(c.ignore, a.ignore), a.include = h(c.include, a.include), a.copy = h(c.copy, a.copy), a.observe = h(c.observe, a.observe)); a.ignore = h(a.ignore, j.ignore); a.include = h(a.include, j.include); a.copy = h(a.copy, j.copy); a.observe = h(a.observe,
|
||||
j.observe); a.mappedProperties = a.mappedProperties || {}; a.copiedProperties = a.copiedProperties || {}; return a
|
||||
} function h(b, c) { "array" !== f.getType(b) && (b = "undefined" === f.getType(b) ? [] : [b]); "array" !== f.getType(c) && (c = "undefined" === f.getType(c) ? [] : [c]); return e.utils.arrayGetDistinctValues(b.concat(c)) } function F(b, c, a, d, k, l, n) {
|
||||
var t = "array" === f.getType(e.utils.unwrapObservable(c)); l = l || ""; if (f.isMapped(b)) { var g = e.utils.unwrapObservable(b)[p]; a = E(g, a) } var j = n || k, h = function () {
|
||||
return a[d] && a[d].create instanceof
|
||||
Function
|
||||
}, x = function (b) {
|
||||
var f = G, g = e.dependentObservable; e.dependentObservable = function (a, b, c) { c = c || {}; a && "object" == typeof a && (c = a); var d = c.deferEvaluation, M = !1; c.deferEvaluation = !0; a = new H(a, b, c); if (!d) { var g = a, d = e.dependentObservable; e.dependentObservable = H; a = e.isWriteableObservable(g); e.dependentObservable = d; d = H({ read: function () { M || (e.utils.arrayRemoveItem(f, g), M = !0); return g.apply(g, arguments) }, write: a && function (a) { return g(a) }, deferEvaluation: !0 }); d.__DO = g; a = d; f.push(a) } return a }; e.dependentObservable.fn =
|
||||
H.fn; e.computed = e.dependentObservable; b = e.utils.unwrapObservable(k) instanceof Array ? a[d].create({ data: b || c, parent: j, skip: N }) : a[d].create({ data: b || c, parent: j }); e.dependentObservable = g; e.computed = e.dependentObservable; return b
|
||||
}, u = function () { return a[d] && a[d].update instanceof Function }, v = function (b, f) { var g = { data: f || c, parent: j, target: e.utils.unwrapObservable(b) }; e.isWriteableObservable(b) && (g.observable = b); return a[d].update(g) }; if (n = I.get(c)) return n; d = d || ""; if (t) {
|
||||
var t = [], s = !1, m = function (a) { return a };
|
||||
a[d] && a[d].key && (m = a[d].key, s = !0); e.isObservable(b) || (b = e.observableArray([]), b.mappedRemove = function (a) { var c = "function" == typeof a ? a : function (b) { return b === m(a) }; return b.remove(function (a) { return c(m(a)) }) }, b.mappedRemoveAll = function (a) { var c = C(a, m); return b.remove(function (a) { return -1 != e.utils.arrayIndexOf(c, m(a)) }) }, b.mappedDestroy = function (a) { var c = "function" == typeof a ? a : function (b) { return b === m(a) }; return b.destroy(function (a) { return c(m(a)) }) }, b.mappedDestroyAll = function (a) {
|
||||
var c = C(a, m); return b.destroy(function (a) {
|
||||
return -1 !=
|
||||
e.utils.arrayIndexOf(c, m(a))
|
||||
})
|
||||
}, b.mappedIndexOf = function (a) { var c = C(b(), m); a = m(a); return e.utils.arrayIndexOf(c, a) }, b.mappedGet = function (a) { return b()[b.mappedIndexOf(a)] }, b.mappedCreate = function (a) { if (-1 !== b.mappedIndexOf(a)) throw Error("There already is an object with the key that you specified."); var c = h() ? x(a) : a; u() && (a = v(c, a), e.isWriteableObservable(c) ? c(a) : c = a); b.push(c); return c }); n = C(e.utils.unwrapObservable(b), m).sort(); g = C(c, m); s && g.sort(); s = e.utils.compareArrays(n, g); n = {}; var J, A = e.utils.unwrapObservable(c),
|
||||
y = {}, z = !0, g = 0; for (J = A.length; g < J; g++) { var r = m(A[g]); if (void 0 === r || r instanceof Object) { z = !1; break } y[r] = A[g] } var A = [], B = 0, g = 0; for (J = s.length; g < J; g++) {
|
||||
var r = s[g], q, w = l + "[" + g + "]"; switch (r.status) {
|
||||
case "added": var D = z ? y[r.value] : K(e.utils.unwrapObservable(c), r.value, m); q = F(void 0, D, a, d, b, w, k); h() || (q = e.utils.unwrapObservable(q)); w = O(e.utils.unwrapObservable(c), D, n); q === N ? B++ : A[w - B] = q; n[w] = !0; break; case "retained": D = z ? y[r.value] : K(e.utils.unwrapObservable(c), r.value, m); q = K(b, r.value, m); F(q, D, a, d, b, w,
|
||||
k); w = O(e.utils.unwrapObservable(c), D, n); A[w] = q; n[w] = !0; break; case "deleted": q = K(b, r.value, m)
|
||||
} t.push({ event: r.status, item: q })
|
||||
} b(A); a[d] && a[d].arrayChanged && e.utils.arrayForEach(t, function (b) { a[d].arrayChanged(b.event, b.item) })
|
||||
} else if (P(c)) {
|
||||
b = e.utils.unwrapObservable(b); if (!b) { if (h()) return s = x(), u() && (s = v(s)), s; if (u()) return v(s); b = {} } u() && (b = v(b)); I.save(c, b); if (u()) return b; Q(c, function (d) {
|
||||
var f = l.length ? l + "." + d : d; if (-1 == e.utils.arrayIndexOf(a.ignore, f)) if (-1 != e.utils.arrayIndexOf(a.copy, f)) b[d] =
|
||||
c[d]; else if ("object" != typeof c[d] && "array" != typeof c[d] && 0 < a.observe.length && -1 == e.utils.arrayIndexOf(a.observe, f)) b[d] = c[d], a.copiedProperties[f] = !0; else { var g = I.get(c[d]), k = F(b[d], c[d], a, d, b, f, b), g = g || k; if (0 < a.observe.length && -1 == e.utils.arrayIndexOf(a.observe, f)) b[d] = g(), a.copiedProperties[f] = !0; else { if (e.isWriteableObservable(b[d])) { if (g = e.utils.unwrapObservable(g), b[d]() !== g) b[d](g) } else g = void 0 === b[d] ? g : e.utils.unwrapObservable(g), b[d] = g; a.mappedProperties[f] = !0 } }
|
||||
})
|
||||
} else switch (f.getType(c)) {
|
||||
case "function": u() ?
|
||||
e.isWriteableObservable(c) ? (c(v(c)), b = c) : b = v(c) : b = c; break; default: if (e.isWriteableObservable(b)) return q = u() ? v(b) : e.utils.unwrapObservable(c), b(q), q; h() || u(); b = h() ? x() : e.observable(e.utils.unwrapObservable(c)); u() && b(v(b))
|
||||
} return b
|
||||
} function O(b, c, a) { for (var d = 0, e = b.length; d < e; d++) if (!0 !== a[d] && b[d] === c) return d; return null } function R(b, c) { var a; c && (a = c(b)); "undefined" === f.getType(a) && (a = b); return e.utils.unwrapObservable(a) } function K(b, c, a) {
|
||||
b = e.utils.unwrapObservable(b); for (var d = 0, f = b.length; d <
|
||||
f; d++) { var l = b[d]; if (R(l, a) === c) return l } throw Error("When calling ko.update*, the key '" + c + "' was not found!");
|
||||
} function C(b, c) { return e.utils.arrayMap(e.utils.unwrapObservable(b), function (a) { return c ? R(a, c) : a }) } function Q(b, c) { if ("array" === f.getType(b)) for (var a = 0; a < b.length; a++) c(a); else for (a in b) c(a) } function P(b) { var c = f.getType(b); return ("object" === c || "array" === c) && null !== b } function T() {
|
||||
var b = [], c = []; this.save = function (a, d) { var f = e.utils.arrayIndexOf(b, a); 0 <= f ? c[f] = d : (b.push(a), c.push(d)) };
|
||||
this.get = function (a) { a = e.utils.arrayIndexOf(b, a); return 0 <= a ? c[a] : void 0 }
|
||||
} function S() { var b = {}, c = function (a) { var c; try { c = a } catch (e) { c = "$$$" } a = b[c]; void 0 === a && (a = new T, b[c] = a); return a }; this.save = function (a, b) { c(a).save(a, b) }; this.get = function (a) { return c(a).get(a) } } var p = "__ko_mapping__", H = e.dependentObservable, B = 0, G, I, L = ["create", "update", "key", "arrayChanged"], N = {}, x = { include: ["_destroy"], ignore: [], copy: [], observe: [] }, j = x; f.isMapped = function (b) { return (b = e.utils.unwrapObservable(b)) && b[p] }; f.fromJS =
|
||||
function (b) { if (0 == arguments.length) throw Error("When calling ko.fromJS, pass the object you want to convert."); try { B++ || (G = [], I = new S); var c, a; 2 == arguments.length && (arguments[1][p] ? a = arguments[1] : c = arguments[1]); 3 == arguments.length && (c = arguments[1], a = arguments[2]); a && (c = E(c, a[p])); c = z(c); var d = F(a, b, c); a && (d = a); if (!--B) for (; G.length;) { var e = G.pop(); e && (e(), e.__DO.throttleEvaluation = e.throttleEvaluation) } d[p] = E(d[p], c); return d } catch (f) { throw B = 0, f; } }; f.fromJSON = function (b) {
|
||||
var c = e.utils.parseJson(b);
|
||||
arguments[0] = c; return f.fromJS.apply(this, arguments)
|
||||
}; f.updateFromJS = function () { throw Error("ko.mapping.updateFromJS, use ko.mapping.fromJS instead. Please note that the order of parameters is different!"); }; f.updateFromJSON = function () { throw Error("ko.mapping.updateFromJSON, use ko.mapping.fromJSON instead. Please note that the order of parameters is different!"); }; f.toJS = function (b, c) {
|
||||
j || f.resetDefaultOptions(); if (0 == arguments.length) throw Error("When calling ko.mapping.toJS, pass the object you want to convert.");
|
||||
if ("array" !== f.getType(j.ignore)) throw Error("ko.mapping.defaultOptions().ignore should be an array."); if ("array" !== f.getType(j.include)) throw Error("ko.mapping.defaultOptions().include should be an array."); if ("array" !== f.getType(j.copy)) throw Error("ko.mapping.defaultOptions().copy should be an array."); c = z(c, b[p]); return f.visitModel(b, function (a) { return e.utils.unwrapObservable(a) }, c)
|
||||
}; f.toJSON = function (b, c) { var a = f.toJS(b, c); return e.utils.stringifyJson(a) }; f.defaultOptions = function () {
|
||||
if (0 < arguments.length) j =
|
||||
arguments[0]; else return j
|
||||
}; f.resetDefaultOptions = function () { j = { include: x.include.slice(0), ignore: x.ignore.slice(0), copy: x.copy.slice(0) } }; f.getType = function (b) { if (b && "object" === typeof b) { if (b.constructor === Date) return "date"; if (b.constructor === Array) return "array" } return typeof b }; f.visitModel = function (b, c, a) {
|
||||
a = a || {}; a.visitedObjects = a.visitedObjects || new S; var d, k = e.utils.unwrapObservable(b); if (P(k)) a = z(a, k[p]), c(b, a.parentName), d = "array" === f.getType(k) ? [] : {}; else return c(b, a.parentName); a.visitedObjects.save(b,
|
||||
d); var l = a.parentName; Q(k, function (b) {
|
||||
if (!(a.ignore && -1 != e.utils.arrayIndexOf(a.ignore, b))) {
|
||||
var j = k[b], g = a, h = l || ""; "array" === f.getType(k) ? l && (h += "[" + b + "]") : (l && (h += "."), h += b); g.parentName = h; if (!(-1 === e.utils.arrayIndexOf(a.copy, b) && -1 === e.utils.arrayIndexOf(a.include, b) && k[p] && k[p].mappedProperties && !k[p].mappedProperties[b] && k[p].copiedProperties && !k[p].copiedProperties[b] && "array" !== f.getType(k))) switch (f.getType(e.utils.unwrapObservable(j))) {
|
||||
case "object": case "array": case "undefined": g = a.visitedObjects.get(j);
|
||||
d[b] = "undefined" !== f.getType(g) ? g : f.visitModel(j, c, a); break; default: d[b] = c(j, a.parentName)
|
||||
}
|
||||
}
|
||||
}); return d
|
||||
}
|
||||
});
|
|
@ -57,6 +57,7 @@
|
|||
</div>
|
||||
@{
|
||||
Html.RenderPartial("_AlertHistory");
|
||||
Html.RenderPartial("_JobIndicators");
|
||||
}
|
||||
</div>
|
||||
@{
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
@using GlobalResources
|
||||
<div id="dashboard_job_indicators" class="dashboard_job_indicators"></div>
|
||||
<script src="~/Scripts/Views/Dashboard/JobIndicators.js"></script>
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
IoTApp.Dashboard.JobIndicators.init();
|
||||
})();
|
||||
</script>
|
|
@ -0,0 +1,95 @@
|
|||
@using System.Globalization
|
||||
@using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Helpers
|
||||
@using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infrastructure.Models
|
||||
@using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Helpers
|
||||
@using GlobalResources
|
||||
@model Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Models.EditDevicePropertiesModel
|
||||
|
||||
@{
|
||||
ViewBag.Title = @Strings.EditDeviceProperties;
|
||||
Layout = "~/Views/Shared/_LayoutNoNavigation.cshtml";
|
||||
}
|
||||
|
||||
<header class="header_main">
|
||||
|
||||
<button class="header_main__button_back" type="button" data-bind="click: backButtonClicked"></button>
|
||||
<h2 class="header_main__subhead header_main__subhead--large">@string.Format(Strings.EditDesiredPropertiesFor, Model.DeviceId)</h2>
|
||||
</header>
|
||||
<div class="content_outer">
|
||||
|
||||
<div class="content_inner">
|
||||
<div id="content">
|
||||
<div id="deviceMetadataForm">
|
||||
@using (Html.BeginForm("EditDeviceProperties", "Device", FormMethod.Post, new { data_bind = "submit: formSubmit" }))
|
||||
{
|
||||
@Html.AntiForgeryToken()
|
||||
@Html.HiddenFor(m => m.DeviceId)
|
||||
<p>
|
||||
@Html.ValidationSummary(false)
|
||||
</p>
|
||||
|
||||
<fieldset class="edit_form">
|
||||
<table class="values_editor" id="propertiesTable">
|
||||
<thead>
|
||||
<tr class="values_editor__groupheader">
|
||||
<th class="edit_form__labelHeader">@Strings.ScheduleDesiredPropertyName</th>
|
||||
<th class="edit_form__labelHeader">@Strings.ScheduleEditValue</th>
|
||||
<th class="edit_form__labelHeader">@Strings.Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach:{data: properties, afterAdd:makeproplist}">
|
||||
<tr class="values_editor__group">
|
||||
<td>
|
||||
<input type="text" class="edit_form__texthalf edit_form__propertiesComboBox"
|
||||
data-msg-required="@Strings.RequiredValidationString"
|
||||
data-rule-required="true"
|
||||
data-bind='value: key, attr: { name: "DesiredProperties" + $index() + ".PropertyName", id: "DesiredProperties" + $index() + ".PropertyName" }' />
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="edit_form__texthalf"
|
||||
data-bind='value: value.value, event: { blur: $root.createEmptyPropertyIfNeeded }, attr: { name: "DesiredProperties[" + $index() + "].PropertyValue", id: "DesiredProperties" + $index() + ".PropertyValue" }' />
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
value="true"
|
||||
data-bind='check: isDeleted, attr: { name: "DesiredProperties" + $index() + ".isDeleted", id: "DesiredProperties" + $index() + ".isDeleted" }' />
|
||||
</td>
|
||||
<td><span class="edit_form__labelsmall" data-bind="text:$root.fromNowValue(value.lastUpdated,getCulture())"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="clear:both"></div>
|
||||
<div>
|
||||
<span class="icon-add"></span>
|
||||
<div>
|
||||
Add Desired Property
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="fieldset_button">
|
||||
<button class="button_base button_secondary button_cancel">@Strings.Cancel</button>
|
||||
<button class="button_base" type="submit">@Strings.SaveChangesToDevice</button>
|
||||
</fieldset>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
var resources = {
|
||||
redirectUrl: '@Url.Action("Index", "Device")',
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="~/Scripts/Views/Device/EditDesiredProperties.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
IoTApp.EditDesiredProperties.init("@Model.DeviceId");
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<button class="header_main__button_back" type="button"></button>
|
||||
<h2 class="header_main__subhead header_main__subhead--large">@string.Format(Strings.EditDevicePropertiesFor, Model.DeviceId)</h2>
|
||||
</header>
|
||||
<div class="content_outer">
|
||||
<div class="content_outer">
|
||||
|
||||
<div class="content_inner">
|
||||
<div id="content">
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
@using System.Globalization
|
||||
@using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Helpers
|
||||
@using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infrastructure.Models
|
||||
@using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Helpers
|
||||
@using GlobalResources
|
||||
@model Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Models.EditDevicePropertiesModel
|
||||
|
||||
@{
|
||||
ViewBag.Title = @Strings.EditDeviceProperties;
|
||||
Layout = "~/Views/Shared/_LayoutNoNavigation.cshtml";
|
||||
}
|
||||
|
||||
<header class="header_main">
|
||||
|
||||
<button class="header_main__button_back" type="button"></button>
|
||||
<h2 class="header_main__subhead header_main__subhead--large">@string.Format(Strings.EditTagsFor, Model.DeviceId)</h2>
|
||||
</header>
|
||||
<div class="content_outer">
|
||||
|
||||
<div class="content_inner">
|
||||
<div id="content">
|
||||
<div id="deviceMetadataForm">
|
||||
@using (Html.BeginForm("EditDeviceProperties", "Device", FormMethod.Post))
|
||||
{
|
||||
@Html.AntiForgeryToken()
|
||||
@Html.HiddenFor(m => m.DeviceId)
|
||||
<p>
|
||||
@Html.ValidationSummary(false)
|
||||
</p>
|
||||
|
||||
<fieldset class="edit_form">
|
||||
@{
|
||||
DateTime? resolvedTime;
|
||||
for (int i = 0; i < Model.DevicePropertyValueModels.Count; ++i)
|
||||
{
|
||||
if (Model.DevicePropertyValueModels[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@Html.LabelFor(
|
||||
m => m.DevicePropertyValueModels[i].Value,
|
||||
Model.DevicePropertyValueModels[i].Name,
|
||||
new { @class = "edit_form__label" })
|
||||
|
||||
if (Model.DevicePropertyValueModels[i].IsEditable)
|
||||
{
|
||||
@Html.TextBoxFor(m => m.DevicePropertyValueModels[i].Value, new { @class = "edit_form__text" })
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((Model.DevicePropertyValueModels[i].PropertyType == PropertyType.DateTime) &&
|
||||
(resolvedTime = DynamicValuesHelper.ConvertToDateTime(CultureInfo.InvariantCulture, Model.DevicePropertyValueModels[i].Value)).HasValue)
|
||||
{
|
||||
@Html.TextBox(Model.DevicePropertyValueModels[i].Name, resolvedTime.Value.ToString(), new { @readonly = "readonly", @class = "input_text--readonly" })
|
||||
}
|
||||
else
|
||||
{
|
||||
@Html.TextBoxFor(m => m.DevicePropertyValueModels[i].Value, new { @readonly = "readonly", @class = "input_text--readonly" })
|
||||
}
|
||||
}
|
||||
|
||||
@Html.HiddenFor(m => m.DevicePropertyValueModels[i].Name)
|
||||
}
|
||||
}
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="fieldset_button">
|
||||
<button class="button_base" type="submit">@Strings.SaveChangesToDevice</button>
|
||||
</fieldset>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
var resources = {
|
||||
redirectUrl: '@Url.Action("Index", "Device")',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
var resources = {
|
||||
redirectUrl: '@Url.Action("Index", "Device")',
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="~/Scripts/Views/Device/EditTags.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
IoTApp.EditTags.init("@Model.DeviceId");
|
||||
});
|
||||
|
||||
</script>
|
|
@ -55,7 +55,7 @@
|
|||
|
||||
<p class="grid_detail_value">
|
||||
@Html.ActionLink(
|
||||
Strings.InvokeMethodLabel, "Index", "DeviceMethod",
|
||||
Strings.DeviceMethods, "Index", "DeviceMethod",
|
||||
new
|
||||
{
|
||||
deviceId = Model.DeviceID
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
@using GlobalResources
|
||||
@using Microsoft.Azure.Devices
|
||||
@using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Helpers
|
||||
@{
|
||||
ViewBag.Title = "Index";
|
||||
}
|
||||
|
||||
<noscript>
|
||||
<div class="error_noscript">
|
||||
<h1 class="error_noscript__header">@Strings.NoscriptHeader</h1>
|
||||
<p>@Strings.NoscriptP1</p>
|
||||
<p>@Strings.NoscriptP2</p>
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
<div class="content_outer content_outer--background_color content_grid">
|
||||
|
||||
<div class="content_inner content_inner--inherit_max_width_no_margin">
|
||||
|
||||
<div class="grid_container height_fixed">
|
||||
|
||||
@*Datatables plugin for jQuery. www.datatables.net*@
|
||||
<table id="jobTable" class="order-column">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>@Strings.StatusHeader</th>
|
||||
<th>@Strings.JobNameHeader</th>
|
||||
<th>@Strings.QueryNameHeader</th>
|
||||
<th>@Strings.OperationsHeader</th>
|
||||
<th>@Strings.StartTimeHeader</th>
|
||||
<th>@Strings.EndTimeHeader</th>
|
||||
<th>@Strings.DeviceCountHeader</th>
|
||||
<th>@Strings.SucceedCountHeader</th>
|
||||
<th>@Strings.FailedCountHeader</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody></tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="details_grid_closed height_fixed button_details_grid">
|
||||
<h2 class="details_grid_closed__grid_subhead">@Strings.JobPropertiesPaneLabel</h2>
|
||||
</div>
|
||||
|
||||
<div class="details_grid height_fixed">
|
||||
|
||||
<h2 class="details_grid__grid_subhead button_details_grid">@Strings.JobPropertiesPaneLabel</h2>
|
||||
|
||||
<div id="details_grid_container">
|
||||
<div class="details_grid__no_selection">
|
||||
@Strings.NoJobSelectedLabel
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="loadingElement" class="loader_container loader_container_details">
|
||||
<div class="loader_container__loader loader_container__loader--large_top_margin"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
var resources = {
|
||||
retry: '@Html.JavaScriptString(Strings.Retry)',
|
||||
serviceUnavailable: '@Html.JavaScriptString(Strings.ServiceUnavailable)',
|
||||
allJobStatus: [
|
||||
'@JobStatus.Unknown',
|
||||
'@JobStatus.Enqueued',
|
||||
'@JobStatus.Running',
|
||||
'@JobStatus.Completed',
|
||||
'@JobStatus.Failed',
|
||||
'@JobStatus.Cancelled',
|
||||
'@JobStatus.Scheduled',
|
||||
'@JobStatus.Queued',
|
||||
],
|
||||
nextPage: '@Html.JavaScriptString(Strings.Next)',
|
||||
previousPage: '@Html.JavaScriptString(Strings.Previous)',
|
||||
jobsList: '@Html.JavaScriptString(Strings.Jobs)',
|
||||
unableToRetrieveJobFromService: '@Html.JavaScriptString(Strings.UnableToRetrieveJobFromService)',
|
||||
failedToRetrieveJobs: '@Html.JavaScriptString(Strings.FailedToRetrieveJobs)',
|
||||
failedToCancelJob: '@Html.JavaScriptString(Strings.FailedToCancelJob)',
|
||||
noJobSelected: '@Html.JavaScriptString(Strings.NoJobSelected)'
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<script src="~/Scripts/jquery-datatables-api-extensions.js"></script>
|
||||
<script src="~/Scripts/Views/Job/JobIndex.js"></script>
|
||||
<script src="~/Scripts/Views/Job/JobProperties.js"></script>
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<header class="header_main">
|
||||
|
||||
<button class="header_main__button_back" type="button"></button>
|
||||
<button class="header_main__button_back" type="button" data-bind="click: backButtonClicked"></button>
|
||||
<h2 class="header_main__subhead header_main__subhead--large">@Strings.InvokeMethod <span class="item__description">@Model.QueryName</span></h2>
|
||||
</header>
|
||||
<div class="content_outer">
|
||||
|
@ -29,24 +29,25 @@
|
|||
|
||||
<fieldset class="edit_form">
|
||||
@{
|
||||
@Html.LabelFor(m => m.JobName, Strings.ScheduleJobName, new { @class = "edit_form__label" });
|
||||
@Html.LabelFor(m => m.JobName, Strings.ScheduleJobName, new { @class = "edit_form__label primary__label" });
|
||||
@Html.TextBoxFor(m => m.JobName, new { placeholder = Strings.ScheduleJobNamePlaceHolder, data_bind = "value: jobName", @class = "edit_form__text" });
|
||||
|
||||
<div style="clear:both"></div>
|
||||
@Html.LabelFor(m => m.MethodName, Strings.InvokeMethodLabel, new { @class = "edit_form__label" });
|
||||
@Html.TextBoxFor(m => m.MethodName, new { data_bind = "textInput: methodName, event{ blur: $root.updateParameter }", @class = "name_selector__text edit_form__text" });
|
||||
|
||||
@Html.LabelFor(m => m.MethodName, Strings.InvokeMethodLabel, new { @class = "edit_form__label primary__label" });
|
||||
@Html.TextBoxFor(m => m.MethodName, new { data_bind = "textInput: methodName, event{ blur: $root.updateParameter }", @class = "edit_form__methodComboBox edit_form__text" });
|
||||
|
||||
<div style="clear:both"></div>
|
||||
|
||||
<div>
|
||||
<span class="text_small" data-bind="text: getMatchedDevices('@Strings.SomeDevicesApplicable')"></span><a href="/Device/Query">query</a>
|
||||
<span class="text_small" data-bind="text: getMatchedDevices('@Strings.SomeDevicesApplicable')"></span> <a class="edit_form__link" href="/Device/Query">query</a>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text_small" data-bind="text: getUnmatchedDevices('@Strings.SomeDevicesApplicable')"></span><a href="/Device/Query">query</a>
|
||||
<span class="text_small" data-bind="text: getUnmatchedDevices('@Strings.SomeDeviceInapplicable')"></span> <a class="edit_form__link" href="/Device/Query">query</a>
|
||||
</div>
|
||||
|
||||
<div style="clear:both"></div>
|
||||
<label for="parameterTable" class="edit_form__label" data-bind="if: parameters">@Strings.ScheduleParameters</label>
|
||||
<label for="parameterTable" class="edit_form__label primary__label" data-bind="if: parameters">@Strings.ScheduleParameters</label>
|
||||
<table class="values_editor" id="paramterTable" data-bind="if: parameters">
|
||||
<thead>
|
||||
<tr class="values_editor__groupheader">
|
||||
|
@ -70,18 +71,18 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<label for="scheduleJobTime" class="edit_form__label">@Strings.ScheduleSetJobTime</label>
|
||||
<label for="scheduleJobTime" class="edit_form__label primary__label" >@Strings.ScheduleSetJobTime</label>
|
||||
|
||||
<div id="scheduleJobTime" class="values_editor">
|
||||
<div class="values_editor__group">
|
||||
<label class="edit_form__labelHeader">@Strings.StartTime</label>
|
||||
<input type='text' id="StartDate" name="StartDateUtc" class="edit_form__text non_float" data-bind="dateTimePicker: startDate ,dateTimePickerOptions:{ format: 'LLL' , locale: window.navigator.language}"
|
||||
<input type='text' id="StartDate" name="StartDateUtc" class="non_float edit_form__text less_margin" data-bind="dateTimePicker: startDate ,dateTimePickerOptions:{ widgetPositioning : {horizontal: 'left' ,vertical: 'top'},format: 'LLL' , locale: getCulture()}"
|
||||
data-msg-required="@Strings.RequiredValidationString"
|
||||
data-rule-required="true" />
|
||||
</div>
|
||||
<div class="values_editor__group">
|
||||
<label class="edit_form__labelHeader">@Strings.MaxExecutionTime</label>
|
||||
<input type="text" class="edit_form__textsmall non_float" name="MaxExecutionTimeInMinutes" data-bind="value: maxExecutionTime"
|
||||
<input type="text" class="non_float edit_form__textsmall less_margin" name="MaxExecutionTimeInMinutes" data-bind="value: maxExecutionTime"
|
||||
data-msg-required="@Strings.RequiredValidationString"
|
||||
data-rule-required="true" />
|
||||
<span>Mins</span>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<header class="header_main">
|
||||
|
||||
<button class="header_main__button_back" type="button"></button>
|
||||
<button class="header_main__button_back" type="button" data-bind="click: backButtonClicked"></button>
|
||||
<h2 class="header_main__subhead header_main__subhead--large">@Strings.ScheduleEditPropertiesOrTags <span class="item__description">@Model.QueryName</span></h2>
|
||||
</header>
|
||||
<div class="content_outer">
|
||||
|
@ -29,11 +29,11 @@
|
|||
|
||||
<fieldset class="edit_form">
|
||||
@{
|
||||
@Html.LabelFor(m => m.JobName, Strings.ScheduleJobName, new { @class = "edit_form__label" });
|
||||
@Html.LabelFor(m => m.JobName, Strings.ScheduleJobName, new { @class = "edit_form__label primary__label" });
|
||||
@Html.TextBoxFor(m => m.JobName, new { placeholder = Strings.ScheduleJobNamePlaceHolder, data_bind = "value: jobName", @class = "edit_form__text" });
|
||||
|
||||
<div style="clear:both"></div>
|
||||
<label for="propertiesTable" class="edit_form__label">@Strings.ScheduleChangeDesiredProperties</label>
|
||||
<label for="propertiesTable" class="edit_form__label primary__label">@Strings.ScheduleChangeDesiredProperties</label>
|
||||
<table class="values_editor" id="propertiesTable">
|
||||
<thead>
|
||||
<tr class="values_editor__groupheader">
|
||||
|
@ -65,7 +65,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<label for="tagsTable" class="edit_form__label">@Strings.ScheduleChangeTags</label>
|
||||
<label for="tagsTable" class="edit_form__label primary__label">@Strings.ScheduleChangeTags</label>
|
||||
<table class="values_editor" id="tagsTable">
|
||||
<thead>
|
||||
<tr class="values_editor__groupheader">
|
||||
|
@ -91,23 +91,23 @@
|
|||
data-bind='check: isDeleted, attr: { name: "Tags[" + $index() + "].isDeleted", id: "Tags" + $index() + ".isDeleted" }' />
|
||||
<input type="hidden" value="false" name="checkbox" data-bind='attr:{ name: "Tags[" + $index() + "].isDeleted" }' />
|
||||
</td>
|
||||
<td><a data-bind="ifnot:$root.onetagleft , click:$root.removeTag">@Strings.Clear</a></td>
|
||||
<td><a class="edit_form__link" data-bind="ifnot:$root.onetagleft , click:$root.removeTag">@Strings.Clear</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<label for="scheduleJobTime" class="edit_form__label">@Strings.ScheduleSetJobTime</label>
|
||||
<label for="scheduleJobTime" class="edit_form__label primary__label">@Strings.ScheduleSetJobTime</label>
|
||||
|
||||
<div id="scheduleJobTime" class="values_editor">
|
||||
<div class="values_editor__group">
|
||||
<label class="edit_form__labelHeader">@Strings.StartTime</label>
|
||||
<input type='text' id="StartDate" name="StartDateUtc" class="edit_form__text non_float" data-bind="dateTimePicker: startDate ,dateTimePickerOptions:{ format: 'LLL', locale: window.navigator.language}"
|
||||
<input type='text' id="StartDate" name="StartDateUtc" class="non_float edit_form__text less_margin" data-bind="dateTimePicker: startDate ,dateTimePickerOptions:{ widgetPositioning : {horizontal: 'left' ,vertical: 'top'}, format: 'LLL', locale: getCulture() }"
|
||||
data-msg-required="@Strings.RequiredValidationString"
|
||||
data-rule-required="true" />
|
||||
</div>
|
||||
<div class="values_editor__group">
|
||||
<label class="edit_form__labelHeader">@Strings.MaxExecutionTime</label>
|
||||
<input type="text" class="edit_form__textsmall non_float" name="MaxExecutionTimeInMinutes" data-bind="value: maxExecutionTime"
|
||||
<input type="text" class="non_float less_margin edit_form__textsmall" name="MaxExecutionTimeInMinutes" data-bind="value: maxExecutionTime"
|
||||
data-msg-required="@Strings.RequiredValidationString"
|
||||
data-rule-required="true" />
|
||||
<span>Mins</span>
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
@using GlobalResources
|
||||
@using Microsoft.Azure.Devices
|
||||
@using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Security
|
||||
@model Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Models.DeviceJobModel
|
||||
|
||||
@if (PermsChecker.HasPermission(Permission.ManageJobs))
|
||||
{
|
||||
<div class="header_grid header_grid_general">
|
||||
<h3 class="grid_subheadhead_detail">@Strings.Actions</h3>
|
||||
</div>
|
||||
|
||||
<section class="details_grid_actions">
|
||||
|
||||
<p class="grid_detail_value">
|
||||
<a id="cloneJobAction">
|
||||
@Strings.CloneJobAction
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@if (Model.Status == JobStatus.Scheduled || Model.Status == JobStatus.Enqueued || Model.Status == JobStatus.Queued)
|
||||
{
|
||||
<p class="grid_detail_value">
|
||||
<a id="cancelJobAction">
|
||||
@Strings.CancelJobAction
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
|
||||
<div class="header_grid header_grid_general">
|
||||
<h3 class="grid_subheadhead_detail">@Strings.JobProperties</h3>
|
||||
</div>
|
||||
|
||||
<section class="details_grid_general" id="jobDetailsGrid">
|
||||
<h4 class="grid_subhead_detail_label">@Strings.JobId</h4>
|
||||
<p class="grid_detail_value" name="deviceField_jobId">@Model.JobId</p>
|
||||
|
||||
<h4 class="grid_subhead_detail_label">@Strings.StatusHeader</h4>
|
||||
<p class="grid_detail_value" name="deviceField_status">@Model.Status</p>
|
||||
|
||||
<h4 class="grid_subhead_detail_label">@Strings.JobNameHeader</h4>
|
||||
<p class="grid_detail_value" name="deviceField_jobName">@Model.JobName</p>
|
||||
|
||||
<h4 class="grid_subhead_detail_label">@Strings.QueryNameHeader</h4>
|
||||
<p class="grid_detail_value" name="deviceField_queryName">@Model.QueryName</p>
|
||||
|
||||
<h4 class="grid_subhead_detail_label">@Strings.OperationsHeader</h4>
|
||||
<p class="grid_detail_value" name="deviceField_operationType">@Model.OperationType</p>
|
||||
|
||||
<h4 class="grid_subhead_detail_label">@Strings.StartTimeHeader</h4>
|
||||
<p class="grid_detail_value" name="deviceField_startTime">@Model.StartTime</p>
|
||||
|
||||
<h4 class="grid_subhead_detail_label">@Strings.EndTimeHeader</h4>
|
||||
<p class="grid_detail_value" name="deviceField_endTime">@Model.EndTime</p>
|
||||
</section>
|
||||
|
||||
<div class="header_grid header_grid_general">
|
||||
<h3 class="grid_subheadhead_detail">@Strings.JobStatistics</h3>
|
||||
</div>
|
||||
|
||||
<section class="details_grid_general" id="deviceGrid">
|
||||
<h4 class="grid_subhead_detail_label">@Strings.DeviceCountSubHeader</h4>
|
||||
<p class="grid_detail_value" name="deviceField_deviceCount">@Model.DeviceCount</p>
|
||||
|
||||
<h4 class="grid_subhead_detail_label">@Strings.SucceededCountSubHeader</h4>
|
||||
<p class="grid_detail_value" name="deviceField_succeedCount">@Model.SucceededCount</p>
|
||||
|
||||
<h4 class="grid_subhead_detail_label">@Strings.FailedCountSubHeader</h4>
|
||||
<p class="grid_detail_value" name="deviceField_failedCount">@Model.FailedCount</p>
|
||||
|
||||
<h4 class="grid_subhead_detail_label">@Strings.PendingCountSubHeader</h4>
|
||||
<p class="grid_detail_value" name="deviceField_pendingCount">@Model.PendingCount</p>
|
||||
|
||||
<h4 class="grid_subhead_detail_label">@Strings.RunningCountSubHeader</h4>
|
||||
<p class="grid_detail_value" name="deviceField_runningCount">@Model.RunningCount</p>
|
||||
</section>
|
||||
|
|
@ -21,11 +21,11 @@
|
|||
};
|
||||
</script>
|
||||
@Scripts.Render("~/bundles/powerbi-visuals")
|
||||
@Scripts.Render("~/bundles/bootstrap")
|
||||
@Scripts.Render("~/bundles/jqueryval")
|
||||
@Scripts.Render("~/bundles/jquerytable")
|
||||
@Scripts.Render("~/bundles/jqueryui")
|
||||
@Scripts.Render("~/bundles/knockout")
|
||||
@Scripts.Render("~/bundles/bootstrap")
|
||||
<script src="~/Scripts/moment-with-locales.min.js"></script>
|
||||
@Scripts.Render("~/bundles/bootstrapdatetime")
|
||||
|
||||
|
|
|
@ -269,7 +269,7 @@
|
|||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="App_GlobalResources\Strings.Designer.cs">
|
||||
<Compile Include="App_GlobalResources\Strings.designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Strings.resx</DependentUpon>
|
||||
|
@ -295,6 +295,7 @@
|
|||
<Compile Include="DataTables\DataTablesResponse.cs" />
|
||||
<Compile Include="DataTables\Search.cs" />
|
||||
<Compile Include="DataTables\SortColumn.cs" />
|
||||
<Compile Include="Extensions\JobTypeExtension.cs" />
|
||||
<Compile Include="Helpers\ICellularExtensions.cs" />
|
||||
<Compile Include="Helpers\CultureHelper.cs" />
|
||||
<Compile Include="Helpers\HtmlHelperExtensions.cs" />
|
||||
|
@ -307,6 +308,7 @@
|
|||
<Compile Include="Models\EditDeviceRuleModel.cs" />
|
||||
<Compile Include="Models\PreScheduleJobModel.cs" />
|
||||
<Compile Include="Models\ScheduleDeviceMethodModel.cs" />
|
||||
<Compile Include="Models\DeviceJobModel.cs" />
|
||||
<Compile Include="Models\LanguageModel.cs" />
|
||||
<Compile Include="Models\NamedDeviceJob.cs" />
|
||||
<Compile Include="Models\NamedJobResponseModel.cs" />
|
||||
|
@ -355,6 +357,7 @@
|
|||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Startup.cs" />
|
||||
<Compile Include="WebApiControllers\DeviceRulesApiController.cs" />
|
||||
<Compile Include="WebApiControllers\JobIndicatorsApiController.cs" />
|
||||
<Compile Include="WebApiControllers\QueryGeneratorApiController.cs" />
|
||||
<Compile Include="WebApiControllers\QueryNameApiController.cs" />
|
||||
<Compile Include="WebApiControllers\QueryApiController.cs" />
|
||||
|
@ -385,6 +388,7 @@
|
|||
<Content Include="Content\img\icon_jasper.svg" />
|
||||
<Content Include="Content\img\icon_save.png" />
|
||||
<Content Include="Content\img\icon_saveas.png" />
|
||||
<Content Include="Content\img\icon_jobs.svg" />
|
||||
<Content Include="Content\styles\datatables.css" />
|
||||
<Content Include="Content\favicon.ico" />
|
||||
<Content Include="Content\img\button_back.svg" />
|
||||
|
@ -469,6 +473,7 @@
|
|||
<Content Include="Content\styles\bootstrap-datetimepicker-build.less" />
|
||||
<Content Include="Content\styles\_bootstrap-datetimepicker.less" />
|
||||
<Content Include="Content\styles\bootstrap\glyphicon.less" />
|
||||
<Content Include="Content\styles\bootstrap\popovers.less" />
|
||||
<None Include="Scripts\jquery-2.2.3.intellisense.js" />
|
||||
<Content Include="Scripts\bootstrap-datetimepicker.min.js" />
|
||||
<Content Include="Scripts\bootstrap.min.js" />
|
||||
|
@ -512,6 +517,7 @@
|
|||
<Content Include="Scripts\js.cookie-1.5.1.min.js" />
|
||||
<Content Include="Scripts\knockout-3.4.0.debug.js" />
|
||||
<Content Include="Scripts\knockout-3.4.0.js" />
|
||||
<Content Include="Scripts\knockout.mapping-2.0.js" />
|
||||
<Content Include="Scripts\moment-with-locales.js" />
|
||||
<Content Include="Scripts\moment-with-locales.min.js" />
|
||||
<Content Include="Scripts\moment.js" />
|
||||
|
@ -522,6 +528,7 @@
|
|||
<Content Include="Scripts\Views\Advanced\Advanced.js" />
|
||||
<Content Include="Scripts\Views\Dashboard\AlertHistory.js" />
|
||||
<Content Include="Scripts\Views\Dashboard\DashboardDevicePane.js" />
|
||||
<Content Include="Scripts\Views\Dashboard\JobIndicators.js" />
|
||||
<Content Include="Scripts\Views\Dashboard\MapPane.js" />
|
||||
<Content Include="Scripts\Views\Dashboard\TelemetryHistory.js" />
|
||||
<Content Include="Scripts\Views\Dashboard\TelemetryHistorySummary.js" />
|
||||
|
@ -580,11 +587,15 @@
|
|||
<Content Include="Scripts\Views\Device\DeviceIndex.js" />
|
||||
<Content Include="Scripts\Views\Device\DeviceListColumns.js" />
|
||||
<Content Include="Scripts\Views\Device\DeviceSelectType.js" />
|
||||
<Content Include="Scripts\Views\Device\EditTags.js" />
|
||||
<Content Include="Scripts\Views\Device\EditDesiredProperties.js" />
|
||||
<Content Include="Scripts\Views\Device\EditDeviceProperties.js" />
|
||||
<Content Include="Scripts\Views\Device\RemoveDevice.js" />
|
||||
<Content Include="Scripts\Views\IoTApp.js" />
|
||||
<Content Include="Scripts\Views\Job\ScheduleDeviceMethod.js" />
|
||||
<Content Include="Scripts\Views\Job\ScheduleTwinUpdate.js" />
|
||||
<Content Include="Scripts\Views\Job\JobIndex.js" />
|
||||
<Content Include="Scripts\Views\Job\JobProperties.js" />
|
||||
<Content Include="Scripts\_references.js" />
|
||||
<Content Include="Scripts\jquery.dataTables.min.js" />
|
||||
<Content Include="Scripts\jquery.unobtrusive-ajax.js" />
|
||||
|
@ -673,6 +684,11 @@
|
|||
<Content Include="Views\DeviceMethod\_InvokeMethod.cshtml" />
|
||||
<Content Include="Views\DeviceMethod\_InvokeMethodForm.cshtml" />
|
||||
<Content Include="Views\DeviceMethod\Index.cshtml" />
|
||||
<Content Include="Views\Job\Index.cshtml" />
|
||||
<Content Include="Views\Job\_JobProperties.cshtml" />
|
||||
<Content Include="Views\Dashboard\_JobIndicators.cshtml" />
|
||||
<None Include="Views\Device\EditDesiredProperties.cshtml" />
|
||||
<None Include="Views\Device\EditTags.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="packages.config">
|
||||
|
@ -730,9 +746,9 @@
|
|||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="App_GlobalResources\Strings.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Strings.designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
<CustomToolNamespace>GlobalResources</CustomToolNamespace>
|
||||
<LastGenOutput>Strings.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="App_GlobalResources\Strings.ru.resx">
|
||||
<Generator>GlobalResourceProxyGenerator</Generator>
|
||||
|
@ -765,9 +781,7 @@
|
|||
<Name>Infrastructure</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Extensions\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
|
|
|
@ -1,35 +1,55 @@
|
|||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Security;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infrastructure.Repository;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Security;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Extensions;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.WebApiControllers
|
||||
{
|
||||
[RoutePrefix("api/v1/devices")]
|
||||
public class DeviceTwinApiController : WebApiControllerBase
|
||||
{
|
||||
public DeviceTwinApiController()
|
||||
private IIoTHubDeviceManager _deviceManager;
|
||||
public DeviceTwinApiController(IIoTHubDeviceManager deviceManager)
|
||||
{
|
||||
this._deviceManager = deviceManager;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{deviceId}/twin")]
|
||||
[Route("{deviceId}/twin/desired")]
|
||||
[WebApiRequirePermission(Permission.ViewDevices)]
|
||||
public async Task<HttpResponseMessage> GetDeviceTwin(string deviceId)
|
||||
public async Task<HttpResponseMessage> GetDeviceTwinDesired(string deviceId)
|
||||
{
|
||||
var twin = new Twin(deviceId);
|
||||
return await GetServiceResponseAsync<Twin>(async () =>
|
||||
var twin = await this._deviceManager.GetTwinAsync(deviceId);
|
||||
IEnumerable<KeyValuePair<string,TwinCollectionExtension.TwinValue>> flattenTwin = twin.Properties.Desired.AsEnumerableFlatten();
|
||||
return await GetServiceResponseAsync<IEnumerable<KeyValuePair<string, TwinCollectionExtension.TwinValue>>>(async () =>
|
||||
{
|
||||
return await Task.FromResult(twin);
|
||||
return await Task.FromResult(flattenTwin);
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{deviceId}/twin/tag")]
|
||||
[WebApiRequirePermission(Permission.ViewDevices)]
|
||||
public async Task<HttpResponseMessage> GetDeviceTwinTag(string deviceId)
|
||||
{
|
||||
var twin = await this._deviceManager.GetTwinAsync(deviceId);
|
||||
IEnumerable<KeyValuePair<string, TwinCollectionExtension.TwinValue>> flattenTwin = twin.Tags.AsEnumerableFlatten();
|
||||
return await GetServiceResponseAsync<IEnumerable<KeyValuePair<string, TwinCollectionExtension.TwinValue>>>(async () =>
|
||||
{
|
||||
return await Task.FromResult(flattenTwin);
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[Route("{deviceId}/twin")]
|
||||
[WebApiRequirePermission(Permission.ViewDevices)]
|
||||
public async Task<HttpResponseMessage> UpdateDeviceTwin(string deviceId)
|
||||
public async Task<HttpResponseMessage> UpdateDeviceTwin(string deviceId, Twin twin )
|
||||
{
|
||||
var twin = new Twin(deviceId);
|
||||
//var twin = new Twin(deviceId);
|
||||
return await GetServiceResponseAsync<Twin>(async () =>
|
||||
{
|
||||
return await Task.FromResult(twin);
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infrastructure.Models;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Security;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.DataTables;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Models;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Security;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infrastructure.Models;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infrastructure.Repository;
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.WebApiControllers
|
||||
{
|
||||
[RoutePrefix("api/v1/jobs")]
|
||||
public class JobApiController : WebApiControllerBase
|
||||
{
|
||||
public JobApiController()
|
||||
private readonly IJobRepository _jobRepository;
|
||||
private readonly IIoTHubDeviceManager _iotHubDeviceManager;
|
||||
|
||||
public JobApiController(IJobRepository jobRepository, IIoTHubDeviceManager iotHubDeviceManager)
|
||||
{
|
||||
_jobRepository = jobRepository;
|
||||
_iotHubDeviceManager = iotHubDeviceManager;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
@ -20,14 +29,20 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.
|
|||
// GET: api/v1/jobs
|
||||
public async Task<HttpResponseMessage> GetJobs()
|
||||
{
|
||||
//TODO: mock code: query Job
|
||||
var jobs = new List<Job>();
|
||||
jobs.Add(new Job() { Name = "sample job1", StatusMessage = "Need to mock some of this using reflection." });
|
||||
|
||||
return await GetServiceResponseAsync<IEnumerable<Job>>(async () =>
|
||||
return await GetServiceResponseAsync<DataTablesResponse<DeviceJobModel>>(async () =>
|
||||
{
|
||||
return await Task.FromResult(jobs);
|
||||
});
|
||||
var jobResponses = await _iotHubDeviceManager.GetJobResponsesAsync();
|
||||
|
||||
var result = jobResponses.OrderByDescending(j => j.CreatedTimeUtc).Select(r => new DeviceJobModel(r)).ToList();
|
||||
var dataTablesResponse = new DataTablesResponse<DeviceJobModel>()
|
||||
{
|
||||
RecordsTotal = result.Count,
|
||||
Data = result.ToArray()
|
||||
};
|
||||
|
||||
return await Task.FromResult(dataTablesResponse);
|
||||
|
||||
}, false);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
@ -48,47 +63,16 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.
|
|||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("")]
|
||||
[WebApiRequirePermission(Permission.ManageJobs)]
|
||||
// Post: api/v1/jobs
|
||||
public async Task<HttpResponseMessage> ScheduleJob()
|
||||
{
|
||||
//TODO: mock code: Add Job
|
||||
var job = new Job() { Id = "jobid1", Name = "frist job", OperationType = Job.JobOperationType.EditPropertyOrTag, QueryName = "sample query 1" };
|
||||
return await GetServiceResponseAsync<Job>(async () =>
|
||||
{
|
||||
return await Task.FromResult(job);
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{id}")]
|
||||
[WebApiRequirePermission(Permission.ViewJobs)]
|
||||
// GET: api/v1/jobs/{id}
|
||||
public async Task<HttpResponseMessage> GetJobById(string jobId)
|
||||
{
|
||||
//TODO: mock code: query Job
|
||||
var jobs = new List<Job>();
|
||||
jobs.Add(new Job() { Id = "jobid1", Name = "frist job", OperationType = Job.JobOperationType.EditPropertyOrTag, QueryName = "sample query 1" });
|
||||
jobs.Add(new Job() { Id = "jobid2", Name = "second job", OperationType = Job.JobOperationType.InvokeMethod, QueryName = "sample query 2" });
|
||||
|
||||
return await GetServiceResponseAsync<IEnumerable<Job>>(async () =>
|
||||
{
|
||||
return await Task.FromResult(jobs);
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[Route("{id}/cancel")]
|
||||
[WebApiRequirePermission(Permission.ManageJobs)]
|
||||
// GET: api/v1/jobs/{id}/cancel
|
||||
public async Task<HttpResponseMessage> CancelJob(string jobId)
|
||||
// PUT: api/v1/jobs/{id}/cancel
|
||||
public async Task<HttpResponseMessage> CancelJob(string id)
|
||||
{
|
||||
//TODO: mock code: cancel job
|
||||
return await GetServiceResponseAsync<bool>(async () =>
|
||||
return await GetServiceResponseAsync<DeviceJobModel>(async () =>
|
||||
{
|
||||
return await Task.FromResult(true);
|
||||
var jobResponse = await _iotHubDeviceManager.CancelJobByJobIdAsync(id);
|
||||
return new DeviceJobModel(jobResponse);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -100,7 +84,6 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.
|
|||
{
|
||||
//TODO: mock code: cancel job
|
||||
var devices = new List<string>() { "SampleDevice1", "SampleDevice2" };
|
||||
|
||||
return await GetServiceResponseAsync<IEnumerable<string>>(async () =>
|
||||
{
|
||||
return await Task.FromResult(devices);
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using GlobalResources;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Infrastructure.Repository;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Security;
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.WebApiControllers
|
||||
{
|
||||
public class JobIndicatorsApiController : WebApiControllerBase
|
||||
{
|
||||
private readonly IIoTHubDeviceManager _iotHubDeviceManager;
|
||||
|
||||
public JobIndicatorsApiController(IIoTHubDeviceManager iotHubDeviceManager)
|
||||
{
|
||||
_iotHubDeviceManager = iotHubDeviceManager;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("api/v1/jobIndicators/values")]
|
||||
[WebApiRequirePermission(Permission.ViewJobs)]
|
||||
public async Task<IEnumerable<string>> GetValues([FromUri] IEnumerable<string> indicators)
|
||||
{
|
||||
var results = new List<string>();
|
||||
|
||||
foreach (var indicator in indicators)
|
||||
{
|
||||
try
|
||||
{
|
||||
results.Add((await GetIndicatorValue(indicator)).ToString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
results.Add(Strings.NotAvilable);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("api/v1/jobIndicators/definitions")]
|
||||
[WebApiRequirePermission(Permission.ViewJobs)]
|
||||
public async Task<IEnumerable<object>> GetDefinitions()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
title = Strings.ActiveJobs,
|
||||
id = "activeJobs"
|
||||
},
|
||||
new
|
||||
{
|
||||
title = Strings.ScheduledJobs,
|
||||
id = "scheduledJobs"
|
||||
},
|
||||
new
|
||||
{
|
||||
title = Strings.FailedJobsInLast24Hours,
|
||||
id = "failedJobsInLast24Hours"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<int> GetIndicatorValue(string indicator)
|
||||
{
|
||||
// ToDo: use a more flexiable indicator naming, such as "<status>[(timespan)]", e.g. "active", "failed(PT24H)"
|
||||
switch (indicator)
|
||||
{
|
||||
case "activeJobs":
|
||||
return await GetActiveJobs();
|
||||
|
||||
case "scheduledJobs":
|
||||
return await GetScheduledJobs();
|
||||
|
||||
case "failedJobsInLast24Hours":
|
||||
return await GetFailedJobsInLast24Hours();
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> GetActiveJobs()
|
||||
{
|
||||
var jobs = await _iotHubDeviceManager.GetJobResponsesByStatus(JobStatus.Running);
|
||||
return jobs.Count();
|
||||
}
|
||||
|
||||
private async Task<int> GetScheduledJobs()
|
||||
{
|
||||
var jobs = await _iotHubDeviceManager.GetJobResponsesByStatus(JobStatus.Scheduled);
|
||||
return jobs.Count();
|
||||
}
|
||||
|
||||
private async Task<int> GetFailedJobsInLast24Hours()
|
||||
{
|
||||
var jobs = await _iotHubDeviceManager.GetJobResponsesByStatus(JobStatus.Failed);
|
||||
|
||||
var oneDayAgoUtc = DateTime.UtcNow - TimeSpan.FromDays(1);
|
||||
return jobs.Count(j => j.CreatedTimeUtc < oneDayAgoUtc);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ static bool g_continueRunning;
|
|||
#define DOWORK_LOOP_NUM 3
|
||||
|
||||
static char connectionString[1024] = { 0 };
|
||||
static char reportedProperties[1024] = { 0 };
|
||||
static char reportedProperties[4096] = { 0 };
|
||||
|
||||
typedef struct EVENT_INSTANCE_TAG
|
||||
{
|
||||
|
|
Двоичные данные
Simulator/Simulator.WebJob/DMSimulator.exe
Двоичные данные
Simulator/Simulator.WebJob/DMSimulator.exe
Двоичный файл не отображается.
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Configurations;
|
||||
|
@ -17,6 +18,7 @@ using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Simulator.WebJob.Sim
|
|||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Simulator.WebJob.SimulatorCore.Transport.Factory;
|
||||
using Microsoft.Azure.Devices.Common.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Simulator.WebJob.SimulatorCore.Devices
|
||||
{
|
||||
|
@ -119,7 +121,7 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Simulator.WebJob
|
|||
{
|
||||
DeviceModel device = DeviceCreatorHelper.BuildDeviceStructure(DeviceID, true, null);
|
||||
device.DeviceProperties = this.DeviceProperties;
|
||||
device.Commands = this.Commands ?? new List<Command>();
|
||||
device.Commands = this.Commands?.Where(c => c.DeliveryType == DeliveryType.Message).ToList() ?? new List<Command>();
|
||||
device.Telemetry = this.Telemetry ?? new List<Common.Models.Telemetry>();
|
||||
device.Version = SampleDeviceFactory.VERSION_1_0;
|
||||
device.ObjectType = SampleDeviceFactory.OBJECT_TYPE_DEVICE_INFO;
|
||||
|
@ -180,7 +182,11 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Simulator.WebJob
|
|||
var deviceConnectionString = Client.IotHubConnectionStringBuilder.Create(HostName, authMethod).ToString();
|
||||
|
||||
// Device properties (InstalledRAM, Processor, etc.) should be treat as reported proerties
|
||||
var reportedProperties = JsonConvert.SerializeObject(DeviceProperties, Formatting.Indented, new JsonSerializerSettings
|
||||
// Supported methods should be treat as reported property
|
||||
var jObject = JObject.FromObject(DeviceProperties);
|
||||
jObject.Merge(JObject.FromObject(SupportedMethodsHelper.GenerateSupportedMethodsReportedProperty(Commands)));
|
||||
|
||||
var reportedProperties = JsonConvert.SerializeObject(jObject, Formatting.Indented, new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver = new SkipByNameContractResolver("DeviceID")
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Helpers;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Models;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Models.Commands;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.UnitTests.Common
|
||||
{
|
||||
public class SupportedMethodsHelperTests
|
||||
{
|
||||
[Fact]
|
||||
public void GenerateSupportedMethodsReportedPropertyTest()
|
||||
{
|
||||
var commands = new List<Command>() {
|
||||
// Method with parameters
|
||||
new Command("method1", DeliveryType.Method, "desc1", new List<Parameter>() {
|
||||
new Parameter("p1", "string"),
|
||||
new Parameter("p2", "int")
|
||||
}),
|
||||
// Command, should be ignored
|
||||
new Command("command1", DeliveryType.Method, "desc1", new List<Parameter>() {
|
||||
new Parameter("p1", "int"),
|
||||
new Parameter("p2", "string")
|
||||
}),
|
||||
// Method without parameters
|
||||
new Command("method2", DeliveryType.Method, "desc2"),
|
||||
// Method name with _
|
||||
new Command("method_3", DeliveryType.Method, "desc3"),
|
||||
// Method without name, should be ignored
|
||||
new Command("", DeliveryType.Method, "desc2"),
|
||||
// parameter with no type, should be ignored
|
||||
new Command("method4", DeliveryType.Method, "desc1", new List<Parameter>() {
|
||||
new Parameter("p1", ""),
|
||||
new Parameter("p2", "int")
|
||||
}),
|
||||
};
|
||||
|
||||
var property = SupportedMethodsHelper.GenerateSupportedMethodsReportedProperty(commands);
|
||||
|
||||
JObject supportedMethods = property["SupportedMethods"] as JObject;
|
||||
Assert.Equal(supportedMethods.Count, commands.Where(c => c.DeliveryType == DeliveryType.Method).Count());
|
||||
|
||||
Assert.Equal(supportedMethods["method1_string_int"]["Name"].ToString(), "method1");
|
||||
Assert.Equal(supportedMethods["method1_string_int"]["Description"].ToString(), "desc1");
|
||||
Assert.Equal(supportedMethods["method1_string_int"]["Parameters"]["p1"]["Type"].ToString(), "string");
|
||||
Assert.Equal(supportedMethods["method1_string_int"]["Parameters"]["p2"]["Type"].ToString(), "int");
|
||||
|
||||
Assert.Equal(supportedMethods["method2"]["Name"].ToString(), "method2");
|
||||
Assert.Equal(supportedMethods["method2"]["Description"].ToString(), "desc2");
|
||||
|
||||
Assert.Equal(supportedMethods["method__3"]["Name"].ToString(), "method_3");
|
||||
|
||||
var device = new DeviceModel();
|
||||
var twin = new Twin();
|
||||
twin.Properties.Reported["SupportedMethods"] = supportedMethods;
|
||||
|
||||
SupportedMethodsHelper.AddSupportedMethodsFromReportedProperty(device, twin);
|
||||
Assert.Equal(supportedMethods.Count - 2, device.Commands.Count);
|
||||
foreach(var command in device.Commands)
|
||||
{
|
||||
var srcCommand = commands.FirstOrDefault(c => c.Name == command.Name);
|
||||
Assert.Equal(command.Name, srcCommand.Name);
|
||||
Assert.Equal(command.Description, srcCommand.Description);
|
||||
Assert.Equal(command.Parameters.Count, srcCommand.Parameters.Count);
|
||||
|
||||
foreach (var parameter in command.Parameters)
|
||||
{
|
||||
var srcParameter = srcCommand.Parameters.FirstOrDefault(p => p.Name == parameter.Name);
|
||||
Assert.Equal(parameter.Name, srcParameter.Name);
|
||||
Assert.Equal(parameter.Type, srcParameter.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -202,6 +202,7 @@
|
|||
<Compile Include="Common\StringExtensionTests.cs" />
|
||||
<Compile Include="Common\TimeSpanExtensionTests.cs" />
|
||||
<Compile Include="Common\TwinCollectionExtensionTests.cs" />
|
||||
<Compile Include="Common\SupportedMethodsHelperTests.cs" />
|
||||
<Compile Include="Common\TwinExtensionTests.cs" />
|
||||
<Compile Include="Common\VirtualDeviceTableStorageTests.cs" />
|
||||
<Compile Include="Infrastructure\ActionLogicTest.cs" />
|
||||
|
@ -257,6 +258,7 @@
|
|||
<Compile Include="Web\Controllers\DeviceCommandControllerTests.cs" />
|
||||
<Compile Include="Web\Controllers\DashboardControllerTests.cs" />
|
||||
<Compile Include="Web\Controllers\DeviceControllerTests.cs" />
|
||||
<Compile Include="Web\Extensions\JobTypeExtensionTest.cs" />
|
||||
<Compile Include="Web\Helpers\CellularExtensionsTests.cs" />
|
||||
<Compile Include="Web\Helpers\DeviceDisplayHelperTests.cs" />
|
||||
<Compile Include="Web\Helpers\JasperCredentialProviderTests.cs" />
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using Xunit;
|
||||
using GlobalResources;
|
||||
using Microsoft.Azure.Devices.Applications.RemoteMonitoring.DeviceAdmin.Web.Extensions;
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.UnitTests.Web.Extensions
|
||||
{
|
||||
public class JobTypeExtensionTest
|
||||
{
|
||||
[Fact]
|
||||
public void JobTypeLocalizedTest()
|
||||
{
|
||||
Assert.Equal(Strings.ExportDevicesJobType, JobType.ExportDevices.LocalizedString());
|
||||
Assert.Equal(Strings.ImportDevicesJobType, JobType.ImportDevices.LocalizedString());
|
||||
Assert.Equal(Strings.ScheduleUpdateTwinJobType, JobType.ScheduleUpdateTwin.LocalizedString());
|
||||
Assert.Equal(Strings.ScheduleDeviceMethodJobType, JobType.ScheduleDeviceMethod.LocalizedString());
|
||||
Assert.Equal(Strings.UnknownJobType, JobType.Unknown.LocalizedString());
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче