Unified · Split
| @@ -58,6 +58,7 @@ · 57 unmodified lines | |||
| 58 | using System; | 58 | using System; |
| 59 | using System.Collections; | 59 | using System.Collections; |
| 60 | using System.Collections.Generic; | 60 | using System.Collections.Generic; |
| 61 | using System.Collections.Specialized; | ||
| 61 | using System.Linq; | 62 | using System.Linq; |
| 62 | using System.Text; | 63 | using System.Text; |
| 63 | using Random = GSF.Security.Cryptography.Random; | 64 | using Random = GSF.Security.Cryptography.Random; |
| @@ -292,6 +293,16 @@ namespace GSF.Collections · 228 unmodified lines | |||
| 292 | return value; | 293 | return value; |
| 293 | } | 294 | } |
| 294 | 295 | ||
| 296 | /// <summary> | ||
| 297 | /// Converts a name/value collection to a dictionary. | ||
| 298 | /// </summary> | ||
| 299 | /// <param name="collection">Name/value collection.</param> | ||
| 300 | /// <returns>Dictionary converted from a name/value collection.</returns> | ||
| 301 | public static Dictionary<string, string> ToDictionary(this NameValueCollection collection) | ||
| 302 | { | ||
| 303 | return collection.AllKeys.ToDictionary(key => key, key => collection[key]); | ||
| 304 | } | ||
| 305 | |||
| 295 | /// <summary> | 306 | /// <summary> |
| 296 | /// Returns <c>true</c> if any item in <see cref="BitArray"/> is equal to <paramref name="value"/>. | 307 | /// Returns <c>true</c> if any item in <see cref="BitArray"/> is equal to <paramref name="value"/>. |
| 297 | /// </summary> | 308 | /// </summary> |
| @@ -122,6 +122,7 @@ · 121 unmodified lines | |||
| 122 | </PropertyGroup> | 122 | </PropertyGroup> |
| 123 | <ItemGroup> | 123 | <ItemGroup> |
| 124 | <Reference Include="System" /> | 124 | <Reference Include="System" /> |
| 125 | <Reference Include="System.ComponentModel.DataAnnotations" /> | ||
| 125 | <Reference Include="System.configuration" /> | 126 | <Reference Include="System.configuration" /> |
| 126 | <Reference Include="System.Core"> | 127 | <Reference Include="System.Core"> |
| 127 | <RequiredTargetFramework>3.5</RequiredTargetFramework> | 128 | <RequiredTargetFramework>3.5</RequiredTargetFramework> |
| @@ -144,6 +145,15 @@ · 16 unmodified lines | |||
| 144 | <ItemGroup> | 145 | <ItemGroup> |
| 145 | <Compile Include="AdoSecurityProvider.cs" /> | 146 | <Compile Include="AdoSecurityProvider.cs" /> |
| 146 | <Compile Include="AdoSecurityCache.cs" /> | 147 | <Compile Include="AdoSecurityCache.cs" /> |
| 148 | <Compile Include="Model\AccessLog.cs" /> | ||
| 149 | <Compile Include="Model\ErrorLog.cs" /> | ||
| 150 | <Compile Include="Model\ApplicationRole.cs" /> | ||
| 151 | <Compile Include="Model\ApplicationRoleSecurityGroup.cs" /> | ||
| 152 | <Compile Include="Model\SecurityGroupUserAccount.cs" /> | ||
| 153 | <Compile Include="Model\ApplicationRoleUserAccount.cs" /> | ||
| 154 | <Compile Include="Model\Node.cs" /> | ||
| 155 | <Compile Include="Model\SecurityGroup.cs" /> | ||
| 156 | <Compile Include="Model\UserAccount.cs" /> | ||
| 147 | <Compile Include="UserRoleCache.cs" /> | 157 | <Compile Include="UserRoleCache.cs" /> |
| 148 | <Compile Include="LdapSecurityProvider.cs" /> | 158 | <Compile Include="LdapSecurityProvider.cs" /> |
| 149 | <Compile Include="RestrictAccessAttribute.cs" /> | 159 | <Compile Include="RestrictAccessAttribute.cs" /> |
| @@ -0,0 +1,58 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // AccessLog.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright � 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/27/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.ComponentModel.DataAnnotations; | ||
| 26 | using GSF.Data.Model; | ||
| 27 | |||
| 28 | namespace GSF.Security.Model | ||
| 29 | { | ||
| 30 | /// <summary> | ||
| 31 | /// Model for AccessLog table. | ||
| 32 | /// </summary> | ||
| 33 | public class AccessLog | ||
| 34 | { | ||
| 35 | /// <summary> | ||
| 36 | /// Unique ID field. | ||
| 37 | /// </summary> | ||
| 38 | [PrimaryKey(true)] | ||
| 39 | public int ID { get; set; } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// User name field. | ||
| 43 | /// </summary> | ||
| 44 | [Required] | ||
| 45 | [StringLength(200)] | ||
| 46 | public string UserName { get; set; } | ||
| 47 | |||
| 48 | /// <summary> | ||
| 49 | /// Access granted flag field. | ||
| 50 | /// </summary> | ||
| 51 | public bool AccessGranted { get; set; } | ||
| 52 | |||
| 53 | /// <summary> | ||
| 54 | /// Created on field. | ||
| 55 | /// </summary> | ||
| 56 | public DateTime CreatedOn { get; set; } | ||
| 57 | } | ||
| 58 | } | ||
| @@ -0,0 +1,79 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // ApplicationRole.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright � 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/27/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.ComponentModel.DataAnnotations; | ||
| 26 | using GSF.Data.Model; | ||
| 27 | |||
| 28 | namespace GSF.Security.Model | ||
| 29 | { | ||
| 30 | /// <summary> | ||
| 31 | /// Model for ApplicationRole table. | ||
| 32 | /// </summary> | ||
| 33 | [PrimaryLabel("Name")] | ||
| 34 | public class ApplicationRole | ||
| 35 | { | ||
| 36 | /// <summary> | ||
| 37 | /// Unique application role ID field. | ||
| 38 | /// </summary> | ||
| 39 | [PrimaryKey(true)] | ||
| 40 | public Guid ID { get; set; } | ||
| 41 | |||
| 42 | /// <summary> | ||
| 43 | /// Name field. | ||
| 44 | /// </summary> | ||
| 45 | [Required] | ||
| 46 | [StringLength(200)] | ||
| 47 | public string Name { get; set; } | ||
| 48 | |||
| 49 | /// <summary> | ||
| 50 | /// Description field. | ||
| 51 | /// </summary> | ||
| 52 | public string Description { get; set; } | ||
| 53 | |||
| 54 | /// <summary> | ||
| 55 | /// Node ID field. | ||
| 56 | /// </summary> | ||
| 57 | public Guid NodeID { get; set; } | ||
| 58 | |||
| 59 | /// <summary> | ||
| 60 | /// Created on field. | ||
| 61 | /// </summary> | ||
| 62 | public DateTime CreatedOn { get; set; } | ||
| 63 | |||
| 64 | /// <summary> | ||
| 65 | /// Created by field. | ||
| 66 | /// </summary> | ||
| 67 | public string CreatedBy { get; set; } | ||
| 68 | |||
| 69 | /// <summary> | ||
| 70 | /// Updated on field. | ||
| 71 | /// </summary> | ||
| 72 | public DateTime UpdatedOn { get; set; } | ||
| 73 | |||
| 74 | /// <summary> | ||
| 75 | /// Updated by field. | ||
| 76 | /// </summary> | ||
| 77 | public string UpdatedBy { get; set; } | ||
| 78 | } | ||
| 79 | } | ||
| @@ -0,0 +1,52 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // ApplicationRoleSecurityGroup.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright � 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 03/03/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.ComponentModel.DataAnnotations; | ||
| 26 | |||
| 27 | namespace GSF.Security.Model | ||
| 28 | { | ||
| 29 | /// <summary> | ||
| 30 | /// Model for ApplicationRoleSecurityGroup table. | ||
| 31 | /// </summary> | ||
| 32 | public class ApplicationRoleSecurityGroup | ||
| 33 | { | ||
| 34 | /// <summary> | ||
| 35 | /// Application role ID field. | ||
| 36 | /// </summary> | ||
| 37 | [Required] | ||
| 38 | public Guid ApplicationRoleID | ||
| 39 | { | ||
| 40 | get; set; | ||
| 41 | } | ||
| 42 | |||
| 43 | /// <summary> | ||
| 44 | /// Security group ID field. | ||
| 45 | /// </summary> | ||
| 46 | [Required] | ||
| 47 | public Guid SecurityGroupID | ||
| 48 | { | ||
| 49 | get; set; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| @@ -0,0 +1,52 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // ApplicationRoleUserAccount.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright � 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/27/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.ComponentModel.DataAnnotations; | ||
| 26 | |||
| 27 | namespace GSF.Security.Model | ||
| 28 | { | ||
| 29 | /// <summary> | ||
| 30 | /// Model for ApplicationRoleUserAccount table. | ||
| 31 | /// </summary> | ||
| 32 | public class ApplicationRoleUserAccount | ||
| 33 | { | ||
| 34 | /// <summary> | ||
| 35 | /// Application role ID field. | ||
| 36 | /// </summary> | ||
| 37 | [Required] | ||
| 38 | public Guid ApplicationRoleID | ||
| 39 | { | ||
| 40 | get; set; | ||
| 41 | } | ||
| 42 | |||
| 43 | /// <summary> | ||
| 44 | /// User account ID field. | ||
| 45 | /// </summary> | ||
| 46 | [Required] | ||
| 47 | public Guid UserAccountID | ||
| 48 | { | ||
| 49 | get; set; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| @@ -0,0 +1,87 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // ErrorLog.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright � 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/27/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.ComponentModel.DataAnnotations; | ||
| 26 | using GSF.Data.Model; | ||
| 27 | |||
| 28 | namespace GSF.Security.Model | ||
| 29 | { | ||
| 30 | /// <summary> | ||
| 31 | /// Model for ErrorLog table. | ||
| 32 | /// </summary> | ||
| 33 | public class ErrorLog | ||
| 34 | { | ||
| 35 | /// <summary> | ||
| 36 | /// Unique ID field. | ||
| 37 | /// </summary> | ||
| 38 | [PrimaryKey(true)] | ||
| 39 | public int ID | ||
| 40 | { | ||
| 41 | get; set; | ||
| 42 | } | ||
| 43 | |||
| 44 | /// <summary> | ||
| 45 | /// Source field. | ||
| 46 | /// </summary> | ||
| 47 | [Required] | ||
| 48 | [StringLength(200)] | ||
| 49 | public string Source | ||
| 50 | { | ||
| 51 | get; set; | ||
| 52 | } | ||
| 53 | |||
| 54 | /// <summary> | ||
| 55 | /// Type field. | ||
| 56 | /// </summary> | ||
| 57 | public string Type | ||
| 58 | { | ||
| 59 | get; set; | ||
| 60 | } | ||
| 61 | |||
| 62 | /// <summary> | ||
| 63 | /// Message field. | ||
| 64 | /// </summary> | ||
| 65 | [Required] | ||
| 66 | public string Message | ||
| 67 | { | ||
| 68 | get; set; | ||
| 69 | } | ||
| 70 | |||
| 71 | /// <summary> | ||
| 72 | /// Detail field. | ||
| 73 | /// </summary> | ||
| 74 | public string Detail | ||
| 75 | { | ||
| 76 | get; set; | ||
| 77 | } | ||
| 78 | |||
| 79 | /// <summary> | ||
| 80 | /// Created on field. | ||
| 81 | /// </summary> | ||
| 82 | public DateTime CreatedOn | ||
| 83 | { | ||
| 84 | get; set; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | } | ||
| @@ -0,0 +1,80 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // Node.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright � 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/27/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.ComponentModel.DataAnnotations; | ||
| 26 | using GSF.Data.Model; | ||
| 27 | |||
| 28 | namespace GSF.Security.Model | ||
| 29 | { | ||
| 30 | /// <summary> | ||
| 31 | /// Model for Node table. | ||
| 32 | /// </summary> | ||
| 33 | [PrimaryLabel("Name")] | ||
| 34 | public class Node | ||
| 35 | { | ||
| 36 | /// <summary> | ||
| 37 | /// Unique node ID field. | ||
| 38 | /// </summary> | ||
| 39 | [PrimaryKey(true)] | ||
| 40 | public Guid ID { get; set; } | ||
| 41 | |||
| 42 | /// <summary> | ||
| 43 | /// Name field. | ||
| 44 | /// </summary> | ||
| 45 | [Required] | ||
| 46 | [StringLength(200)] | ||
| 47 | public string Name { get; set; } | ||
| 48 | |||
| 49 | /// <summary> | ||
| 50 | /// Description field. | ||
| 51 | /// </summary> | ||
| 52 | public string Description { get; set; } | ||
| 53 | |||
| 54 | /// <summary> | ||
| 55 | /// Enabled field. | ||
| 56 | /// </summary> | ||
| 57 | [InitialValue("true")] | ||
| 58 | public bool Enabled { get; set; } | ||
| 59 | |||
| 60 | /// <summary> | ||
| 61 | /// Created on field. | ||
| 62 | /// </summary> | ||
| 63 | public DateTime CreatedOn { get; set; } | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Created by field. | ||
| 67 | /// </summary> | ||
| 68 | public string CreatedBy { get; set; } | ||
| 69 | |||
| 70 | /// <summary> | ||
| 71 | /// Updated on field. | ||
| 72 | /// </summary> | ||
| 73 | public DateTime UpdatedOn { get; set; } | ||
| 74 | |||
| 75 | /// <summary> | ||
| 76 | /// Updated by field. | ||
| 77 | /// </summary> | ||
| 78 | public string UpdatedBy { get; set; } | ||
| 79 | } | ||
| 80 | } | ||
| @@ -0,0 +1,78 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // SecurityGroup.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright � 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/27/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.ComponentModel.DataAnnotations; | ||
| 26 | using GSF.Data.Model; | ||
| 27 | |||
| 28 | namespace GSF.Security.Model | ||
| 29 | { | ||
| 30 | /// <summary> | ||
| 31 | /// Model for SecurityGroup table. | ||
| 32 | /// </summary> | ||
| 33 | /// <remarks> | ||
| 34 | /// A record in this table can represent a database defined group with associated users | ||
| 35 | /// or an Active Directory group that maintains its own users. | ||
| 36 | /// </remarks> | ||
| 37 | [PrimaryLabel("Name")] | ||
| 38 | public class SecurityGroup | ||
| 39 | { | ||
| 40 | /// <summary> | ||
| 41 | /// Unique security group ID field. | ||
| 42 | /// </summary> | ||
| 43 | [PrimaryKey(true)] | ||
| 44 | public Guid ID { get; set; } | ||
| 45 | |||
| 46 | /// <summary> | ||
| 47 | /// Group name field - stores SID for AD authentication. | ||
| 48 | /// </summary> | ||
| 49 | [Required] | ||
| 50 | [StringLength(200)] | ||
| 51 | public string Name { get; set; } | ||
| 52 | |||
| 53 | /// <summary> | ||
| 54 | /// Description field. | ||
| 55 | /// </summary> | ||
| 56 | public string Description { get; set; } | ||
| 57 | |||
| 58 | /// <summary> | ||
| 59 | /// Created on field. | ||
| 60 | /// </summary> | ||
| 61 | public DateTime CreatedOn { get; set; } | ||
| 62 | |||
| 63 | /// <summary> | ||
| 64 | /// Created by field. | ||
| 65 | /// </summary> | ||
| 66 | public string CreatedBy { get; set; } | ||
| 67 | |||
| 68 | /// <summary> | ||
| 69 | /// Updated on field. | ||
| 70 | /// </summary> | ||
| 71 | public DateTime UpdatedOn { get; set; } | ||
| 72 | |||
| 73 | /// <summary> | ||
| 74 | /// Updated by field. | ||
| 75 | /// </summary> | ||
| 76 | public string UpdatedBy { get; set; } | ||
| 77 | } | ||
| 78 | } | ||
| @@ -0,0 +1,52 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // SecurityGroupUserAccount.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright � 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 03/03/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.ComponentModel.DataAnnotations; | ||
| 26 | |||
| 27 | namespace GSF.Security.Model | ||
| 28 | { | ||
| 29 | /// <summary> | ||
| 30 | /// Model for SecurityGroupUserAccount table. | ||
| 31 | /// </summary> | ||
| 32 | public class SecurityGroupUserAccount | ||
| 33 | { | ||
| 34 | /// <summary> | ||
| 35 | /// Security group ID field. | ||
| 36 | /// </summary> | ||
| 37 | [Required] | ||
| 38 | public Guid SecurityGroupID | ||
| 39 | { | ||
| 40 | get; set; | ||
| 41 | } | ||
| 42 | |||
| 43 | /// <summary> | ||
| 44 | /// User account ID field. | ||
| 45 | /// </summary> | ||
| 46 | [Required] | ||
| 47 | public Guid UserAccountID | ||
| 48 | { | ||
| 49 | get; set; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| @@ -0,0 +1,139 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // UserAccount.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright � 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/27/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.ComponentModel.DataAnnotations; | ||
| 26 | using GSF.Data.Model; | ||
| 27 | |||
| 28 | namespace GSF.Security.Model | ||
| 29 | { | ||
| 30 | /// <summary> | ||
| 31 | /// Model for UserAccount table. | ||
| 32 | /// </summary> | ||
| 33 | /// <remarks> | ||
| 34 | /// A record in this table can represent a database defined user with associated detail | ||
| 35 | /// or an Active Directory user that maintains its own user details. | ||
| 36 | /// </remarks> | ||
| 37 | [PrimaryLabel("Name")] | ||
| 38 | public class UserAccount | ||
| 39 | { | ||
| 40 | /// <summary> | ||
| 41 | /// Unique user account ID field. | ||
| 42 | /// </summary> | ||
| 43 | [PrimaryKey(true)] | ||
| 44 | public Guid ID { get; set; } | ||
| 45 | |||
| 46 | /// <summary> | ||
| 47 | /// User name field - stores SID for AD authentication. | ||
| 48 | /// </summary> | ||
| 49 | [Label("User Name")] | ||
| 50 | [Required] | ||
| 51 | [StringLength(200)] | ||
| 52 | public string Name { get; set; } | ||
| 53 | |||
| 54 | /// <summary> | ||
| 55 | /// Password field - stores password hash for DB authentication only. | ||
| 56 | /// </summary> | ||
| 57 | [Required] | ||
| 58 | [StringLength(200)] | ||
| 59 | public string Password { get; set; } | ||
| 60 | |||
| 61 | /// <summary> | ||
| 62 | /// First name field - for DB authentication only. | ||
| 63 | /// </summary> | ||
| 64 | [Label("First Name")] | ||
| 65 | [StringLength(200)] | ||
| 66 | public string FirstName { get; set; } | ||
| 67 | |||
| 68 | /// <summary> | ||
| 69 | /// Last name field - for DB authentication only. | ||
| 70 | /// </summary> | ||
| 71 | [Label("Last Name")] | ||
| 72 | [StringLength(200)] | ||
| 73 | public string LastName { get; set; } | ||
| 74 | |||
| 75 | /// <summary> | ||
| 76 | /// Default Node ID field. | ||
| 77 | /// </summary> | ||
| 78 | [Required] | ||
| 79 | public Guid DefaultNodeID { get; set; } | ||
| 80 | |||
| 81 | /// <summary> | ||
| 82 | /// Phone number field - for DB authentication only. | ||
| 83 | /// </summary> | ||
| 84 | [StringLength(200)] | ||
| 85 | public string Phone { get; set; } | ||
| 86 | |||
| 87 | /// <summary> | ||
| 88 | /// E-mail field - for DB authentication only. | ||
| 89 | /// </summary> | ||
| 90 | [Label("E-Mail")] | ||
| 91 | [StringLength(200)] | ||
| 92 | [RegularExpression(@"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$", ErrorMessage = "Invalid e-mail address.")] | ||
| 93 | public string Email { get; set; } | ||
| 94 | |||
| 95 | /// <summary> | ||
| 96 | /// Change password on date field - for DB authentication only. | ||
| 97 | /// </summary> | ||
| 98 | public DateTime? ChangePasswordOn | ||
| 99 | { | ||
| 100 | get; set; | ||
| 101 | } | ||
| 102 | |||
| 103 | /// <summary> | ||
| 104 | /// Use Active Directory authentication field. | ||
| 105 | /// </summary> | ||
| 106 | [Label("Use Active Directory Authentication")] | ||
| 107 | [InitialValue("true")] | ||
| 108 | public bool UseADAuthentication { get; set; } | ||
| 109 | |||
| 110 | /// <summary> | ||
| 111 | /// User locked-out field. | ||
| 112 | /// </summary> | ||
| 113 | [Label("Locked Out")] | ||
| 114 | public bool LockedOut | ||
| 115 | { | ||
| 116 | get; set; | ||
| 117 | } | ||
| 118 | |||
| 119 | /// <summary> | ||
| 120 | /// Created on field. | ||
| 121 | /// </summary> | ||
| 122 | public DateTime CreatedOn { get; set; } | ||
| 123 | |||
| 124 | /// <summary> | ||
| 125 | /// Created by field. | ||
| 126 | /// </summary> | ||
| 127 | public string CreatedBy { get; set; } | ||
| 128 | |||
| 129 | /// <summary> | ||
| 130 | /// Updated on field. | ||
| 131 | /// </summary> | ||
| 132 | public DateTime UpdatedOn { get; set; } | ||
| 133 | |||
| 134 | /// <summary> | ||
| 135 | /// Updated by field. | ||
| 136 | /// </summary> | ||
| 137 | public string UpdatedBy { get; set; } | ||
| 138 | } | ||
| 139 | } | ||
| @@ -115,15 +115,96 @@ · 114 unmodified lines | |||
| 115 | <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet> | 115 | <CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet> |
| 116 | </PropertyGroup> | 116 | </PropertyGroup> |
| 117 | <ItemGroup> | 117 | <ItemGroup> |
| 118 | <Reference Include="Microsoft.AspNet.SignalR.Core, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 119 | <HintPath>..\..\Dependencies\NuGet\Microsoft.AspNet.SignalR.Core.2.2.0\lib\net45\Microsoft.AspNet.SignalR.Core.dll</HintPath> | ||
| 120 | <Private>True</Private> | ||
| 121 | </Reference> | ||
| 122 | <Reference Include="Microsoft.AspNet.SignalR.SystemWeb, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 123 | <HintPath>..\..\Dependencies\NuGet\Microsoft.AspNet.SignalR.SystemWeb.2.2.0\lib\net45\Microsoft.AspNet.SignalR.SystemWeb.dll</HintPath> | ||
| 124 | <Private>True</Private> | ||
| 125 | </Reference> | ||
| 126 | <Reference Include="Microsoft.CSharp" /> | ||
| 127 | <Reference Include="Microsoft.Owin, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 128 | <HintPath>..\..\Dependencies\NuGet\Microsoft.Owin.2.1.0\lib\net45\Microsoft.Owin.dll</HintPath> | ||
| 129 | <Private>True</Private> | ||
| 130 | </Reference> | ||
| 131 | <Reference Include="Microsoft.Owin.Host.SystemWeb, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 132 | <HintPath>..\..\Dependencies\NuGet\Microsoft.Owin.Host.SystemWeb.2.1.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll</HintPath> | ||
| 133 | <Private>True</Private> | ||
| 134 | </Reference> | ||
| 135 | <Reference Include="Microsoft.Owin.Security, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 136 | <HintPath>..\..\Dependencies\NuGet\Microsoft.Owin.Security.2.1.0\lib\net45\Microsoft.Owin.Security.dll</HintPath> | ||
| 137 | <Private>True</Private> | ||
| 138 | </Reference> | ||
| 139 | <Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 140 | <HintPath>..\..\Dependencies\NuGet\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath> | ||
| 141 | <Private>True</Private> | ||
| 142 | </Reference> | ||
| 143 | <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> | ||
| 144 | <HintPath>..\..\Dependencies\NuGet\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll</HintPath> | ||
| 145 | <Private>True</Private> | ||
| 146 | </Reference> | ||
| 147 | <Reference Include="Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5, processorArchitecture=MSIL"> | ||
| 148 | <HintPath>..\..\Dependencies\NuGet\Owin.1.0\lib\net40\Owin.dll</HintPath> | ||
| 149 | <Private>True</Private> | ||
| 150 | </Reference> | ||
| 151 | <Reference Include="RazorEngine, Version=3.7.7.0, Culture=neutral, PublicKeyToken=9ee697374c7e744a, processorArchitecture=MSIL"> | ||
| 152 | <HintPath>..\..\Dependencies\NuGet\RazorEngine.3.7.7\lib\net45\RazorEngine.dll</HintPath> | ||
| 153 | <Private>True</Private> | ||
| 154 | </Reference> | ||
| 118 | <Reference Include="System" /> | 155 | <Reference Include="System" /> |
| 156 | <Reference Include="System.ComponentModel.DataAnnotations" /> | ||
| 119 | <Reference Include="System.configuration" /> | 157 | <Reference Include="System.configuration" /> |
| 120 | <Reference Include="System.Core"> | 158 | <Reference Include="System.Core"> |
| 121 | <RequiredTargetFramework>3.5</RequiredTargetFramework> | 159 | <RequiredTargetFramework>3.5</RequiredTargetFramework> |
| 122 | </Reference> | 160 | </Reference> |
| 161 | <Reference Include="System.IO" /> | ||
| 162 | <Reference Include="System.Net" /> | ||
| 163 | <Reference Include="System.Net.Http"> | ||
| 164 | <HintPath>..\..\Dependencies\NuGet\System.Net.Http.2.0.20126.16343\lib\net40\System.Net.Http.dll</HintPath> | ||
| 165 | <Private>True</Private> | ||
| 166 | </Reference> | ||
| 167 | <Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 168 | <HintPath>..\..\Dependencies\NuGet\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath> | ||
| 169 | <Private>True</Private> | ||
| 170 | </Reference> | ||
| 171 | <Reference Include="System.Net.Http.WebRequest"> | ||
| 172 | <HintPath>..\..\Dependencies\NuGet\System.Net.Http.2.0.20126.16343\lib\net40\System.Net.Http.WebRequest.dll</HintPath> | ||
| 173 | <Private>True</Private> | ||
| 174 | </Reference> | ||
| 175 | <Reference Include="System.Security" /> | ||
| 123 | <Reference Include="System.ServiceModel" /> | 176 | <Reference Include="System.ServiceModel" /> |
| 124 | <Reference Include="System.ServiceModel.Activation" /> | 177 | <Reference Include="System.ServiceModel.Activation" /> |
| 125 | <Reference Include="System.ServiceModel.Web" /> | 178 | <Reference Include="System.ServiceModel.Web" /> |
| 126 | <Reference Include="System.Web" /> | 179 | <Reference Include="System.Web" /> |
| 180 | <Reference Include="System.Web.Helpers, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 181 | <HintPath>..\..\Dependencies\NuGet\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll</HintPath> | ||
| 182 | <Private>True</Private> | ||
| 183 | </Reference> | ||
| 184 | <Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 185 | <HintPath>..\..\Dependencies\NuGet\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath> | ||
| 186 | <Private>True</Private> | ||
| 187 | </Reference> | ||
| 188 | <Reference Include="System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 189 | <HintPath>..\..\Dependencies\NuGet\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll</HintPath> | ||
| 190 | <Private>True</Private> | ||
| 191 | </Reference> | ||
| 192 | <Reference Include="System.Web.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 193 | <HintPath>..\..\Dependencies\NuGet\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll</HintPath> | ||
| 194 | <Private>True</Private> | ||
| 195 | </Reference> | ||
| 196 | <Reference Include="System.Web.WebPages, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 197 | <HintPath>..\..\Dependencies\NuGet\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll</HintPath> | ||
| 198 | <Private>True</Private> | ||
| 199 | </Reference> | ||
| 200 | <Reference Include="System.Web.WebPages.Deployment, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 201 | <HintPath>..\..\Dependencies\NuGet\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll</HintPath> | ||
| 202 | <Private>True</Private> | ||
| 203 | </Reference> | ||
| 204 | <Reference Include="System.Web.WebPages.Razor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> | ||
| 205 | <HintPath>..\..\Dependencies\NuGet\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll</HintPath> | ||
| 206 | <Private>True</Private> | ||
| 207 | </Reference> | ||
| 127 | <Reference Include="System.Xml.Linq"> | 208 | <Reference Include="System.Xml.Linq"> |
| 128 | <RequiredTargetFramework>3.5</RequiredTargetFramework> | 209 | <RequiredTargetFramework>3.5</RequiredTargetFramework> |
| 129 | </Reference> | 210 | </Reference> |
| @@ -142,6 +223,8 @@ · 12 unmodified lines | |||
| 142 | <Compile Include="Hosting\VirtualFileBaseCollection.cs" /> | 223 | <Compile Include="Hosting\VirtualFileBaseCollection.cs" /> |
| 143 | <Compile Include="Embedded\ISecurityService.cs" /> | 224 | <Compile Include="Embedded\ISecurityService.cs" /> |
| 144 | <Compile Include="IgnoreServicesSecurityModule.cs" /> | 225 | <Compile Include="IgnoreServicesSecurityModule.cs" /> |
| 226 | <Compile Include="Model\DataContext.cs" /> | ||
| 227 | <Compile Include="Model\RecordOperationsCache.cs" /> | ||
| 145 | <Compile Include="NamespaceDoc.cs" /> | 228 | <Compile Include="NamespaceDoc.cs" /> |
| 146 | <Compile Include="Properties\AssemblyInfo.cs" /> | 229 | <Compile Include="Properties\AssemblyInfo.cs" /> |
| 147 | <Compile Include="SecurityModule.cs" /> | 230 | <Compile Include="SecurityModule.cs" /> |
| @@ -156,7 +239,14 @@ · 8 unmodified lines | |||
| 156 | <DependentUpon>SecurityService.svc</DependentUpon> | 239 | <DependentUpon>SecurityService.svc</DependentUpon> |
| 157 | </Compile> | 240 | </Compile> |
| 158 | <Compile Include="Embedded\SecurityServiceHostFactory.cs" /> | 241 | <Compile Include="Embedded\SecurityServiceHostFactory.cs" /> |
| 242 | <Compile Include="Security\AuthorizationCache.cs" /> | ||
| 243 | <Compile Include="Security\AuthorizeControllerRoleAttribute.cs" /> | ||
| 244 | <Compile Include="Security\AuthorizeHubRoleAttribute.cs" /> | ||
| 245 | <Compile Include="Security\SecurityHub.cs" /> | ||
| 246 | <Compile Include="Templating\NamespaceDoc.cs" /> | ||
| 247 | <Compile Include="Templating\RazorView.cs" /> | ||
| 159 | <Compile Include="UI\ClientSideExtensions.cs" /> | 248 | <Compile Include="UI\ClientSideExtensions.cs" /> |
| 249 | <Compile Include="WebExtensions.cs" /> | ||
| 160 | </ItemGroup> | 250 | </ItemGroup> |
| 161 | <ItemGroup> | 251 | <ItemGroup> |
| 162 | <ProjectReference Include="..\GSF.Core\GSF.Core.csproj"> | 252 | <ProjectReference Include="..\GSF.Core\GSF.Core.csproj"> |
| @@ -214,6 +304,21 @@ · 51 unmodified lines | |||
| 214 | </ItemGroup> | 304 | </ItemGroup> |
| 215 | <ItemGroup> | 305 | <ItemGroup> |
| 216 | <None Include="GSF.Web.nuspec" /> | 306 | <None Include="GSF.Web.nuspec" /> |
| 307 | <EmbeddedResource Include="Model\Views\PagedViewModel.cshtml" /> | ||
| 308 | <None Include="packages.config" /> | ||
| 309 | <EmbeddedResource Include="Templating\Views\AddCheckBoxField.cshtml" /> | ||
| 310 | <EmbeddedResource Include="Templating\Views\AddDateField.cshtml" /> | ||
| 311 | <EmbeddedResource Include="Templating\Views\AddInputField.cshtml" /> | ||
| 312 | <EmbeddedResource Include="Templating\Views\AddSelectField.cshtml" /> | ||
| 313 | <EmbeddedResource Include="Templating\Views\AddTextAreaField.cshtml" /> | ||
| 314 | <EmbeddedResource Include="Security\Views\Groups.cshtml" /> | ||
| 315 | <EmbeddedResource Include="Security\Views\Users.cshtml" /> | ||
| 316 | </ItemGroup> | ||
| 317 | <ItemGroup> | ||
| 318 | <EmbeddedResource Include="Model\Scripts\pagedViewModel.js" /> | ||
| 319 | </ItemGroup> | ||
| 320 | <ItemGroup> | ||
| 321 | <EmbeddedResource Include="Model\Scripts\gsf.web.client.js" /> | ||
| 217 | </ItemGroup> | 322 | </ItemGroup> |
| 218 | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | 323 | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
| 219 | <!-- To modify your build process, add your task inside one of the targets below and uncomment it. | 324 | <!-- To modify your build process, add your task inside one of the targets below and uncomment it. |
| @@ -0,0 +1,955 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // DataContext.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/01/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.Collections.Generic; | ||
| 26 | using System.ComponentModel.DataAnnotations; | ||
| 27 | using System.Linq; | ||
| 28 | using System.Text; | ||
| 29 | using GSF.Collections; | ||
| 30 | using GSF.Data; | ||
| 31 | using GSF.Data.Model; | ||
| 32 | using GSF.Reflection; | ||
| 33 | using GSF.Web.Templating; | ||
| 34 | using RazorEngine.Templating; | ||
| 35 | |||
| 36 | namespace GSF.Web.Model | ||
| 37 | { | ||
| 38 | /// <summary> | ||
| 39 | /// Defines a default data context object based on embedded resources. | ||
| 40 | /// </summary> | ||
| 41 | public class DataContext : DataContext<CSharpEmbeddedResource> | ||
| 42 | { | ||
| 43 | /// <summary> | ||
| 44 | /// Creates a new <see cref="DataContext"/>. | ||
| 45 | /// </summary> | ||
| 46 | /// <param name="connection"><see cref="AdoDataConnection"/> to use; defaults to a new connection.</param> | ||
| 47 | /// <param name="disposeConnection">Set to <c>true</c> to dispose the provided <paramref name="connection"/>.</param> | ||
| 48 | /// <param name="exceptionHandler">Delegate to handle exceptions.</param> | ||
| 49 | public DataContext(AdoDataConnection connection = null, bool disposeConnection = false, Action<Exception> exceptionHandler = null) : | ||
| 50 | base(connection, disposeConnection, exceptionHandler) | ||
| 51 | { | ||
| 52 | } | ||
| 53 | |||
| 54 | /// <summary> | ||
| 55 | /// Creates a new <see cref="DataContext"/> using the specified <paramref name="settingsCategory"/>. | ||
| 56 | /// </summary> | ||
| 57 | /// <param name="settingsCategory">Setting category that contains the connection settings.</param> | ||
| 58 | /// <param name="exceptionHandler">Delegate to handle exceptions.</param> | ||
| 59 | public DataContext(string settingsCategory, Action<Exception> exceptionHandler = null) : | ||
| 60 | base(settingsCategory, exceptionHandler) | ||
| 61 | { | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Defines a data context for modeled tables. | ||
| 67 | /// </summary> | ||
| 68 | /// <typeparam name="TLanguage"><see cref="LanguageContraint"/> type to use for Razor views.</typeparam> | ||
| 69 | public class DataContext<TLanguage> : IDisposable where TLanguage : LanguageContraint, new() | ||
| 70 | { | ||
| 71 | #region [ Members ] | ||
| 72 | |||
| 73 | // Fields | ||
| 74 | private AdoDataConnection m_connection; | ||
| 75 | private readonly Dictionary<Type, object> m_tableOperations; | ||
| 76 | private readonly Action<Exception> m_exceptionHandler; | ||
| 77 | private readonly Dictionary<string, Tuple<string, string>> m_fieldValidationParameters; | ||
| 78 | private readonly List<Tuple<string, string>> m_fieldValueInitializers; | ||
| 79 | private readonly List<string> m_definedDateFields; | ||
| 80 | private readonly string m_settingsCategory; | ||
| 81 | private readonly bool m_disposeConnection; | ||
| 82 | private string m_initialFocusField; | ||
| 83 | private string m_addDateFieldTemplate; | ||
| 84 | private string m_addInputFieldTemplate; | ||
| 85 | private string m_addTextAreaFieldTemplate; | ||
| 86 | private string m_addSelectFieldTemplate; | ||
| 87 | private string m_addCheckBoxFieldTemplate; | ||
| 88 | private bool m_disposed; | ||
| 89 | |||
| 90 | #endregion | ||
| 91 | |||
| 92 | #region [ Constructors ] | ||
| 93 | |||
| 94 | /// <summary> | ||
| 95 | /// Creates a new <see cref="DataContext{TLanguage}"/>. | ||
| 96 | /// </summary> | ||
| 97 | /// <param name="connection"><see cref="AdoDataConnection"/> to use; defaults to a new connection.</param> | ||
| 98 | /// <param name="disposeConnection">Set to <c>true</c> to dispose the provided <paramref name="connection"/>.</param> | ||
| 99 | /// <param name="exceptionHandler">Delegate to handle exceptions.</param> | ||
| 100 | public DataContext(AdoDataConnection connection = null, bool disposeConnection = false, Action<Exception> exceptionHandler = null) | ||
| 101 | { | ||
| 102 | m_connection = connection; | ||
| 103 | m_tableOperations = new Dictionary<Type, object>(); | ||
| 104 | m_exceptionHandler = exceptionHandler; | ||
| 105 | m_fieldValidationParameters = new Dictionary<string, Tuple<string, string>>(); | ||
| 106 | m_fieldValueInitializers = new List<Tuple<string, string>>(); | ||
| 107 | m_definedDateFields = new List<string>(); | ||
| 108 | m_settingsCategory = "systemSettings"; | ||
| 109 | m_disposeConnection = disposeConnection || connection == null; | ||
| 110 | } | ||
| 111 | |||
| 112 | /// <summary> | ||
| 113 | /// Creates a new <see cref="DataContext{TLanguage}"/> using the specified <paramref name="settingsCategory"/>. | ||
| 114 | /// </summary> | ||
| 115 | /// <param name="settingsCategory">Setting category that contains the connection settings.</param> | ||
| 116 | /// <param name="exceptionHandler">Delegate to handle exceptions.</param> | ||
| 117 | public DataContext(string settingsCategory, Action<Exception> exceptionHandler = null) | ||
| 118 | { | ||
| 119 | m_tableOperations = new Dictionary<Type, object>(); | ||
| 120 | m_exceptionHandler = exceptionHandler; | ||
| 121 | m_fieldValidationParameters = new Dictionary<string, Tuple<string, string>>(); | ||
| 122 | m_fieldValueInitializers = new List<Tuple<string, string>>(); | ||
| 123 | m_definedDateFields = new List<string>(); | ||
| 124 | m_settingsCategory = settingsCategory; | ||
| 125 | m_disposeConnection = true; | ||
| 126 | } | ||
| 127 | |||
| 128 | #endregion | ||
| 129 | |||
| 130 | #region [ Properties ] | ||
| 131 | |||
| 132 | /// <summary> | ||
| 133 | /// Gets the <see cref="AdoDataConnection"/> for this <see cref="DataContext{TLanguage}"/>. | ||
| 134 | /// </summary> | ||
| 135 | public AdoDataConnection Connection => m_connection ?? (m_connection = new AdoDataConnection(m_settingsCategory)); | ||
| 136 | |||
| 137 | /// <summary> | ||
| 138 | /// Gets validation pattern and error message for rendered fields, if any. | ||
| 139 | /// </summary> | ||
| 140 | public Dictionary<string, Tuple<string, string>> FieldValidationParameters => m_fieldValidationParameters; | ||
| 141 | |||
| 142 | /// <summary> | ||
| 143 | /// Gets field value initializers, if any. | ||
| 144 | /// </summary> | ||
| 145 | public List<Tuple<string, string>> FieldValueInitializers => m_fieldValueInitializers; | ||
| 146 | |||
| 147 | /// <summary> | ||
| 148 | /// Gets defined date fields, if any. | ||
| 149 | /// </summary> | ||
| 150 | public List<string> DefinedDateFields => m_definedDateFields; | ||
| 151 | |||
| 152 | /// <summary> | ||
| 153 | /// Gets field name designated for initial focus. | ||
| 154 | /// </summary> | ||
| 155 | public string InitialFocusField => m_initialFocusField; | ||
| 156 | |||
| 157 | /// <summary> | ||
| 158 | /// Gets or sets the date field razor template file name. | ||
| 159 | /// </summary> | ||
| 160 | public string AddDateFieldTemplate | ||
| 161 | { | ||
| 162 | get | ||
| 163 | { | ||
| 164 | return m_addDateFieldTemplate ?? (m_addDateFieldTemplate = $"{RazorView<TLanguage>.TemplatePath}AddDateField.cshtml"); | ||
| 165 | } | ||
| 166 | set | ||
| 167 | { | ||
| 168 | m_addDateFieldTemplate = value; | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | /// <summary> | ||
| 173 | /// Gets or sets the input field razor template file name. | ||
| 174 | /// </summary> | ||
| 175 | public string AddInputFieldTemplate | ||
| 176 | { | ||
| 177 | get | ||
| 178 | { | ||
| 179 | return m_addInputFieldTemplate ?? (m_addInputFieldTemplate = $"{RazorView<TLanguage>.TemplatePath}AddInputField.cshtml"); | ||
| 180 | } | ||
| 181 | set | ||
| 182 | { | ||
| 183 | m_addInputFieldTemplate = value; | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | /// <summary> | ||
| 188 | /// Gets or sets the text area field razor template file name. | ||
| 189 | /// </summary> | ||
| 190 | public string AddTextAreaFieldTemplate | ||
| 191 | { | ||
| 192 | get | ||
| 193 | { | ||
| 194 | return m_addTextAreaFieldTemplate ?? (m_addTextAreaFieldTemplate = $"{RazorView<TLanguage>.TemplatePath}AddTextAreaField.cshtml"); | ||
| 195 | } | ||
| 196 | set | ||
| 197 | { | ||
| 198 | m_addTextAreaFieldTemplate = value; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | /// <summary> | ||
| 203 | /// Gets or sets the select field razor template file name. | ||
| 204 | /// </summary> | ||
| 205 | public string AddSelectFieldTemplate | ||
| 206 | { | ||
| 207 | get | ||
| 208 | { | ||
| 209 | return m_addSelectFieldTemplate ?? (m_addSelectFieldTemplate = $"{RazorView<TLanguage>.TemplatePath}AddSelectField.cshtml"); | ||
| 210 | } | ||
| 211 | set | ||
| 212 | { | ||
| 213 | m_addSelectFieldTemplate = value; | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | /// <summary> | ||
| 218 | /// Gets or sets the check box field razor template file name. | ||
| 219 | /// </summary> | ||
| 220 | public string AddCheckBoxFieldTemplate | ||
| 221 | { | ||
| 222 | get | ||
| 223 | { | ||
| 224 | return m_addCheckBoxFieldTemplate ?? (m_addCheckBoxFieldTemplate = $"{RazorView<TLanguage>.TemplatePath}AddCheckBoxField.cshtml"); | ||
| 225 | } | ||
| 226 | set | ||
| 227 | { | ||
| 228 | m_addCheckBoxFieldTemplate = value; | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | #endregion | ||
| 233 | |||
| 234 | #region [ Methods ] | ||
| 235 | |||
| 236 | /// <summary> | ||
| 237 | /// Releases all the resources used by the <see cref="DataContext{TLanguage}"/> object. | ||
| 238 | /// </summary> | ||
| 239 | public void Dispose() | ||
| 240 | { | ||
| 241 | Dispose(true); | ||
| 242 | GC.SuppressFinalize(this); | ||
| 243 | } | ||
| 244 | |||
| 245 | /// <summary> | ||
| 246 | /// Releases the unmanaged resources used by the <see cref="DataContext{TLanguage}"/> object and optionally releases the managed resources. | ||
| 247 | /// </summary> | ||
| 248 | /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> | ||
| 249 | protected virtual void Dispose(bool disposing) | ||
| 250 | { | ||
| 251 | if (!m_disposed) | ||
| 252 | { | ||
| 253 | try | ||
| 254 | { | ||
| 255 | if (disposing) | ||
| 256 | { | ||
| 257 | if (m_disposeConnection) | ||
| 258 | m_connection?.Dispose(); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | finally | ||
| 262 | { | ||
| 263 | m_disposed = true; // Prevent duplicate dispose. | ||
| 264 | } | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | /// <summary> | ||
| 269 | /// Gets the table operations for the specified modeled table <typeparamref name="T"/>. | ||
| 270 | /// </summary> | ||
| 271 | /// <typeparam name="T">Modeled database table (or view).</typeparam> | ||
| 272 | /// <returns>Table operations for the specified modeled table <typeparamref name="T"/>.</returns> | ||
| 273 | public TableOperations<T> Table<T>() where T : class, new() | ||
| 274 | { | ||
| 275 | return m_tableOperations.GetOrAdd(typeof(T), type => new TableOperations<T>(Connection, m_exceptionHandler)) as TableOperations<T>; | ||
| 276 | } | ||
| 277 | |||
| 278 | /// <summary> | ||
| 279 | /// Gets the field name targeted as the primary label for the modeled table. | ||
| 280 | /// </summary> | ||
| 281 | /// <typeparam name="T">Modeled database table (or view).</typeparam> | ||
| 282 | /// <returns>Field name targeted as the primary label for the modeled table.</returns> | ||
| 283 | public string GetPrimaryLabelField<T>() where T : class, new() | ||
| 284 | { | ||
| 285 | PrimaryLabelAttribute primaryLabelAttribute; | ||
| 286 | |||
| 287 | if (typeof(T).TryGetAttribute(out primaryLabelAttribute) && !string.IsNullOrWhiteSpace(primaryLabelAttribute.FieldName)) | ||
| 288 | return primaryLabelAttribute.FieldName; | ||
| 289 | |||
| 290 | string[] fieldNames = Table<T>().GetFieldNames(); | ||
| 291 | return fieldNames.Length > 0 ? fieldNames[0] : ""; | ||
| 292 | } | ||
| 293 | |||
| 294 | /// <summary> | ||
| 295 | /// Gets the field name targeted to mark a record as deleted. | ||
| 296 | /// </summary> | ||
| 297 | /// <typeparam name="T">Modeled database table (or view).</typeparam> | ||
| 298 | /// <returns>Field name targeted to mark a record as deleted.</returns> | ||
| 299 | public string GetIsDeletedFlag<T>() where T : class, new() | ||
| 300 | { | ||
| 301 | IsDeletedFlagAttribute isDeletedFlagAttribute; | ||
| 302 | |||
| 303 | if (typeof(T).TryGetAttribute(out isDeletedFlagAttribute) && !string.IsNullOrWhiteSpace(isDeletedFlagAttribute.FieldName)) | ||
| 304 | return isDeletedFlagAttribute.FieldName; | ||
| 305 | |||
| 306 | return null; | ||
| 307 | } | ||
| 308 | |||
| 309 | /// <summary> | ||
| 310 | /// Looks up proper user roles for paged based on modeled security in <see cref="RecordOperationsCache"/>. | ||
| 311 | /// </summary> | ||
| 312 | /// <typeparam name="T">Modeled database table (or view).</typeparam> | ||
| 313 | /// <param name="cache">Data hub record operations cache.</param> | ||
| 314 | /// <param name="viewBag">ViewBag for the current view.</param> | ||
| 315 | /// <remarks> | ||
| 316 | /// Typically used in paged view model scenarios and invoked by controller prior to view rendering. | ||
| 317 | /// Security is controlled at hub level, so failure to call will not impact security but may result | ||
| 318 | /// in screen enabling and/or showing controls that the user does not actually have access to and | ||
| 319 | /// upon attempted use will result in a security error. | ||
| 320 | /// </remarks> | ||
| 321 | public void EstablishUserRolesForPage<T>(RecordOperationsCache cache, dynamic viewBag) where T : class, new() | ||
| 322 | { | ||
| 323 | // Get any authorized roles as defined in hub for key records operations of modeled table | ||
| 324 | Tuple<string, string>[] recordOperations = cache.GetRecordOperations<T>(); | ||
| 325 | |||
| 326 | // Create a function to check if a method exists for operation - if none is defined, read-only access will be assumed (e.g. for a view) | ||
| 327 | Func<RecordOperation, string> getRoles = operationType => | ||
| 328 | { | ||
| 329 | Tuple<string, string> recordOperation = recordOperations[(int)operationType]; | ||
| 330 | return string.IsNullOrEmpty(recordOperation?.Item1) ? "" : recordOperation.Item2 ?? ""; | ||
| 331 | }; | ||
| 332 | |||
| 333 | viewBag.EditRoles = getRoles(RecordOperation.UpdateRecord); | ||
| 334 | viewBag.AddNewRoles = getRoles(RecordOperation.AddNewRecord); | ||
| 335 | viewBag.DeleteRoles = getRoles(RecordOperation.DeleteRecord); | ||
| 336 | } | ||
| 337 | |||
| 338 | /// <summary> | ||
| 339 | /// Renders client-side configuration script for paged view model. | ||
| 340 | /// </summary> | ||
| 341 | /// <typeparam name="T">Modeled database table (or view).</typeparam> | ||
| 342 | /// <param name="cache">Data hub record operations cache.</param> | ||
| 343 | /// <param name="viewBag">ViewBag for the view.</param> | ||
| 344 | /// <param name="defaultSortField">Default sort field name, defaults to first primary key field. Prefix field name with a minus, i.e., '-', to default to descending sort.</param> | ||
| 345 | /// <param name="parentKeys">Primary keys values of the parent record to load.</param> | ||
| 346 | /// <returns>Rendered paged view model configuration script.</returns> | ||
| 347 | public string RenderViewModelConfiguration<T>(RecordOperationsCache cache, dynamic viewBag, string defaultSortField = null, params object[] parentKeys) where T : class, new() | ||
| 348 | { | ||
| 349 | StringBuilder javascript = new StringBuilder(); | ||
| 350 | string[] primaryKeyFields = Table<T>().GetPrimaryKeyFieldNames(); | ||
| 351 | string defaultSortAscending = "true"; | ||
| 352 | |||
| 353 | defaultSortField = defaultSortField ?? primaryKeyFields[0]; | ||
| 354 | |||
| 355 | // Handle case for desired default descending sort order | ||
| 356 | if (defaultSortField[0] == '-') | ||
| 357 | { | ||
| 358 | defaultSortField = defaultSortField.Substring(1); | ||
| 359 | defaultSortAscending = "false"; | ||
| 360 | } | ||
| 361 | |||
| 362 | javascript.Append($@"// Configure view model | ||
| 363 | viewModel.defaultSortField = ""{defaultSortField}""; | ||
| 364 | viewModel.defaultSortAscending = {defaultSortAscending}; | ||
| 365 | viewModel.labelField = ""{GetPrimaryLabelField<T>()}""; | ||
| 366 | viewModel.primaryKeyFields = [{primaryKeyFields.Select(fieldName => $"\"{fieldName}\"").ToDelimitedString(", ")}]; | ||
| 367 | ".FixForwardSpacing()); | ||
| 368 | |||
| 369 | string showDeletedValue = null; | ||
| 370 | |||
| 371 | if (viewBag.IsDeletedField != null) | ||
| 372 | showDeletedValue = (viewBag.ShowDeleted ?? false).ToString().ToLower(); | ||
| 373 | |||
| 374 | Func<string, string> toCamelCase = methodName => methodName == null ? null : $"{char.ToLower(methodName[0])}{methodName.Substring(1)}"; | ||
| 375 | |||
| 376 | // Get method names for records operations of modeled table | ||
| 377 | Tuple<string, string>[] recordOperations = cache.GetRecordOperations<T>(); | ||
| 378 | string queryRecordCountMethod = toCamelCase(recordOperations[(int)RecordOperation.QueryRecordCount]?.Item1); | ||
| 379 | string queryRecordsMethod = toCamelCase(recordOperations[(int)RecordOperation.QueryRecords]?.Item1); | ||
| 380 | string deleteRecordMethod = toCamelCase(recordOperations[(int)RecordOperation.DeleteRecord]?.Item1); | ||
| 381 | string createNewRecordMethod = toCamelCase(recordOperations[(int)RecordOperation.CreateNewRecord]?.Item1); | ||
| 382 | string addNewRecordMethod = toCamelCase(recordOperations[(int)RecordOperation.AddNewRecord]?.Item1); | ||
| 383 | string updateMethod = toCamelCase(recordOperations[(int)RecordOperation.UpdateRecord]?.Item1); | ||
| 384 | |||
| 385 | string keyValues = null; | ||
| 386 | |||
| 387 | if (parentKeys.Length > 0) | ||
| 388 | keyValues = parentKeys.ToDelimitedString(", "); | ||
| 389 | |||
| 390 | // If modeled table has IsDeletedField marker, the showDeleted parameter should come first in DataHub operations | ||
| 391 | if (showDeletedValue != null) | ||
| 392 | keyValues = keyValues != null ? $"{showDeletedValue}, {keyValues}" : showDeletedValue; | ||
| 393 | |||
| 394 | if (!string.IsNullOrWhiteSpace(queryRecordCountMethod)) | ||
| 395 | javascript.Append($@" | ||
| 396 | viewModel.setQueryRecordCount(function () {{ | ||
| 397 | return dataHub.{queryRecordCountMethod}({keyValues}); | ||
| 398 | }}); | ||
| 399 | ".FixForwardSpacing()); | ||
| 400 | |||
| 401 | if (!string.IsNullOrWhiteSpace(queryRecordsMethod)) | ||
| 402 | javascript.Append($@" | ||
| 403 | viewModel.setQueryRecords(function (sortField, ascending, page, pageSize) {{ | ||
| 404 | return dataHub.{queryRecordsMethod}({(keyValues == null ? "" : $"{keyValues}, ")}sortField, ascending, page, pageSize); | ||
| 405 | }}); | ||
| 406 | ".FixForwardSpacing()); | ||
| 407 | |||
| 408 | if (!string.IsNullOrWhiteSpace(deleteRecordMethod)) | ||
| 409 | javascript.Append($@" | ||
| 410 | viewModel.setDeleteRecord(function (keyValues) {{ | ||
| 411 | return dataHub.{deleteRecordMethod}({Enumerable.Range(0, Table<T>().GetPrimaryKeyFieldNames().Length).Select(index => $"keyValues[{index}]").ToDelimitedString(", ")}); | ||
| 412 | }}); | ||
| 413 | ".FixForwardSpacing()); | ||
| 414 | |||
| 415 | if (!string.IsNullOrWhiteSpace(createNewRecordMethod)) | ||
| 416 | javascript.Append($@" | ||
| 417 | viewModel.setNewRecord(function () {{ | ||
| 418 | return dataHub.{createNewRecordMethod}(); | ||
| 419 | }}); | ||
| 420 | ".FixForwardSpacing()); | ||
| 421 | |||
| 422 | if (!string.IsNullOrWhiteSpace(addNewRecordMethod)) | ||
| 423 | javascript.Append($@" | ||
| 424 | viewModel.setAddNewRecord(function (record) {{ | ||
| 425 | return dataHub.{addNewRecordMethod}(record); | ||
| 426 | }}); | ||
| 427 | ".FixForwardSpacing()); | ||
| 428 | |||
| 429 | if (!string.IsNullOrWhiteSpace(updateMethod)) | ||
| 430 | javascript.Append($@" | ||
| 431 | viewModel.setUpdateRecord(function (record) {{ | ||
| 432 | return dataHub.{updateMethod}(record); | ||
| 433 | }}); | ||
| 434 | ".FixForwardSpacing()); | ||
| 435 | |||
| 436 | return javascript.ToString(); | ||
| 437 | } | ||
| 438 | |||
| 439 | /// <summary> | ||
| 440 | /// Adds a new pattern based validation and option error message to a field. | ||
| 441 | /// </summary> | ||
| 442 | /// <param name="observableFieldReference">Observable field reference (from JS view model).</param> | ||
| 443 | /// <param name="validationPattern">Regex based validation pattern.</param> | ||
| 444 | /// <param name="errorMessage">Optional error message to display when pattern fails.</param> | ||
| 445 | public void AddFieldValidation(string observableFieldReference, string validationPattern, string errorMessage = null) | ||
| 446 | { | ||
| 447 | m_fieldValidationParameters[observableFieldReference] = new Tuple<string, string>(validationPattern, errorMessage); | ||
| 448 | } | ||
| 449 | |||
| 450 | /// <summary> | ||
| 451 | /// Adds a new field value initializer. | ||
| 452 | /// </summary> | ||
| 453 | /// <param name="fieldName">Field name (as defined in model).</param> | ||
| 454 | /// <param name="initialValue">Javascript based initial value for field.</param> | ||
| 455 | public void AddFieldValueInitializer(string fieldName, string initialValue = null) | ||
| 456 | { | ||
| 457 | m_fieldValueInitializers.Add(new Tuple<string, string>(fieldName, initialValue ?? "\"\"")); | ||
| 458 | } | ||
| 459 | |||
| 460 | /// <summary> | ||
| 461 | /// Adds a new field value initializer based on modeled table field attributes. | ||
| 462 | /// </summary> | ||
| 463 | /// <param name="fieldName">Field name (as defined in model).</param> | ||
| 464 | public void AddFieldValueInitializer<T>(string fieldName) where T : class, new() | ||
| 465 | { | ||
| 466 | InitialValueAttribute initialValueAttribute; | ||
| 467 | |||
| 468 | if (Table<T>().TryGetFieldAttribute(fieldName, out initialValueAttribute)) | ||
| 469 | AddFieldValueInitializer(fieldName, initialValueAttribute.InitialValue); | ||
| 470 | } | ||
| 471 | |||
| 472 | /// <summary> | ||
| 473 | /// Generates template based input date field based on reflected modeled table field attributes. | ||
| 474 | /// </summary> | ||
| 475 | /// <typeparam name="T">Modeled table.</typeparam> | ||
| 476 | /// <param name="fieldName">Field name for input date field.</param> | ||
| 477 | /// <param name="inputType">Input field type, defaults to text.</param> | ||
| 478 | /// <param name="fieldLabel">Label name for input date field, pulls from <see cref="LabelAttribute"/> if defined, otherwise defaults to <paramref name="fieldName"/>.</param> | ||
| 479 | /// <param name="fieldID">ID to use for input field; defaults to date + <paramref name="fieldName"/>.</param> | ||
| 480 | /// <param name="groupDataBinding">Data-bind operations to apply to outer form-group div, if any.</param> | ||
| 481 | /// <param name="labelDataBinding">Data-bind operations to apply to label, if any.</param> | ||
| 482 | /// <param name="requiredDataBinding">Boolean data-bind operation to apply to required state, if any.</param> | ||
| 483 | /// <param name="customDataBinding">Extra custom data-binding operations to apply to field, if any.</param> | ||
| 484 | /// <param name="dependencyFieldName">Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.</param> | ||
| 485 | /// <param name="toolTip">Tool tip text to apply to field, if any.</param> | ||
| 486 | /// <param name="initialFocus">Use field for initial focus.</param> | ||
| 487 | /// <returns>Generated HTML for new date based input field based on modeled table field attributes.</returns> | ||
| 488 | public string AddDateField<T>(string fieldName, string inputType = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false) where T : class, new() | ||
| 489 | { | ||
| 490 | TableOperations<T> tableOperations = Table<T>(); | ||
| 491 | StringLengthAttribute stringLengthAttribute; | ||
| 492 | RegularExpressionAttribute regularExpressionAttribute; | ||
| 493 | |||
| 494 | if (string.IsNullOrEmpty(fieldLabel)) | ||
| 495 | { | ||
| 496 | LabelAttribute labelAttribute; | ||
| 497 | |||
| 498 | if (tableOperations.TryGetFieldAttribute(fieldName, out labelAttribute)) | ||
| 499 | fieldLabel = labelAttribute.Label; | ||
| 500 | } | ||
| 501 | |||
| 502 | tableOperations.TryGetFieldAttribute(fieldName, out stringLengthAttribute); | ||
| 503 | tableOperations.TryGetFieldAttribute(fieldName, out regularExpressionAttribute); | ||
| 504 | |||
| 505 | if ((object)regularExpressionAttribute != null) | ||
| 506 | { | ||
| 507 | string observableReference; | ||
| 508 | |||
| 509 | if (string.IsNullOrEmpty(groupDataBinding)) | ||
| 510 | observableReference = $"viewModel.currentRecord().{fieldName}"; | ||
| 511 | else // "with: $root.connectionString" | ||
| 512 | observableReference = $"viewModel.{groupDataBinding.Substring(groupDataBinding.IndexOf('.') + 1)}"; | ||
| 513 | |||
| 514 | AddFieldValidation(observableReference, regularExpressionAttribute.Pattern, regularExpressionAttribute.ErrorMessage ?? "Invalid format."); | ||
| 515 | } | ||
| 516 | |||
| 517 | AddFieldValueInitializer<T>(fieldName); | ||
| 518 | |||
| 519 | return AddDateField(fieldName, tableOperations.FieldHasAttribute<RequiredAttribute>(fieldName), | ||
| 520 | stringLengthAttribute?.MaximumLength ?? 0, inputType, fieldLabel, fieldID, groupDataBinding, labelDataBinding, requiredDataBinding, customDataBinding, dependencyFieldName, toolTip, initialFocus); | ||
| 521 | } | ||
| 522 | |||
| 523 | /// <summary> | ||
| 524 | /// Generates template based input date field based on specified parameters. | ||
| 525 | /// </summary> | ||
| 526 | /// <param name="fieldName">Field name for input date field.</param> | ||
| 527 | /// <param name="required">Determines if field name is required.</param> | ||
| 528 | /// <param name="maxLength">Defines maximum input field length.</param> | ||
| 529 | /// <param name="inputType">Input field type, defaults to text.</param> | ||
| 530 | /// <param name="fieldLabel">Label name for input date field, defaults to <paramref name="fieldName"/>.</param> | ||
| 531 | /// <param name="fieldID">ID to use for input field; defaults to date + <paramref name="fieldName"/>.</param> | ||
| 532 | /// <param name="groupDataBinding">Data-bind operations to apply to outer form-group div, if any.</param> | ||
| 533 | /// <param name="labelDataBinding">Data-bind operations to apply to label, if any.</param> | ||
| 534 | /// <param name="requiredDataBinding">Boolean data-bind operation to apply to required state, if any.</param> | ||
| 535 | /// <param name="customDataBinding">Extra custom data-binding operations to apply to field, if any.</param> | ||
| 536 | /// <param name="dependencyFieldName">Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.</param> | ||
| 537 | /// <param name="toolTip">Tool tip text to apply to field, if any.</param> | ||
| 538 | /// <param name="initialFocus">Use field for initial focus.</param> | ||
| 539 | /// <returns>Generated HTML for new date based input field based on specified parameters.</returns> | ||
| 540 | public string AddDateField(string fieldName, bool required, int maxLength = 0, string inputType = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false) | ||
| 541 | { | ||
| 542 | RazorView<TLanguage> addDateFieldTemplate = new RazorView<TLanguage>(AddDateFieldTemplate, m_exceptionHandler); | ||
| 543 | DynamicViewBag viewBag = addDateFieldTemplate.ViewBag; | ||
| 544 | |||
| 545 | if (string.IsNullOrEmpty(fieldID)) | ||
| 546 | fieldID = $"date{fieldName}"; | ||
| 547 | |||
| 548 | if (initialFocus) | ||
| 549 | m_initialFocusField = fieldID; | ||
| 550 | |||
| 551 | viewBag.AddValue("FieldName", fieldName); | ||
| 552 | viewBag.AddValue("Required", required); | ||
| 553 | viewBag.AddValue("MaxLength", maxLength); | ||
| 554 | viewBag.AddValue("InputType", inputType ?? "text"); | ||
| 555 | viewBag.AddValue("FieldLabel", fieldLabel ?? fieldName); | ||
| 556 | viewBag.AddValue("FieldID", fieldID); | ||
| 557 | viewBag.AddValue("GroupDataBinding", groupDataBinding); | ||
| 558 | viewBag.AddValue("LabelDataBinding", labelDataBinding); | ||
| 559 | viewBag.AddValue("RequiredDataBinding", requiredDataBinding); | ||
| 560 | viewBag.AddValue("CustomDataBinding", customDataBinding); | ||
| 561 | viewBag.AddValue("DependencyFieldName", dependencyFieldName); | ||
| 562 | viewBag.AddValue("ToolTip", toolTip); | ||
| 563 | |||
| 564 | m_definedDateFields.Add(fieldName); | ||
| 565 | |||
| 566 | return addDateFieldTemplate.Execute(); | ||
| 567 | } | ||
| 568 | |||
| 569 | /// <summary> | ||
| 570 | /// Generates template based input text field based on reflected modeled table field attributes. | ||
| 571 | /// </summary> | ||
| 572 | /// <typeparam name="T">Modeled table.</typeparam> | ||
| 573 | /// <param name="fieldName">Field name for input text field.</param> | ||
| 574 | /// <param name="inputType">Input field type, defaults to text.</param> | ||
| 575 | /// <param name="fieldLabel">Label name for input text field, pulls from <see cref="LabelAttribute"/> if defined, otherwise defaults to <paramref name="fieldName"/>.</param> | ||
| 576 | /// <param name="fieldID">ID to use for input field; defaults to input + <paramref name="fieldName"/>.</param> | ||
| 577 | /// <param name="groupDataBinding">Data-bind operations to apply to outer form-group div, if any.</param> | ||
| 578 | /// <param name="labelDataBinding">Data-bind operations to apply to label, if any.</param> | ||
| 579 | /// <param name="requiredDataBinding">Boolean data-bind operation to apply to required state, if any.</param> | ||
| 580 | /// <param name="customDataBinding">Extra custom data-binding operations to apply to field, if any.</param> | ||
| 581 | /// <param name="dependencyFieldName">Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.</param> | ||
| 582 | /// <param name="toolTip">Tool tip text to apply to field, if any.</param> | ||
| 583 | /// <param name="initialFocus">Use field for initial focus.</param> | ||
| 584 | /// <returns>Generated HTML for new input field based on modeled table field attributes.</returns> | ||
| 585 | public string AddInputField<T>(string fieldName, string inputType = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false) where T : class, new() | ||
| 586 | { | ||
| 587 | TableOperations<T> tableOperations = Table<T>(); | ||
| 588 | StringLengthAttribute stringLengthAttribute; | ||
| 589 | RegularExpressionAttribute regularExpressionAttribute; | ||
| 590 | |||
| 591 | if (string.IsNullOrEmpty(inputType)) | ||
| 592 | { | ||
| 593 | Type fieldType = tableOperations.GetFieldType(fieldName); | ||
| 594 | |||
| 595 | if (IsNumericType(fieldType)) | ||
| 596 | inputType = "number"; | ||
| 597 | else if (fieldType == typeof(DateTime)) | ||
| 598 | inputType = "date"; | ||
| 599 | } | ||
| 600 | |||
| 601 | if (string.IsNullOrEmpty(fieldLabel)) | ||
| 602 | { | ||
| 603 | LabelAttribute labelAttribute; | ||
| 604 | |||
| 605 | if (tableOperations.TryGetFieldAttribute(fieldName, out labelAttribute)) | ||
| 606 | fieldLabel = labelAttribute.Label; | ||
| 607 | } | ||
| 608 | |||
| 609 | tableOperations.TryGetFieldAttribute(fieldName, out stringLengthAttribute); | ||
| 610 | tableOperations.TryGetFieldAttribute(fieldName, out regularExpressionAttribute); | ||
| 611 | |||
| 612 | if ((object)regularExpressionAttribute != null) | ||
| 613 | { | ||
| 614 | string observableReference; | ||
| 615 | |||
| 616 | if (string.IsNullOrEmpty(groupDataBinding)) | ||
| 617 | observableReference = $"viewModel.currentRecord().{fieldName}"; | ||
| 618 | else // "with: $root.connectionString" | ||
| 619 | observableReference = $"viewModel.{groupDataBinding.Substring(groupDataBinding.IndexOf('.') + 1)}"; | ||
| 620 | |||
| 621 | AddFieldValidation(observableReference, regularExpressionAttribute.Pattern, regularExpressionAttribute.ErrorMessage ?? "Invalid format."); | ||
| 622 | } | ||
| 623 | |||
| 624 | AddFieldValueInitializer<T>(fieldName); | ||
| 625 | |||
| 626 | return AddInputField(fieldName, tableOperations.FieldHasAttribute<RequiredAttribute>(fieldName), | ||
| 627 | stringLengthAttribute?.MaximumLength ?? 0, inputType, fieldLabel, fieldID, groupDataBinding, labelDataBinding, requiredDataBinding, customDataBinding, dependencyFieldName, toolTip, initialFocus); | ||
| 628 | } | ||
| 629 | |||
| 630 | /// <summary> | ||
| 631 | /// Generates template based input text field based on specified parameters. | ||
| 632 | /// </summary> | ||
| 633 | /// <param name="fieldName">Field name for input text field.</param> | ||
| 634 | /// <param name="required">Determines if field name is required.</param> | ||
| 635 | /// <param name="maxLength">Defines maximum input field length.</param> | ||
| 636 | /// <param name="inputType">Input field type, defaults to text.</param> | ||
| 637 | /// <param name="fieldLabel">Label name for input text field, defaults to <paramref name="fieldName"/>.</param> | ||
| 638 | /// <param name="fieldID">ID to use for input field; defaults to input + <paramref name="fieldName"/>.</param> | ||
| 639 | /// <param name="groupDataBinding">Data-bind operations to apply to outer form-group div, if any.</param> | ||
| 640 | /// <param name="labelDataBinding">Data-bind operations to apply to label, if any.</param> | ||
| 641 | /// <param name="requiredDataBinding">Boolean data-bind operation to apply to required state, if any.</param> | ||
| 642 | /// <param name="customDataBinding">Extra custom data-binding operations to apply to field, if any.</param> | ||
| 643 | /// <param name="dependencyFieldName">Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.</param> | ||
| 644 | /// <param name="toolTip">Tool tip text to apply to field, if any.</param> | ||
| 645 | /// <param name="initialFocus">Use field for initial focus.</param> | ||
| 646 | /// <returns>Generated HTML for new input field based on specified parameters.</returns> | ||
| 647 | public string AddInputField(string fieldName, bool required, int maxLength = 0, string inputType = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false) | ||
| 648 | { | ||
| 649 | RazorView<TLanguage> addInputFieldTemplate = new RazorView<TLanguage>(AddInputFieldTemplate, m_exceptionHandler); | ||
| 650 | DynamicViewBag viewBag = addInputFieldTemplate.ViewBag; | ||
| 651 | |||
| 652 | if (string.IsNullOrEmpty(fieldID)) | ||
| 653 | fieldID = $"input{fieldName}"; | ||
| 654 | |||
| 655 | if (initialFocus) | ||
| 656 | m_initialFocusField = fieldID; | ||
| 657 | |||
| 658 | viewBag.AddValue("FieldName", fieldName); | ||
| 659 | viewBag.AddValue("Required", required); | ||
| 660 | viewBag.AddValue("MaxLength", maxLength); | ||
| 661 | viewBag.AddValue("InputType", inputType ?? "text"); | ||
| 662 | viewBag.AddValue("FieldLabel", fieldLabel ?? fieldName); | ||
| 663 | viewBag.AddValue("FieldID", fieldID); | ||
| 664 | viewBag.AddValue("GroupDataBinding", groupDataBinding); | ||
| 665 | viewBag.AddValue("LabelDataBinding", labelDataBinding); | ||
| 666 | viewBag.AddValue("RequiredDataBinding", requiredDataBinding); | ||
| 667 | viewBag.AddValue("CustomDataBinding", customDataBinding); | ||
| 668 | viewBag.AddValue("DependencyFieldName", dependencyFieldName); | ||
| 669 | viewBag.AddValue("ToolTip", toolTip); | ||
| 670 | |||
| 671 | return addInputFieldTemplate.Execute(); | ||
| 672 | } | ||
| 673 | |||
| 674 | /// <summary> | ||
| 675 | /// Generates template based text area field based on reflected modeled table field attributes. | ||
| 676 | /// </summary> | ||
| 677 | /// <typeparam name="T">Modeled table.</typeparam> | ||
| 678 | /// <param name="fieldName">Field name for text area field.</param> | ||
| 679 | /// <param name="rows">Number of rows for text area.</param> | ||
| 680 | /// <param name="fieldLabel">Label name for text area field, pulls from <see cref="LabelAttribute"/> if defined, otherwise defaults to <paramref name="fieldName"/>.</param> | ||
| 681 | /// <param name="fieldID">ID to use for text area field; defaults to text + <paramref name="fieldName"/>.</param> | ||
| 682 | /// <param name="groupDataBinding">Data-bind operations to apply to outer form-group div, if any.</param> | ||
| 683 | /// <param name="labelDataBinding">Data-bind operations to apply to label, if any.</param> | ||
| 684 | /// <param name="requiredDataBinding">Boolean data-bind operation to apply to required state, if any.</param> | ||
| 685 | /// <param name="customDataBinding">Extra custom data-binding operations to apply to field, if any.</param> | ||
| 686 | /// <param name="dependencyFieldName">Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.</param> | ||
| 687 | /// <param name="toolTip">Tool tip text to apply to field, if any.</param> | ||
| 688 | /// <param name="initialFocus">Use field for initial focus.</param> | ||
| 689 | /// <returns>Generated HTML for new text area field based on modeled table field attributes.</returns> | ||
| 690 | public string AddTextAreaField<T>(string fieldName, int rows = 2, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false) where T : class, new() | ||
| 691 | { | ||
| 692 | TableOperations<T> tableOperations = Table<T>(); | ||
| 693 | StringLengthAttribute stringLengthAttribute; | ||
| 694 | RegularExpressionAttribute regularExpressionAttribute; | ||
| 695 | |||
| 696 | if (string.IsNullOrEmpty(fieldLabel)) | ||
| 697 | { | ||
| 698 | LabelAttribute labelAttribute; | ||
| 699 | |||
| 700 | if (tableOperations.TryGetFieldAttribute(fieldName, out labelAttribute)) | ||
| 701 | fieldLabel = labelAttribute.Label; | ||
| 702 | } | ||
| 703 | |||
| 704 | tableOperations.TryGetFieldAttribute(fieldName, out stringLengthAttribute); | ||
| 705 | tableOperations.TryGetFieldAttribute(fieldName, out regularExpressionAttribute); | ||
| 706 | |||
| 707 | if ((object)regularExpressionAttribute != null) | ||
| 708 | { | ||
| 709 | string observableReference; | ||
| 710 | |||
| 711 | if (string.IsNullOrEmpty(groupDataBinding)) | ||
| 712 | observableReference = $"viewModel.currentRecord().{fieldName}"; | ||
| 713 | else // "with: $root.connectionString" | ||
| 714 | observableReference = $"viewModel.{groupDataBinding.Substring(groupDataBinding.IndexOf('.') + 1)}"; | ||
| 715 | |||
| 716 | AddFieldValidation(observableReference, regularExpressionAttribute.Pattern, regularExpressionAttribute.ErrorMessage ?? "Invalid format."); | ||
| 717 | } | ||
| 718 | |||
| 719 | AddFieldValueInitializer<T>(fieldName); | ||
| 720 | |||
| 721 | return AddTextAreaField(fieldName, tableOperations.FieldHasAttribute<RequiredAttribute>(fieldName), | ||
| 722 | stringLengthAttribute?.MaximumLength ?? 0, rows, fieldLabel, fieldID, groupDataBinding, labelDataBinding, requiredDataBinding, customDataBinding, dependencyFieldName, toolTip, initialFocus); | ||
| 723 | } | ||
| 724 | |||
| 725 | /// <summary> | ||
| 726 | /// Generates template based text area field based on specified parameters. | ||
| 727 | /// </summary> | ||
| 728 | /// <param name="fieldName">Field name for text area field.</param> | ||
| 729 | /// <param name="required">Determines if field name is required.</param> | ||
| 730 | /// <param name="maxLength">Defines maximum text area field length.</param> | ||
| 731 | /// <param name="rows">Number of rows for text area.</param> | ||
| 732 | /// <param name="fieldLabel">Label name for text area field, defaults to <paramref name="fieldName"/>.</param> | ||
| 733 | /// <param name="fieldID">ID to use for text area field; defaults to text + <paramref name="fieldName"/>.</param> | ||
| 734 | /// <param name="groupDataBinding">Data-bind operations to apply to outer form-group div, if any.</param> | ||
| 735 | /// <param name="labelDataBinding">Data-bind operations to apply to label, if any.</param> | ||
| 736 | /// <param name="requiredDataBinding">Boolean data-bind operation to apply to required state, if any.</param> | ||
| 737 | /// <param name="customDataBinding">Extra custom data-binding operations to apply to field, if any.</param> | ||
| 738 | /// <param name="dependencyFieldName">Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.</param> | ||
| 739 | /// <param name="toolTip">Tool tip text to apply to field, if any.</param> | ||
| 740 | /// <param name="initialFocus">Use field for initial focus.</param> | ||
| 741 | /// <returns>Generated HTML for new text area field based on specified parameters.</returns> | ||
| 742 | public string AddTextAreaField(string fieldName, bool required, int maxLength = 0, int rows = 2, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string requiredDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false) | ||
| 743 | { | ||
| 744 | RazorView<TLanguage> addTextAreaTemplate = new RazorView<TLanguage>(AddTextAreaFieldTemplate, m_exceptionHandler); | ||
| 745 | DynamicViewBag viewBag = addTextAreaTemplate.ViewBag; | ||
| 746 | |||
| 747 | if (string.IsNullOrEmpty(fieldID)) | ||
| 748 | fieldID = $"text{fieldName}"; | ||
| 749 | |||
| 750 | if (initialFocus) | ||
| 751 | m_initialFocusField = fieldID; | ||
| 752 | |||
| 753 | viewBag.AddValue("FieldName", fieldName); | ||
| 754 | viewBag.AddValue("Required", required); | ||
| 755 | viewBag.AddValue("MaxLength", maxLength); | ||
| 756 | viewBag.AddValue("Rows", rows); | ||
| 757 | viewBag.AddValue("FieldLabel", fieldLabel ?? fieldName); | ||
| 758 | viewBag.AddValue("FieldID", fieldID); | ||
| 759 | viewBag.AddValue("GroupDataBinding", groupDataBinding); | ||
| 760 | viewBag.AddValue("LabelDataBinding", labelDataBinding); | ||
| 761 | viewBag.AddValue("RequiredDataBinding", requiredDataBinding); | ||
| 762 | viewBag.AddValue("CustomDataBinding", customDataBinding); | ||
| 763 | viewBag.AddValue("DependencyFieldName", dependencyFieldName); | ||
| 764 | viewBag.AddValue("ToolTip", toolTip); | ||
| 765 | |||
| 766 | return addTextAreaTemplate.Execute(); | ||
| 767 | } | ||
| 768 | |||
| 769 | /// <summary> | ||
| 770 | /// Generates template based select field based on reflected modeled table field attributes. | ||
| 771 | /// </summary> | ||
| 772 | /// <typeparam name="TSelect">Modeled table for select field.</typeparam> | ||
| 773 | /// <typeparam name="TOption">Modeled table for option data.</typeparam> | ||
| 774 | /// <param name="fieldName">Field name for value of select field.</param> | ||
| 775 | /// <param name="optionValueFieldName">Field name for ID of option data.</param> | ||
| 776 | /// <param name="optionLabelFieldName">Field name for label of option data, defaults to <paramref name="optionValueFieldName"/></param> | ||
| 777 | /// <param name="optionSortFieldName">Field name for sort order of option data, defaults to <paramref name="optionLabelFieldName"/></param> | ||
| 778 | /// <param name="fieldLabel">Label name for select field, pulls from <see cref="LabelAttribute"/> if defined, otherwise defaults to <paramref name="fieldName"/>.</param> | ||
| 779 | /// <param name="fieldID">ID to use for select field; defaults to select + <paramref name="fieldName"/>.</param> | ||
| 780 | /// <param name="groupDataBinding">Data-bind operations to apply to outer form-group div, if any.</param> | ||
| 781 | /// <param name="labelDataBinding">Data-bind operations to apply to label, if any.</param> | ||
| 782 | /// <param name="customDataBinding">Extra custom data-binding operations to apply to field, if any.</param> | ||
| 783 | /// <param name="dependencyFieldName">Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.</param> | ||
| 784 | /// <param name="optionDataBinding">Data-bind operations to apply to each option value, if any.</param> | ||
| 785 | /// <param name="toolTip">Tool tip text to apply to field, if any.</param> | ||
| 786 | /// <param name="initialFocus">Use field for initial focus.</param> | ||
| 787 | /// <param name="restriction">Record restriction to apply, if any.</param> | ||
| 788 | /// <returns>Generated HTML for new text field based on modeled table field attributes.</returns> | ||
| 789 | public string AddSelectField<TSelect, TOption>(string fieldName, string optionValueFieldName, string optionLabelFieldName = null, string optionSortFieldName = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string optionDataBinding = null, string toolTip = null, bool initialFocus = false, RecordRestriction restriction = null) where TSelect : class, new() where TOption : class, new() | ||
| 790 | { | ||
| 791 | if (string.IsNullOrEmpty(fieldLabel)) | ||
| 792 | { | ||
| 793 | LabelAttribute labelAttribute; | ||
| 794 | |||
| 795 | if (Table<TSelect>().TryGetFieldAttribute(fieldName, out labelAttribute)) | ||
| 796 | fieldLabel = labelAttribute.Label; | ||
| 797 | } | ||
| 798 | |||
| 799 | AddFieldValueInitializer<TSelect>(fieldName); | ||
| 800 | |||
| 801 | return AddSelectField<TOption>(fieldName, Table<TSelect>().FieldHasAttribute<RequiredAttribute>(fieldName), | ||
| 802 | optionValueFieldName, optionLabelFieldName, optionSortFieldName, fieldLabel, fieldID, groupDataBinding, labelDataBinding, customDataBinding, dependencyFieldName, optionDataBinding, toolTip, initialFocus, restriction); | ||
| 803 | } | ||
| 804 | |||
| 805 | /// <summary> | ||
| 806 | /// Generates template based select field based on specified parameters. | ||
| 807 | /// </summary> | ||
| 808 | /// <typeparam name="TOption">Modeled table for option data.</typeparam> | ||
| 809 | /// <param name="fieldName">Field name for value of select field.</param> | ||
| 810 | /// <param name="required">Determines if field name is required.</param> | ||
| 811 | /// <param name="optionValueFieldName">Field name for ID of option data.</param> | ||
| 812 | /// <param name="optionLabelFieldName">Field name for label of option data, defaults to <paramref name="optionValueFieldName"/></param> | ||
| 813 | /// <param name="optionSortFieldName">Field name for sort order of option data, defaults to <paramref name="optionLabelFieldName"/></param> | ||
| 814 | /// <param name="fieldLabel">Label name for select field, defaults to <paramref name="fieldName"/>.</param> | ||
| 815 | /// <param name="fieldID">ID to use for select field; defaults to select + <paramref name="fieldName"/>.</param> | ||
| 816 | /// <param name="groupDataBinding">Data-bind operations to apply to outer form-group div, if any.</param> | ||
| 817 | /// <param name="labelDataBinding">Data-bind operations to apply to label, if any.</param> | ||
| 818 | /// <param name="customDataBinding">Extra custom data-binding operations to apply to field, if any.</param> | ||
| 819 | /// <param name="dependencyFieldName">Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.</param> | ||
| 820 | /// <param name="optionDataBinding">Data-bind operations to apply to each option value, if any.</param> | ||
| 821 | /// <param name="toolTip">Tool tip text to apply to field, if any.</param> | ||
| 822 | /// <param name="initialFocus">Use field for initial focus.</param> | ||
| 823 | /// <param name="restriction">Record restriction to apply, if any.</param> | ||
| 824 | /// <returns>Generated HTML for new text field based on specified parameters.</returns> | ||
| 825 | public string AddSelectField<TOption>(string fieldName, bool required, string optionValueFieldName, string optionLabelFieldName = null, string optionSortFieldName = null, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string optionDataBinding = null, string toolTip = null, bool initialFocus = false, RecordRestriction restriction = null) where TOption : class, new() | ||
| 826 | { | ||
| 827 | RazorView<TLanguage> addSelectFieldTemplate = new RazorView<TLanguage>(AddSelectFieldTemplate, m_exceptionHandler); | ||
| 828 | DynamicViewBag viewBag = addSelectFieldTemplate.ViewBag; | ||
| 829 | TableOperations<TOption> optionTableOperations = Table<TOption>(); | ||
| 830 | Dictionary<string, string> options = new Dictionary<string, string>(); | ||
| 831 | string optionTableName = typeof(TOption).Name; | ||
| 832 | |||
| 833 | optionLabelFieldName = optionLabelFieldName ?? optionValueFieldName; | ||
| 834 | optionSortFieldName = optionSortFieldName ?? optionLabelFieldName; | ||
| 835 | fieldLabel = fieldLabel ?? optionTableName; | ||
| 836 | |||
| 837 | if (string.IsNullOrEmpty(fieldID)) | ||
| 838 | fieldID = $"select{fieldName}"; | ||
| 839 | |||
| 840 | if (initialFocus) | ||
| 841 | m_initialFocusField = fieldID; | ||
| 842 | |||
| 843 | viewBag.AddValue("FieldName", fieldName); | ||
| 844 | viewBag.AddValue("Required", required); | ||
| 845 | viewBag.AddValue("FieldLabel", fieldLabel); | ||
| 846 | viewBag.AddValue("FieldID", fieldID); | ||
| 847 | viewBag.AddValue("GroupDataBinding", groupDataBinding); | ||
| 848 | viewBag.AddValue("LabelDataBinding", labelDataBinding); | ||
| 849 | viewBag.AddValue("CustomDataBinding", customDataBinding); | ||
| 850 | viewBag.AddValue("DependencyFieldName", dependencyFieldName); | ||
| 851 | viewBag.AddValue("ToolTip", toolTip); | ||
| 852 | viewBag.AddValue("OptionDataBinding", optionDataBinding); | ||
| 853 | |||
| 854 | foreach (TOption record in Table<TOption>().QueryRecords(optionSortFieldName, restriction)) | ||
| 855 | { | ||
| 856 | if (record != null) | ||
| 857 | options.Add(optionTableOperations.GetFieldValue(record, optionValueFieldName).ToString(), optionTableOperations.GetFieldValue(record, optionLabelFieldName).ToNonNullString(fieldLabel)); | ||
| 858 | } | ||
| 859 | |||
| 860 | viewBag.AddValue("Options", options); | ||
| 861 | |||
| 862 | return addSelectFieldTemplate.Execute(); | ||
| 863 | } | ||
| 864 | |||
| 865 | /// <summary> | ||
| 866 | /// Generates template based check box field based on reflected modeled table field attributes. | ||
| 867 | /// </summary> | ||
| 868 | /// <typeparam name="T">Modeled table.</typeparam> | ||
| 869 | /// <param name="fieldName">Field name for value of check box field.</param> | ||
| 870 | /// <param name="fieldLabel">Label name for check box field, defaults to <paramref name="fieldName"/>.</param> | ||
| 871 | /// <param name="fieldID">ID to use for check box field; defaults to check + <paramref name="fieldName"/>.</param> | ||
| 872 | /// <param name="groupDataBinding">Data-bind operations to apply to outer form-group div, if any.</param> | ||
| 873 | /// <param name="labelDataBinding">Data-bind operations to apply to label, if any.</param> | ||
| 874 | /// <param name="customDataBinding">Extra custom data-binding operations to apply to field, if any.</param> | ||
| 875 | /// <param name="dependencyFieldName">Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.</param> | ||
| 876 | /// <param name="toolTip">Tool tip text to apply to field, if any.</param> | ||
| 877 | /// <param name="initialFocus">Use field for initial focus.</param> | ||
| 878 | /// <returns>Generated HTML for new check box field based on modeled table field attributes.</returns> | ||
| 879 | public string AddCheckBoxField<T>(string fieldName, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false) where T : class, new() | ||
| 880 | { | ||
| 881 | if (string.IsNullOrEmpty(fieldLabel)) | ||
| 882 | { | ||
| 883 | LabelAttribute labelAttribute; | ||
| 884 | |||
| 885 | if (Table<T>().TryGetFieldAttribute(fieldName, out labelAttribute)) | ||
| 886 | fieldLabel = labelAttribute.Label; | ||
| 887 | } | ||
| 888 | |||
| 889 | AddFieldValueInitializer<T>(fieldName); | ||
| 890 | |||
| 891 | return AddCheckBoxField(fieldName, fieldLabel, fieldID, groupDataBinding, labelDataBinding, customDataBinding, dependencyFieldName, toolTip, initialFocus); | ||
| 892 | } | ||
| 893 | |||
| 894 | /// <summary> | ||
| 895 | /// Generates template based check box field based on specified parameters. | ||
| 896 | /// </summary> | ||
| 897 | /// <param name="fieldName">Field name for value of check box field.</param> | ||
| 898 | /// <param name="fieldLabel">Label name for check box field, defaults to <paramref name="fieldName"/>.</param> | ||
| 899 | /// <param name="fieldID">ID to use for check box field; defaults to check + <paramref name="fieldName"/>.</param> | ||
| 900 | /// <param name="groupDataBinding">Data-bind operations to apply to outer form-group div, if any.</param> | ||
| 901 | /// <param name="labelDataBinding">Data-bind operations to apply to label, if any.</param> | ||
| 902 | /// <param name="customDataBinding">Extra custom data-binding operations to apply to field, if any.</param> | ||
| 903 | /// <param name="dependencyFieldName">Defines default "enabled" subordinate data-bindings based a single boolean field, e.g., a check-box.</param> | ||
| 904 | /// <param name="toolTip">Tool tip text to apply to field, if any.</param> | ||
| 905 | /// <param name="initialFocus">Use field for initial focus.</param> | ||
| 906 | /// <returns>Generated HTML for new check box field based on specified parameters.</returns> | ||
| 907 | public string AddCheckBoxField(string fieldName, string fieldLabel = null, string fieldID = null, string groupDataBinding = null, string labelDataBinding = null, string customDataBinding = null, string dependencyFieldName = null, string toolTip = null, bool initialFocus = false) | ||
| 908 | { | ||
| 909 | RazorView<TLanguage> addCheckBoxFieldTemplate = new RazorView<TLanguage>(AddCheckBoxFieldTemplate, m_exceptionHandler); | ||
| 910 | DynamicViewBag viewBag = addCheckBoxFieldTemplate.ViewBag; | ||
| 911 | |||
| 912 | if (string.IsNullOrEmpty(fieldID)) | ||
| 913 | fieldID = $"check{fieldName}"; | ||
| 914 | |||
| 915 | if (initialFocus) | ||
| 916 | m_initialFocusField = fieldID; | ||
| 917 | |||
| 918 | viewBag.AddValue("FieldName", fieldName); | ||
| 919 | viewBag.AddValue("FieldLabel", fieldLabel ?? fieldName); | ||
| 920 | viewBag.AddValue("FieldID", fieldID); | ||
| 921 | viewBag.AddValue("GroupDataBinding", groupDataBinding); | ||
| 922 | viewBag.AddValue("LabelDataBinding", labelDataBinding); | ||
| 923 | viewBag.AddValue("CustomDataBinding", customDataBinding); | ||
| 924 | viewBag.AddValue("DependencyFieldName", dependencyFieldName); | ||
| 925 | viewBag.AddValue("ToolTip", toolTip); | ||
| 926 | |||
| 927 | return addCheckBoxFieldTemplate.Execute(); | ||
| 928 | } | ||
| 929 | |||
| 930 | #endregion | ||
| 931 | |||
| 932 | #region [ Static ] | ||
| 933 | |||
| 934 | // Static Methods | ||
| 935 | private static bool IsNumericType(Type type) | ||
| 936 | { | ||
| 937 | return | ||
| 938 | type == typeof(byte) || | ||
| 939 | type == typeof(sbyte) || | ||
| 940 | type == typeof(short) || | ||
| 941 | type == typeof(ushort) || | ||
| 942 | type == typeof(Int24) || | ||
| 943 | type == typeof(UInt24) || | ||
| 944 | type == typeof(int) || | ||
| 945 | type == typeof(uint) || | ||
| 946 | type == typeof(long) || | ||
| 947 | type == typeof(ulong) || | ||
| 948 | type == typeof(float) || | ||
| 949 | type == typeof(double) || | ||
| 950 | type == typeof(decimal); | ||
| 951 | } | ||
| 952 | |||
| 953 | #endregion | ||
| 954 | } | ||
| 955 | } | ||
| @@ -0,0 +1,91 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // RecordOperationsCache.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 01/14/2016 - Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.Collections.Generic; | ||
| 26 | using System.Reflection; | ||
| 27 | using GSF.Collections; | ||
| 28 | using GSF.Data.Model; | ||
| 29 | using GSF.Reflection; | ||
| 30 | using GSF.Web.Security; | ||
| 31 | |||
| 32 | namespace GSF.Web.Model | ||
| 33 | { | ||
| 34 | /// <summary> | ||
| 35 | /// Defines class that caches data hub operations. | ||
| 36 | /// </summary> | ||
| 37 | public class RecordOperationsCache | ||
| 38 | { | ||
| 39 | #region [ Members ] | ||
| 40 | |||
| 41 | // Fields | ||
| 42 | private readonly Dictionary<Type, Tuple<string, string>[]> m_recordOperations; | ||
| 43 | |||
| 44 | #endregion | ||
| 45 | |||
| 46 | #region [ Constructors ] | ||
| 47 | |||
| 48 | /// <summary> | ||
| 49 | /// Creates a new <see cref="RecordOperationsCache"/>. | ||
| 50 | /// </summary> | ||
| 51 | /// <param name="hub">Hub to analyze for record operations.</param> | ||
| 52 | public RecordOperationsCache(Type hub) | ||
| 53 | { | ||
| 54 | int recordOperations = Enum.GetValues(typeof(RecordOperation)).Length; | ||
| 55 | m_recordOperations = new Dictionary<Type, Tuple<string, string>[]>(); | ||
| 56 | |||
| 57 | // Analyze and cache data hub methods that are targeted for record operations | ||
| 58 | foreach (MethodInfo method in hub.GetMethods(BindingFlags.Public | BindingFlags.Instance)) | ||
| 59 | { | ||
| 60 | AuthorizeHubRoleAttribute authorizeHubRoleAttribute; | ||
| 61 | RecordOperationAttribute recordOperationAttribute; | ||
| 62 | |||
| 63 | method.TryGetAttribute(out authorizeHubRoleAttribute); | ||
| 64 | |||
| 65 | if (method.TryGetAttribute(out recordOperationAttribute)) | ||
| 66 | { | ||
| 67 | // Cache method name and any defined authorized roles for current record operation | ||
| 68 | m_recordOperations.GetOrAdd(recordOperationAttribute.ModelType, | ||
| 69 | type => new Tuple<string, string>[recordOperations])[(int)recordOperationAttribute.Operation] = | ||
| 70 | new Tuple<string, string>(method.Name, authorizeHubRoleAttribute?.Roles); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | #endregion | ||
| 76 | |||
| 77 | #region [ Methods ] | ||
| 78 | |||
| 79 | /// <summary> | ||
| 80 | /// Gets record operation methods for specified modeled table. | ||
| 81 | /// </summary> | ||
| 82 | /// <typeparam name="T">Modeled table.</typeparam> | ||
| 83 | /// <returns>record operation methods for specified modeled table.</returns> | ||
| 84 | public Tuple<string, string>[] GetRecordOperations<T>() where T : class, new() | ||
| 85 | { | ||
| 86 | return m_recordOperations[typeof(T)]; | ||
| 87 | } | ||
| 88 | |||
| 89 | #endregion | ||
| 90 | } | ||
| 91 | } | ||
| @@ -0,0 +1,667 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // gsf.web.client.js - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 01/26/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | // ReSharper disable NativeTypePrototypeExtending | ||
| 24 | |||
| 25 | // Grid Solutions Framework Core Web Client Script Functions | ||
| 26 | "use strict"; | ||
| 27 | |||
| 28 | const isIE = detectIE(); | ||
| 29 | var textMetrics; | ||
| 30 | |||
| 31 | $(function () { | ||
| 32 | // Create a canvas object that will be used for text metrics calculations | ||
| 33 | $("<canvas id=\"textMetricsCanvas\" height=\"1px\" width=\"1px\" style=\"visibility: hidden\"></canvas>").appendTo("body"); | ||
| 34 | |||
| 35 | // Get text metrics canvas context | ||
| 36 | textMetrics = document.getElementById("textMetricsCanvas").getContext("2d"); | ||
| 37 | }); | ||
| 38 | |||
| 39 | // Miscellaneous functions | ||
| 40 | function notNull(value, nonNullValue) { | ||
| 41 | return value || (nonNullValue || ""); | ||
| 42 | } | ||
| 43 | |||
| 44 | function detectIE() { | ||
| 45 | const ua = window.navigator.userAgent; | ||
| 46 | const msie = ua.indexOf("MSIE "); | ||
| 47 | |||
| 48 | if (msie > 0) { | ||
| 49 | // IE 10 or older => return version number | ||
| 50 | return parseInt(ua.substring(msie + 5, ua.indexOf(".", msie)), 10); | ||
| 51 | } | ||
| 52 | |||
| 53 | const trident = ua.indexOf("Trident/"); | ||
| 54 | |||
| 55 | if (trident > 0) { | ||
| 56 | // IE 11 => return version number | ||
| 57 | const rv = ua.indexOf("rv:"); | ||
| 58 | return parseInt(ua.substring(rv + 3, ua.indexOf(".", rv)), 10); | ||
| 59 | } | ||
| 60 | |||
| 61 | const edge = ua.indexOf("Edge/"); | ||
| 62 | |||
| 63 | if (edge > 0) { | ||
| 64 | // Edge (IE 12+) => return version number | ||
| 65 | return parseInt(ua.substring(edge + 5, ua.indexOf(".", edge)), 10); | ||
| 66 | } | ||
| 67 | |||
| 68 | // Other browser | ||
| 69 | return false; | ||
| 70 | } | ||
| 71 | |||
| 72 | function isNumber(val) { | ||
| 73 | return !isNaN(parseFloat(val)) && isFinite(val); | ||
| 74 | } | ||
| 75 | |||
| 76 | function isBool(val) { | ||
| 77 | if (typeof value === "boolean") | ||
| 78 | return true; | ||
| 79 | |||
| 80 | const lval = val.toString().toLowerCase(); | ||
| 81 | return lval === "true" || lval === "false"; | ||
| 82 | } | ||
| 83 | |||
| 84 | function getBool(val) { | ||
| 85 | const num = +val; | ||
| 86 | return !isNaN(num) ? !!num : !!String(val).toLowerCase().replace(false, ""); | ||
| 87 | } | ||
| 88 | |||
| 89 | // Number functions | ||
| 90 | Number.prototype.truncate = function() { | ||
| 91 | if (typeof Math.trunc != "function") | ||
| 92 | return parseInt(this.toString()); | ||
| 93 | |||
| 94 | return Math.trunc(this); | ||
| 95 | } | ||
| 96 | |||
| 97 | Number.prototype.padLeft = function (totalWidth, paddingChar) { | ||
| 98 | return this.truncate().toString().padLeft(totalWidth, paddingChar || "0"); | ||
| 99 | } | ||
| 100 | |||
| 101 | Number.prototype.padRight = function (totalWidth, paddingChar) { | ||
| 102 | return this.truncate().toString().padRight(totalWidth, paddingChar || "0"); | ||
| 103 | } | ||
| 104 | |||
| 105 | // Array functions | ||
| 106 | |||
| 107 | // Combines a dictionary of key-value pairs into a string. Values will be escaped within startValueDelimiter and endValueDelimiter | ||
| 108 | // to contain nested key/value pair expressions like the following: "normalKVP=-1; nestedKVP={p1=true; p2=0.001}" when either the | ||
| 109 | // parameterDelimiter or the keyValueDelimiter are detected in the value of the key/value pair. | ||
| 110 | function joinKeyValuePairs (source, parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter) { | ||
| 111 | if (!parameterDelimiter) | ||
| 112 | parameterDelimiter = ";"; | ||
| 113 | |||
| 114 | if (!keyValueDelimiter) | ||
| 115 | keyValueDelimiter = "="; | ||
| 116 | |||
| 117 | if (!startValueDelimiter) | ||
| 118 | startValueDelimiter = "{"; | ||
| 119 | |||
| 120 | if (!endValueDelimiter) | ||
| 121 | endValueDelimiter = "}"; | ||
| 122 | |||
| 123 | const values = []; | ||
| 124 | |||
| 125 | for (var key in source) { | ||
| 126 | if (source.hasOwnProperty(key)) { | ||
| 127 | var value = source[key]; | ||
| 128 | |||
| 129 | if (isBool(value)) | ||
| 130 | value = value.toString().toLowerCase(); | ||
| 131 | else | ||
| 132 | value = value ? value.toString() : ""; | ||
| 133 | |||
| 134 | if (value.indexOf(parameterDelimiter) >= 0 || value.indexOf(keyValueDelimiter) >= 0) | ||
| 135 | value = startValueDelimiter + value + endValueDelimiter; | ||
| 136 | |||
| 137 | values.push(key + keyValueDelimiter + value); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | return values.join(parameterDelimiter + " "); | ||
| 142 | }; | ||
| 143 | |||
| 144 | Array.prototype.joinKeyValuePairs = function (parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter) { | ||
| 145 | return joinKeyValuePairs(this, parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter); | ||
| 146 | }; | ||
| 147 | |||
| 148 | // Represents a dictionary style class with case-insensitive keys | ||
| 149 | function Dictionary(source) { | ||
| 150 | const self = this; | ||
| 151 | |||
| 152 | if (!source) | ||
| 153 | source = []; | ||
| 154 | |||
| 155 | self._keys = []; | ||
| 156 | self._values = []; | ||
| 157 | |||
| 158 | self.count = function () { | ||
| 159 | var size = 0; | ||
| 160 | |||
| 161 | for (var property in self._values) { | ||
| 162 | if (self._values.hasOwnProperty(property)) | ||
| 163 | size++; | ||
| 164 | } | ||
| 165 | |||
| 166 | return size; | ||
| 167 | } | ||
| 168 | |||
| 169 | self.keys = function () { | ||
| 170 | const keys = []; | ||
| 171 | |||
| 172 | for (var property in self._keys) { | ||
| 173 | if (self._keys.hasOwnProperty(property)) | ||
| 174 | keys.push(self._keys[property]); | ||
| 175 | } | ||
| 176 | |||
| 177 | return keys; | ||
| 178 | } | ||
| 179 | |||
| 180 | self.values = function () { | ||
| 181 | const values = []; | ||
| 182 | |||
| 183 | for (var property in self._values) { | ||
| 184 | if (self._keys.hasOwnProperty(property)) | ||
| 185 | values.push(self._values[property]); | ||
| 186 | } | ||
| 187 | |||
| 188 | return values; | ||
| 189 | } | ||
| 190 | |||
| 191 | self.get = function (key) { | ||
| 192 | return self._values[String(key).toLowerCase()]; | ||
| 193 | } | ||
| 194 | |||
| 195 | self.set = function (key, value) { | ||
| 196 | const lkey = String(key).toLowerCase(); | ||
| 197 | |||
| 198 | if (!self._keys[lkey] || self._keys[lkey].toLowerCase() !== key) | ||
| 199 | self._keys[lkey] = key; | ||
| 200 | |||
| 201 | if (isBool(value)) | ||
| 202 | self._values[lkey] = getBool(value); | ||
| 203 | else | ||
| 204 | self._values[lkey] = value; | ||
| 205 | } | ||
| 206 | |||
| 207 | self.remove = function (key) { | ||
| 208 | const lkey = String(key).toLowerCase(); | ||
| 209 | delete self._keys[lkey]; | ||
| 210 | delete self._values[lkey]; | ||
| 211 | } | ||
| 212 | |||
| 213 | self.containsKey = function (key) { | ||
| 214 | const lkey = String(key).toLowerCase(); | ||
| 215 | |||
| 216 | for (var property in self._values) { | ||
| 217 | if (self._values.hasOwnProperty(property) && property === lkey) | ||
| 218 | return true; | ||
| 219 | } | ||
| 220 | |||
| 221 | return false; | ||
| 222 | } | ||
| 223 | |||
| 224 | self.containsValue = function (value) { | ||
| 225 | for (var property in self._values) { | ||
| 226 | if (self._values.hasOwnProperty(property) && self._values[property] === value) | ||
| 227 | return true; | ||
| 228 | } | ||
| 229 | |||
| 230 | return false; | ||
| 231 | } | ||
| 232 | |||
| 233 | self.clear = function () { | ||
| 234 | self._keys = []; | ||
| 235 | self._values = []; | ||
| 236 | } | ||
| 237 | |||
| 238 | self.joinKeyValuePairs = function (parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter) { | ||
| 239 | const keyValuePairs = []; | ||
| 240 | |||
| 241 | for (var property in self._values) { | ||
| 242 | if (self._values.hasOwnProperty(property)) | ||
| 243 | keyValuePairs[self._keys[property]] = self._values[property]; | ||
| 244 | } | ||
| 245 | |||
| 246 | return keyValuePairs.joinKeyValuePairs(parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter); | ||
| 247 | } | ||
| 248 | |||
| 249 | self.pushAll = function (source) { | ||
| 250 | for (var property in source) | ||
| 251 | if (source.hasOwnProperty(property)) | ||
| 252 | self.set(property, source[property]); | ||
| 253 | } | ||
| 254 | |||
| 255 | self.toObservableDictionary = function (useLowerKeys) { | ||
| 256 | // See ko.observableDictionary.js | ||
| 257 | const observableDictionary = new ko.observableDictionary(); | ||
| 258 | |||
| 259 | for (var property in self._values) { | ||
| 260 | if (self._values.hasOwnProperty(property)) | ||
| 261 | observableDictionary.push(useLowerKeys ? property : self._keys[property], self._values[property]); | ||
| 262 | } | ||
| 263 | |||
| 264 | return observableDictionary; | ||
| 265 | } | ||
| 266 | |||
| 267 | self.updateObservableDictionary = function (observableDictionary, useLowerKeys) { | ||
| 268 | for (var property in self._values) { | ||
| 269 | if (self._values.hasOwnProperty(property)) | ||
| 270 | observableDictionary.set(useLowerKeys ? property : self._keys[property], self._values[property]); | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 274 | // Construction | ||
| 275 | if (source instanceof Dictionary) { | ||
| 276 | for (var property in source._values) | ||
| 277 | if (source._values.hasOwnProperty(property)) | ||
| 278 | self.set(source._keys[property], source._values[property]); | ||
| 279 | } | ||
| 280 | else { | ||
| 281 | for (var property in source) { | ||
| 282 | if (source.hasOwnProperty(property)) | ||
| 283 | self.set(property, source[property]); | ||
| 284 | } | ||
| 285 | } | ||
| 286 | } | ||
| 287 | |||
| 288 | Dictionary.fromObservableDictionary = function (observableDictionary) { | ||
| 289 | var dictionary = new Dictionary(); | ||
| 290 | dictionary.pushAll(observableDictionary.toJSON()); | ||
| 291 | return dictionary; | ||
| 292 | } | ||
| 293 | |||
| 294 | // String functions | ||
| 295 | function isEmpty(str) { | ||
| 296 | return !str || String(str).length === 0; | ||
| 297 | } | ||
| 298 | |||
| 299 | String.prototype.truncate = function (limit) { | ||
| 300 | const text = this.trim(); | ||
| 301 | |||
| 302 | if (text.length > limit) | ||
| 303 | return text.substr(0, limit - 3) + "..."; | ||
| 304 | |||
| 305 | return text; | ||
| 306 | } | ||
| 307 | |||
| 308 | String.prototype.replaceAll = function (findText, replaceWith, ignoreCase) { | ||
| 309 | return this.replace( | ||
| 310 | new RegExp(findText.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g, "\\$&"), (ignoreCase ? "gi" : "g")), | ||
| 311 | (typeof replaceWith == "string") ? replaceWith.replace(/\$/g, "$$$$") : replaceWith); | ||
| 312 | } | ||
| 313 | |||
| 314 | String.prototype.padLeft = function (totalWidth, paddingChar) { | ||
| 315 | if (totalWidth > this.length) | ||
| 316 | return Array(totalWidth - this.length + 1).join(paddingChar || " ") + this; | ||
| 317 | |||
| 318 | return this; | ||
| 319 | } | ||
| 320 | |||
| 321 | String.prototype.padRight = function (totalWidth, paddingChar) { | ||
| 322 | if (totalWidth > this.length) | ||
| 323 | return this + Array(totalWidth - this.length + 1).join(paddingChar || " "); | ||
| 324 | |||
| 325 | return this; | ||
| 326 | } | ||
| 327 | |||
| 328 | String.prototype.countOccurrences = function (searchString) { | ||
| 329 | return (this.split(searchString).length - 1); | ||
| 330 | } | ||
| 331 | |||
| 332 | if (!String.prototype.startsWith) { | ||
| 333 | String.prototype.startsWith = function (searchString, position) { | ||
| 334 | position = position || 0; | ||
| 335 | return this.substr(position, searchString.length) === searchString; | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | String.prototype.regexEncode = function () { | ||
| 340 | return "\\u" + this.charCodeAt(0).toString(16).padLeft(4, "0"); | ||
| 341 | } | ||
| 342 | |||
| 343 | // Returns a Dictionary of the parsed key/value pair expressions from a string. Parameter pairs are delimited by keyValueDelimiter | ||
| 344 | // and multiple pairs separated by parameterDelimiter. Supports encapsulated nested expressions. | ||
| 345 | String.prototype.parseKeyValuePairs = function (parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter, ignoreDuplicateKeys) { | ||
| 346 | if (!parameterDelimiter) | ||
| 347 | parameterDelimiter = ";"; | ||
| 348 | |||
| 349 | if (!keyValueDelimiter) | ||
| 350 | keyValueDelimiter = "="; | ||
| 351 | |||
| 352 | if (!startValueDelimiter) | ||
| 353 | startValueDelimiter = "{"; | ||
| 354 | |||
| 355 | if (!endValueDelimiter) | ||
| 356 | endValueDelimiter = "}"; | ||
| 357 | |||
| 358 | if (ignoreDuplicateKeys === undefined) | ||
| 359 | ignoreDuplicateKeys = true; | ||
| 360 | |||
| 361 | if (parameterDelimiter === keyValueDelimiter || | ||
| 362 | parameterDelimiter === startValueDelimiter || | ||
| 363 | parameterDelimiter === endValueDelimiter || | ||
| 364 | keyValueDelimiter === startValueDelimiter || | ||
| 365 | keyValueDelimiter === endValueDelimiter || | ||
| 366 | startValueDelimiter === endValueDelimiter) | ||
| 367 | throw "All delimiters must be unique"; | ||
| 368 | |||
| 369 | const escapedParameterDelimiter = parameterDelimiter.regexEncode(); | ||
| 370 | const escapedKeyValueDelimiter = keyValueDelimiter.regexEncode(); | ||
| 371 | const escapedStartValueDelimiter = startValueDelimiter.regexEncode(); | ||
| 372 | const escapedEndValueDelimiter = endValueDelimiter.regexEncode(); | ||
| 373 | const backslashDelimiter = "\\".regexEncode(); | ||
| 374 | |||
| 375 | var keyValuePairs = new Dictionary(); | ||
| 376 | var escapedValue = []; | ||
| 377 | var valueEscaped = false; | ||
| 378 | var delimiterDepth = 0; | ||
| 379 | |||
| 380 | // Escape any parameter or key/value delimiters within tagged value sequences | ||
| 381 | // For example, the following string: | ||
| 382 | // "normalKVP=-1; nestedKVP={p1=true; p2=false}") | ||
| 383 | // would be encoded as: | ||
| 384 | // "normalKVP=-1; nestedKVP=p1\\u003dtrue\\u003b p2\\u003dfalse") | ||
| 385 | for (var i = 0; i < this.length; i++) { | ||
| 386 | var character = this[i]; | ||
| 387 | |||
| 388 | if (character === startValueDelimiter) { | ||
| 389 | if (!valueEscaped) { | ||
| 390 | valueEscaped = true; | ||
| 391 | continue; // Don't add tag start delimiter to final value | ||
| 392 | } | ||
| 393 | |||
| 394 | // Handle nested delimiters | ||
| 395 | delimiterDepth++; | ||
| 396 | } | ||
| 397 | |||
| 398 | if (character === endValueDelimiter) { | ||
| 399 | if (valueEscaped) { | ||
| 400 | if (delimiterDepth > 0) { | ||
| 401 | // Handle nested delimiters | ||
| 402 | delimiterDepth--; | ||
| 403 | } | ||
| 404 | else { | ||
| 405 | valueEscaped = false; | ||
| 406 | continue; // Don't add tag stop delimiter to final value | ||
| 407 | } | ||
| 408 | } | ||
| 409 | else { | ||
| 410 | throw "Failed to parse key/value pairs: invalid delimiter mismatch. Encountered end value delimiter \"" + endValueDelimiter + "\" before start value delimiter \"" + startValueDelimiter + "\"."; | ||
| 411 | } | ||
| 412 | } | ||
| 413 | |||
| 414 | if (valueEscaped) { | ||
| 415 | // Escape any delimiter characters inside nested key/value pair | ||
| 416 | if (character === parameterDelimiter) | ||
| 417 | escapedValue.push(escapedParameterDelimiter); | ||
| 418 | else if (character === keyValueDelimiter) | ||
| 419 | escapedValue.push(escapedKeyValueDelimiter); | ||
| 420 | else if (character === startValueDelimiter) | ||
| 421 | escapedValue.push(escapedStartValueDelimiter); | ||
| 422 | else if (character === endValueDelimiter) | ||
| 423 | escapedValue.push(escapedEndValueDelimiter); | ||
| 424 | else if (character === "\\") | ||
| 425 | escapedValue.push(backslashDelimiter); | ||
| 426 | else | ||
| 427 | escapedValue.push(character); | ||
| 428 | } | ||
| 429 | else { | ||
| 430 | if (character === "\\") | ||
| 431 | escapedValue.push(backslashDelimiter); | ||
| 432 | else | ||
| 433 | escapedValue.push(character); | ||
| 434 | } | ||
| 435 | } | ||
| 436 | |||
| 437 | if (delimiterDepth !== 0 || valueEscaped) { | ||
| 438 | // If value is still escaped, tagged expression was not terminated | ||
| 439 | if (valueEscaped) | ||
| 440 | delimiterDepth = 1; | ||
| 441 | |||
| 442 | throw "Failed to parse key/value pairs: invalid delimiter mismatch. Encountered more " + | ||
| 443 | (delimiterDepth > 0 ? "start value delimiters \"" + startValueDelimiter + "\"" : "end value delimiters \"" + endValueDelimiter + "\"") + " than " + | ||
| 444 | (delimiterDepth < 0 ? "start value delimiters \"" + startValueDelimiter + "\"" : "end value delimiters \"" + endValueDelimiter + "\"") + "."; | ||
| 445 | } | ||
| 446 | |||
| 447 | // Parse key/value pairs from escaped value | ||
| 448 | var pairs = escapedValue.join("").split(parameterDelimiter); | ||
| 449 | |||
| 450 | for (var i = 0; i < pairs.length; i++) { | ||
| 451 | // Separate key from value | ||
| 452 | var elements = pairs[i].split(keyValueDelimiter); | ||
| 453 | |||
| 454 | if (elements.length === 2) { | ||
| 455 | // Get key | ||
| 456 | var key = elements[0].trim(); | ||
| 457 | |||
| 458 | // Get unescaped value | ||
| 459 | var unescapedValue = elements[1].trim(). | ||
| 460 | replaceAll(escapedParameterDelimiter, parameterDelimiter). | ||
| 461 | replaceAll(escapedKeyValueDelimiter, keyValueDelimiter). | ||
| 462 | replaceAll(escapedStartValueDelimiter, startValueDelimiter). | ||
| 463 | replaceAll(escapedEndValueDelimiter, endValueDelimiter). | ||
| 464 | replaceAll(backslashDelimiter, "\\"); | ||
| 465 | |||
| 466 | // Add key/value pair to dictionary | ||
| 467 | if (ignoreDuplicateKeys) { | ||
| 468 | // Add or replace key elements with unescaped value | ||
| 469 | keyValuePairs.set(key, unescapedValue); | ||
| 470 | } | ||
| 471 | else { | ||
| 472 | // Add key elements with unescaped value throwing an exception for encountered duplicate keys | ||
| 473 | if (keyValuePairs.containsKey(key)) | ||
| 474 | throw "Failed to parse key/value pairs: duplicate key encountered. Key \"" + key + "\" is not unique within the string: \"" + this + "\""; | ||
| 475 | |||
| 476 | keyValuePairs.set(key, unescapedValue); | ||
| 477 | } | ||
| 478 | } | ||
| 479 | } | ||
| 480 | |||
| 481 | return keyValuePairs; | ||
| 482 | } | ||
| 483 | |||
| 484 | // Date Functions | ||
| 485 | Date.prototype.addDays = function (days) { | ||
| 486 | return new Date(this.setDate(this.getDate() + days)); | ||
| 487 | } | ||
| 488 | |||
| 489 | Date.prototype.toUTC = function () { | ||
| 490 | this.setMinutes(this.getMinutes() - this.getTimezoneOffset()); | ||
| 491 | return this; | ||
| 492 | } | ||
| 493 | |||
| 494 | Date.prototype.daysBetween = function (startDate) { | ||
| 495 | const millisecondsPerDay = 24 * 60 * 60 * 1000; | ||
| 496 | return (this.toUTC() - startDate.toUTC()) / millisecondsPerDay; | ||
| 497 | } | ||
| 498 | |||
| 499 | String.prototype.toDate = function () { | ||
| 500 | return new Date(Date.parse(this)); | ||
| 501 | }; | ||
| 502 | |||
| 503 | String.prototype.formatDate = function (format, utc) { | ||
| 504 | return formatDate(this.toDate(), format, utc); | ||
| 505 | }; | ||
| 506 | |||
| 507 | Date.prototype.formatDate = function (format, utc) { | ||
| 508 | return formatDate(this, format, utc); | ||
| 509 | }; | ||
| 510 | |||
| 511 | function formatDate(date, format, utc) { | ||
| 512 | if (typeof date === "string") | ||
| 513 | return formatDate(date.toDate(), format, utc); | ||
| 514 | |||
| 515 | if (format === undefined && DateTimeFormat != undefined) | ||
| 516 | format = DateTimeFormat; | ||
| 517 | |||
| 518 | if (utc === undefined) | ||
| 519 | utc = true; | ||
| 520 | |||
| 521 | var MMMM = ["\x00", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; | ||
| 522 | var MMM = ["\x01", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; | ||
| 523 | var dddd = ["\x02", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; | ||
| 524 | var ddd = ["\x03", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; | ||
| 525 | |||
| 526 | function ii(i, len) { | ||
| 527 | var ss = i + ""; | ||
| 528 | len = len || 2; | ||
| 529 | while (ss.length < len) ss = "0" + ss; | ||
| 530 | return ss; | ||
| 531 | } | ||
| 532 | |||
| 533 | var y = utc ? date.getUTCFullYear() : date.getFullYear(); | ||
| 534 | format = format.replace(/(^|[^\\])yyyy+/g, "$1" + y); | ||
| 535 | format = format.replace(/(^|[^\\])yy/g, "$1" + y.toString().substr(2, 2)); | ||
| 536 | format = format.replace(/(^|[^\\])y/g, "$1" + y); | ||
| 537 | |||
| 538 | var M = (utc ? date.getUTCMonth() : date.getMonth()) + 1; | ||
| 539 | format = format.replace(/(^|[^\\])MMMM+/g, "$1" + MMMM[0]); | ||
| 540 | format = format.replace(/(^|[^\\])MMM/g, "$1" + MMM[0]); | ||
| 541 | format = format.replace(/(^|[^\\])MM/g, "$1" + ii(M)); | ||
| 542 | format = format.replace(/(^|[^\\])M/g, "$1" + M); | ||
| 543 | |||
| 544 | var d = utc ? date.getUTCDate() : date.getDate(); | ||
| 545 | format = format.replace(/(^|[^\\])dddd+/g, "$1" + dddd[0]); | ||
| 546 | format = format.replace(/(^|[^\\])ddd/g, "$1" + ddd[0]); | ||
| 547 | format = format.replace(/(^|[^\\])dd/g, "$1" + ii(d)); | ||
| 548 | format = format.replace(/(^|[^\\])d/g, "$1" + d); | ||
| 549 | |||
| 550 | var H = utc ? date.getUTCHours() : date.getHours(); | ||
| 551 | format = format.replace(/(^|[^\\])HH+/g, "$1" + ii(H)); | ||
| 552 | format = format.replace(/(^|[^\\])H/g, "$1" + H); | ||
| 553 | |||
| 554 | var h = H > 12 ? H - 12 : H === 0 ? 12 : H; | ||
| 555 | format = format.replace(/(^|[^\\])hh+/g, "$1" + ii(h)); | ||
| 556 | format = format.replace(/(^|[^\\])h/g, "$1" + h); | ||
| 557 | |||
| 558 | var m = utc ? date.getUTCMinutes() : date.getMinutes(); | ||
| 559 | format = format.replace(/(^|[^\\])mm+/g, "$1" + ii(m)); | ||
| 560 | format = format.replace(/(^|[^\\])m/g, "$1" + m); | ||
| 561 | |||
| 562 | var s = utc ? date.getUTCSeconds() : date.getSeconds(); | ||
| 563 | format = format.replace(/(^|[^\\])ss+/g, "$1" + ii(s)); | ||
| 564 | format = format.replace(/(^|[^\\])s/g, "$1" + s); | ||
| 565 | |||
| 566 | var f = utc ? date.getUTCMilliseconds() : date.getMilliseconds(); | ||
| 567 | format = format.replace(/(^|[^\\])fff+/g, "$1" + ii(f, 3)); | ||
| 568 | f = Math.round(f / 10); | ||
| 569 | format = format.replace(/(^|[^\\])ff/g, "$1" + ii(f)); | ||
| 570 | f = Math.round(f / 10); | ||
| 571 | format = format.replace(/(^|[^\\])f/g, "$1" + f); | ||
| 572 | |||
| 573 | var T = H < 12 ? "AM" : "PM"; | ||
| 574 | format = format.replace(/(^|[^\\])TT+/g, "$1" + T); | ||
| 575 | format = format.replace(/(^|[^\\])T/g, "$1" + T.charAt(0)); | ||
| 576 | |||
| 577 | var t = T.toLowerCase(); | ||
| 578 | format = format.replace(/(^|[^\\])tt+/g, "$1" + t); | ||
| 579 | format = format.replace(/(^|[^\\])t/g, "$1" + t.charAt(0)); | ||
| 580 | |||
| 581 | var tz = -date.getTimezoneOffset(); | ||
| 582 | var K = utc || !tz ? "Z" : tz > 0 ? "+" : "-"; | ||
| 583 | if (!utc) { | ||
| 584 | tz = Math.abs(tz); | ||
| 585 | var tzHrs = Math.floor(tz / 60); | ||
| 586 | var tzMin = tz % 60; | ||
| 587 | K += ii(tzHrs) + ":" + ii(tzMin); | ||
| 588 | } | ||
| 589 | |||
| 590 | format = format.replace(/(^|[^\\])K/g, "$1" + K); | ||
| 591 | var day = (utc ? date.getUTCDay() : date.getDay()) + 1; | ||
| 592 | |||
| 593 | format = format.replace(new RegExp(dddd[0], "g"), dddd[day]); | ||
| 594 | format = format.replace(new RegExp(ddd[0], "g"), ddd[day]); | ||
| 595 | |||
| 596 | format = format.replace(new RegExp(MMMM[0], "g"), MMMM[M]); | ||
| 597 | format = format.replace(new RegExp(MMM[0], "g"), MMM[M]); | ||
| 598 | |||
| 599 | format = format.replace(/\\(.)/g, "$1"); | ||
| 600 | |||
| 601 | return format; | ||
| 602 | }; | ||
| 603 | |||
| 604 | // jQuery extensions | ||
| 605 | $.fn.paddingHeight = function () { | ||
| 606 | return this.outerHeight(true) - this.height(); | ||
| 607 | } | ||
| 608 | |||
| 609 | $.fn.paddingWidth = function () { | ||
| 610 | return this.outerWidth(true) - this.width(); | ||
| 611 | } | ||
| 612 | |||
| 613 | // Cell truncations should only be used with .table-cell-hard-wrap style | ||
| 614 | $.fn.truncateToWidth = function (text, rows) { | ||
| 615 | if (isEmpty(text)) | ||
| 616 | return ""; | ||
| 617 | |||
| 618 | if (!rows) | ||
| 619 | rows = 1; | ||
| 620 | |||
| 621 | textMetrics.font = this.css("font"); | ||
| 622 | |||
| 623 | var targetWidth = this.innerWidth(); | ||
| 624 | var textWidth = textMetrics.measureText(text).width; | ||
| 625 | |||
| 626 | if (rows > 1) | ||
| 627 | targetWidth *= ((isIE ? 0.45 : 0.75) * rows); | ||
| 628 | |||
| 629 | var limit = Math.min(text.length, Math.ceil(targetWidth / (textWidth / text.length))); | ||
| 630 | |||
| 631 | while (textWidth > targetWidth && limit > 1) { | ||
| 632 | limit--; | ||
| 633 | text = text.truncate(limit); | ||
| 634 | textWidth = textMetrics.measureText(text).width; | ||
| 635 | } | ||
| 636 | |||
| 637 | return text; | ||
| 638 | } | ||
| 639 | |||
| 640 | $.fn.visible = function () { | ||
| 641 | return this.css("visibility", "visible"); | ||
| 642 | } | ||
| 643 | |||
| 644 | $.fn.invisible = function () { | ||
| 645 | return this.css("visibility", "hidden"); | ||
| 646 | } | ||
| 647 | |||
| 648 | // The following target arrays of promises | ||
| 649 | $.fn.whenAny = function () { | ||
| 650 | var finish = $.Deferred(); | ||
| 651 | |||
| 652 | if (this.length === 0) | ||
| 653 | finish.resolve(); | ||
| 654 | else | ||
| 655 | $.each(this, function (index, deferred) { | ||
| 656 | deferred.done(finish.resolve); | ||
| 657 | }); | ||
| 658 | |||
| 659 | return finish.promise(); | ||
| 660 | } | ||
| 661 | |||
| 662 | $.fn.whenAll = function () { | ||
| 663 | if (this.length > 0) | ||
| 664 | return $.when.apply($, this); | ||
| 665 | |||
| 666 | return $.Deferred().resolve().promise(); | ||
| 667 | } | ||
| @@ -0,0 +1,568 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // pagedViewModel.js - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 01/23/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | // Paged view model base class scripts | ||
| 25 | "use strict"; | ||
| 26 | |||
| 27 | var RecordMode = { | ||
| 28 | View: 0, | ||
| 29 | Edit: 1, | ||
| 30 | AddNew: 2 | ||
| 31 | } | ||
| 32 | |||
| 33 | // Define paged view model class | ||
| 34 | function PagedViewModel() { | ||
| 35 | const self = this; | ||
| 36 | |||
| 37 | // Configuration fields | ||
| 38 | self.labelField = "{name}"; // Field name that best represents row for delete confirmation | ||
| 39 | self.primaryKeyFields = ["{id}"]; // Primary key field names array | ||
| 40 | self.defaultSortField = "{id}"; // Default sort field | ||
| 41 | self.defaultSortAscending = true; // Default sort ascending flag | ||
| 42 | self.initialFocusField = ""; // Initial add/edit field with focus | ||
| 43 | self.modelName = "{name}"; // Name of model used for cookie names, defaults to page title | ||
| 44 | |||
| 45 | // Observable fields | ||
| 46 | self.pageRecords = ko.observableArray(); // Records queried for current page | ||
| 47 | self.recordCount = ko.observable(0); // Queried record count | ||
| 48 | self.sortField = ko.observable(self.defaultSortField); // Current sort field, persisted per model | ||
| 49 | self.sortAscending = ko.observable(self.defaultSortAscending); // Current sort ascending flag, persisted per model | ||
| 50 | self.unassignedFieldCount = ko.observable(0); // Number of bound fields with missing data | ||
| 51 | self.dataHubIsConnected = ko.observable(false); // Data hub connected observable flag - externally managed | ||
| 52 | self.errors = ko.validation.group([ko.observable(0)]); // Validation errors array | ||
| 53 | self.canEdit = ko.observable(true); // Can edit flag - normally controlled by external roles | ||
| 54 | self.canAddNew = ko.observable(true); // Can add new flag - normally controlled by external roles | ||
| 55 | self.canDelete = ko.observable(true); // Can delete flag - normally controlled by external roles | ||
| 56 | |||
| 57 | // Internal fields | ||
| 58 | self._currentPageSize = ko.observable(1); | ||
| 59 | self._currentPage = ko.observable(0); | ||
| 60 | self._currentRecord = ko.observable(); | ||
| 61 | self._recordMode = ko.observable(RecordMode.View); | ||
| 62 | self._isDirty = ko.observable(false); | ||
| 63 | self._columnWidths = []; | ||
| 64 | |||
| 65 | // Properties | ||
| 66 | |||
| 67 | // Gets or sets the number records to display on the page | ||
| 68 | self.currentPageSize = ko.pureComputed({ | ||
| 69 | read: self._currentPageSize, | ||
| 70 | write: function (value) { | ||
| 71 | if (value < 1) | ||
| 72 | value = 1; | ||
| 73 | |||
| 74 | if (value !== self._currentPageSize()) { | ||
| 75 | self._currentPageSize(value); | ||
| 76 | |||
| 77 | // Validate current page after page size change | ||
| 78 | if (self.currentPage()) | ||
| 79 | self.currentPage(self.currentPage()); | ||
| 80 | } | ||
| 81 | }, | ||
| 82 | owner: self | ||
| 83 | }); | ||
| 84 | |||
| 85 | // Gets or sets the current page number | ||
| 86 | self.currentPage = ko.pureComputed({ | ||
| 87 | read: self._currentPage, | ||
| 88 | write: function (value) { | ||
| 89 | if (value < 1) | ||
| 90 | value = 1; | ||
| 91 | else if (value > self.totalPages()) | ||
| 92 | value = self.totalPages(); | ||
| 93 | |||
| 94 | // Requery records for page when current page changes | ||
| 95 | if (value !== self._currentPage()) { | ||
| 96 | self._currentPage(value); | ||
| 97 | self.queryPageRecords(); | ||
| 98 | } | ||
| 99 | }, | ||
| 100 | owner: self | ||
| 101 | }); | ||
| 102 | |||
| 103 | // Gets or sets current record on page | ||
| 104 | self.currentRecord = ko.pureComputed({ | ||
| 105 | read: self._currentRecord, | ||
| 106 | write: function(value) { | ||
| 107 | self._currentRecord(value); | ||
| 108 | self.isDirty(false); | ||
| 109 | $(self).trigger("currentRecordChanged"); | ||
| 110 | self.applyValidationParameters(); | ||
| 111 | |||
| 112 | // Watch for changes to fields in current record | ||
| 113 | ko.watch(self._currentRecord(), function (parents, child, item) { | ||
| 114 | self.isDirty(true); | ||
| 115 | $(self).trigger("currentRecordUpdated", [child]); | ||
| 116 | |||
| 117 | // Recalculate unassigned field count after bindings may have been updated by field change | ||
| 118 | setTimeout(self.calculateUnassignedFieldCount, 10); | ||
| 119 | }); | ||
| 120 | }, | ||
| 121 | owner: self | ||
| 122 | }); | ||
| 123 | |||
| 124 | // Gets or sets the record mode, i.e., view, edit or add new | ||
| 125 | self.recordMode = ko.pureComputed({ | ||
| 126 | read: self._recordMode, | ||
| 127 | write: function (newMode) { | ||
| 128 | const oldMode = self._recordMode(); | ||
| 129 | |||
| 130 | if (newMode !== oldMode) { | ||
| 131 | self._recordMode(newMode); | ||
| 132 | |||
| 133 | if (oldMode === RecordMode.View && newMode === RecordMode.Edit) | ||
| 134 | self.setFocusOnInitialField(); | ||
| 135 | |||
| 136 | $(self).trigger("recordModeChanged", [oldMode, newMode]); | ||
| 137 | } | ||
| 138 | }, | ||
| 139 | owner: self | ||
| 140 | }); | ||
| 141 | |||
| 142 | // Gets or sets current record changed flag | ||
| 143 | self.isDirty = ko.pureComputed({ | ||
| 144 | read: self._isDirty, | ||
| 145 | write: function (value) { | ||
| 146 | if (value === undefined) | ||
| 147 | value = true; | ||
| 148 | |||
| 149 | self._isDirty(value); | ||
| 150 | self.calculateUnassignedFieldCount(); | ||
| 151 | }, | ||
| 152 | owner: self | ||
| 153 | }); | ||
| 154 | |||
| 155 | // Gets total number pages based on total record count and current page size | ||
| 156 | self.totalPages = ko.pureComputed(function () { | ||
| 157 | return Math.max(Math.ceil(self.recordCount() / self.currentPageSize()), 1); | ||
| 158 | }); | ||
| 159 | |||
| 160 | // Gets flag that determines if current page is first page | ||
| 161 | self.onFirstPage = ko.pureComputed(function () { | ||
| 162 | return self.currentPage() <= 1; | ||
| 163 | }); | ||
| 164 | |||
| 165 | // Gets flag that determines if current page is last page | ||
| 166 | self.onLastPage = ko.pureComputed(function () { | ||
| 167 | return self.currentPage() >= self.totalPages(); | ||
| 168 | }); | ||
| 169 | |||
| 170 | // Return validation errors, i.e., non-required field errors | ||
| 171 | self.validationErrors = ko.pureComputed(function() { | ||
| 172 | return self.errors().length - self.unassignedFieldCount(); | ||
| 173 | }).extend({ notify: "always" }); | ||
| 174 | |||
| 175 | // Delegates | ||
| 176 | self.queryRecordCount = function () { }; | ||
| 177 | self.queryRecords = function (/* sortField, ascending, page, pageSize */) { }; | ||
| 178 | self.deleteRecord = function (/* keyValues[] */) { }; | ||
| 179 | self.newRecord = function () { }; | ||
| 180 | self.addNewRecord = function (/* record */) { }; | ||
| 181 | self.updateRecord = function (/* record */) { }; | ||
| 182 | self.applyValidationParameters = function () { }; | ||
| 183 | |||
| 184 | // Setters needed to assign delegate properties, 'cause Javascript | ||
| 185 | self.setQueryRecordCount = function (queryRecordCountFunction) { | ||
| 186 | self.queryRecordCount = queryRecordCountFunction; | ||
| 187 | } | ||
| 188 | |||
| 189 | self.setQueryRecords = function (queryRecordsFunction) { | ||
| 190 | self.queryRecords = queryRecordsFunction; | ||
| 191 | } | ||
| 192 | |||
| 193 | self.setDeleteRecord = function (deleteRecordFunction) { | ||
| 194 | self.deleteRecord = deleteRecordFunction; | ||
| 195 | } | ||
| 196 | |||
| 197 | self.setNewRecord = function (newRecordFunction) { | ||
| 198 | self.newRecord = newRecordFunction; | ||
| 199 | } | ||
| 200 | |||
| 201 | self.setAddNewRecord = function (addNewRecordFunction) { | ||
| 202 | self.addNewRecord = addNewRecordFunction; | ||
| 203 | } | ||
| 204 | |||
| 205 | self.setUpdateRecord = function (updateRecordFunction) { | ||
| 206 | self.updateRecord = updateRecordFunction; | ||
| 207 | } | ||
| 208 | |||
| 209 | self.setApplyValidationParameters = function (applyValidationParametersFunction) { | ||
| 210 | self.applyValidationParameters = applyValidationParametersFunction; | ||
| 211 | } | ||
| 212 | |||
| 213 | // Methods | ||
| 214 | self.initialize = function () { | ||
| 215 | // Restore any previous sort order | ||
| 216 | const lastSortField = Cookies.get(self.modelName + "!LastSortField"); | ||
| 217 | const lastSortAscending = Cookies.get(self.modelName + "!LastSortAscending"); | ||
| 218 | |||
| 219 | if (lastSortField === undefined) | ||
| 220 | self.sortField(self.defaultSortField); | ||
| 221 | else | ||
| 222 | self.sortField(lastSortField); | ||
| 223 | |||
| 224 | if (lastSortAscending === undefined) | ||
| 225 | self.sortAscending(self.defaultSortAscending); | ||
| 226 | else | ||
| 227 | self.sortAscending(lastSortAscending === "true"); | ||
| 228 | |||
| 229 | if (self.dataHubIsConnected()) { | ||
| 230 | // Query total record count | ||
| 231 | self.queryRecordCount().done(function (count) { | ||
| 232 | // Update record count observable | ||
| 233 | self.recordCount(count); | ||
| 234 | |||
| 235 | // Force page refresh when record count has been updated | ||
| 236 | const currentPage = self.currentPage(); | ||
| 237 | self._currentPage(0); | ||
| 238 | self.currentPage(currentPage); | ||
| 239 | }); | ||
| 240 | |||
| 241 | // Initialize current record with an empty row | ||
| 242 | self.newRecord().done(function (emptyRecord) { | ||
| 243 | self.deriveObservableRecord(emptyRecord).done(function (observableRecord) { | ||
| 244 | self.currentRecord(observableRecord); | ||
| 245 | }); | ||
| 246 | }); | ||
| 247 | } | ||
| 248 | |||
| 249 | // Initialize column widths array | ||
| 250 | self._columnWidths = []; | ||
| 251 | const columns = $("#recordRow").find("td"); | ||
| 252 | |||
| 253 | for (let i = 0; i < columns.length; i++) { | ||
| 254 | self._columnWidths.push($(columns[i]).width()); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | self.calculatePageSize = function (forceRefresh) { | ||
| 259 | // Calculate total number of table rows that will fit within current page height | ||
| 260 | const remainingHeight = calculateRemainingBodyHeight() - | ||
| 261 | $("#contentWell").paddingHeight() - | ||
| 262 | $("#responsiveTableDiv").paddingHeight() - | ||
| 263 | $("#recordsTable").paddingHeight() - | ||
| 264 | $("#pageControlsRow").outerHeight(true); | ||
| 265 | |||
| 266 | // Estimate page size based on height of first record row | ||
| 267 | const firstRow = $("#recordRow"); | ||
| 268 | var pageSize = (remainingHeight / firstRow.outerHeight(true)).truncate(); | ||
| 269 | |||
| 270 | if (!pageSize || isNaN(pageSize) || !isFinite(pageSize) || pageSize < 1) | ||
| 271 | pageSize = 1; | ||
| 272 | |||
| 273 | if (forceRefresh === undefined) | ||
| 274 | forceRefresh = false; | ||
| 275 | |||
| 276 | // Check for dynamic Bootstrap column resizing, in which case we need to refresh data | ||
| 277 | // for cases where binding may be truncating data lengths, see $.fn.truncateToWidth | ||
| 278 | const columns = firstRow.find("td"); | ||
| 279 | var columnWidth; | ||
| 280 | |||
| 281 | for (let i = 0; i < columns.length; i++) { | ||
| 282 | columnWidth = $(columns[i]).width(); | ||
| 283 | |||
| 284 | if (self._columnWidths[i] !== columnWidth) { | ||
| 285 | self._columnWidths[i] = columnWidth; | ||
| 286 | forceRefresh = true; | ||
| 287 | } | ||
| 288 | } | ||
| 289 | |||
| 290 | if (pageSize !== self.currentPageSize() || forceRefresh) { | ||
| 291 | const currentPage = self.currentPage(); | ||
| 292 | |||
| 293 | // Updating page size will validate current page number | ||
| 294 | self.currentPageSize(pageSize); | ||
| 295 | |||
| 296 | // Requery data for page unless current page was dynamically changed | ||
| 297 | // by page size update and has reloaded data already | ||
| 298 | if (currentPage === self.currentPage()) | ||
| 299 | self.queryPageRecords(); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | self.calculateUnassignedFieldCount = function () { | ||
| 304 | // Derive unassigned field count based on existence of Bootstrap "has-error" class | ||
| 305 | self.unassignedFieldCount($("#addNewEditDialog div.form-group.has-error").length); | ||
| 306 | } | ||
| 307 | |||
| 308 | self.refreshValidationErrors = function () { | ||
| 309 | // Force re-evaluation of validation errors property | ||
| 310 | self.unassignedFieldCount.valueHasMutated(); | ||
| 311 | |||
| 312 | // Make sure any initial validation error messages are visible | ||
| 313 | self.errors.showAllMessages(); | ||
| 314 | } | ||
| 315 | |||
| 316 | // Convert observable object to a simple Javascript record | ||
| 317 | self.deriveJSRecord = function () { | ||
| 318 | return $.Deferred(function (deferred) { | ||
| 319 | const observableRecord = self.currentRecord(); | ||
| 320 | const promises = []; | ||
| 321 | |||
| 322 | // Allow customization of observable record before conversion | ||
| 323 | $(self).trigger("derivingJSRecord", [observableRecord, promises]); | ||
| 324 | |||
| 325 | $(promises).whenAll().done(function () { | ||
| 326 | deferred.resolve(ko.mapping.toJS(observableRecord)); | ||
| 327 | }); | ||
| 328 | }).promise(); | ||
| 329 | } | ||
| 330 | |||
| 331 | // Convert simple Javascript record to an observable object | ||
| 332 | self.deriveObservableRecord = function (record) { | ||
| 333 | return $.Deferred(function (deferred) { | ||
| 334 | const observableRecord = ko.mapping.fromJS(record); | ||
| 335 | const promises = []; | ||
| 336 | |||
| 337 | // Allow customization of new observable record | ||
| 338 | $(self).trigger("derivingObservableRecord", [observableRecord, promises]); | ||
| 339 | |||
| 340 | $(promises).whenAll().done(function () { | ||
| 341 | // Apply validation binding to current observable record | ||
| 342 | self.errors = ko.validation.group(observableRecord); | ||
| 343 | self.refreshValidationErrors(); | ||
| 344 | deferred.resolve(observableRecord); | ||
| 345 | }); | ||
| 346 | }).promise(); | ||
| 347 | } | ||
| 348 | |||
| 349 | self.setFocusOnInitialField = function () { | ||
| 350 | if (!isEmpty(self.initialFocusField)) | ||
| 351 | $("#" + self.initialFocusField).focus(); | ||
| 352 | } | ||
| 353 | |||
| 354 | self.nextPage = function () { | ||
| 355 | if (self.currentPage() < self.totalPages()) | ||
| 356 | self.currentPage(self.currentPage() + 1); | ||
| 357 | } | ||
| 358 | |||
| 359 | self.previousPage = function () { | ||
| 360 | if (self.currentPage() > 1) | ||
| 361 | self.currentPage(self.currentPage() - 1); | ||
| 362 | } | ||
| 363 | |||
| 364 | self.updateSortOrder = function (fieldName, ascending) { | ||
| 365 | self.sortField(fieldName); | ||
| 366 | self.sortAscending(ascending); | ||
| 367 | self.queryPageRecords(); | ||
| 368 | |||
| 369 | // Save last sort order | ||
| 370 | Cookies.set(self.modelName + "!LastSortField", self.sortField(), { expires: 365 }); | ||
| 371 | Cookies.set(self.modelName + "!LastSortAscending", self.sortAscending().toString(), { expires: 365 }); | ||
| 372 | } | ||
| 373 | |||
| 374 | self.isSortOrder = function (fieldName, ascending) { | ||
| 375 | return self.sortField().toUpperCase() === fieldName.toUpperCase() && self.sortAscending() === ascending; | ||
| 376 | } | ||
| 377 | |||
| 378 | self.queryPageRecords = function () { | ||
| 379 | if (self.dataHubIsConnected()) | ||
| 380 | self.queryRecords(self.sortField(), self.sortAscending(), self.currentPage(), self.currentPageSize()).done(function (records) { | ||
| 381 | $(self).trigger("pageRecordsQueried", [records]); | ||
| 382 | self.pageRecords.removeAll(); | ||
| 383 | self.pageRecords(records); | ||
| 384 | refreshHubDependentControlState(); | ||
| 385 | $("[id='recordRow']").css("visibility", "visible"); | ||
| 386 | $("#loadingDataLabel").hide(); | ||
| 387 | |||
| 388 | // Validate proper page size after any record refresh | ||
| 389 | setTimeout(self.calculatePageSize, 150); | ||
| 390 | }).fail(function (error) { | ||
| 391 | showErrorMessage(error); | ||
| 392 | }); | ||
| 393 | } | ||
| 394 | |||
| 395 | self.removePageRecord = function (record) { | ||
| 396 | if (!self.canDelete()) | ||
| 397 | return; | ||
| 398 | |||
| 399 | if (self.dataHubIsConnected() && confirm("Are you sure you want to delete \"" + record[self.labelField] + "\"?")) { | ||
| 400 | const keyValues = []; | ||
| 401 | |||
| 402 | for (var i = 0; i < self.primaryKeyFields.length; i++) { | ||
| 403 | keyValues.push(record[self.primaryKeyFields[i]]); | ||
| 404 | } | ||
| 405 | |||
| 406 | self.deleteRecord(keyValues).done(function () { | ||
| 407 | self.pageRecords.remove(record); | ||
| 408 | self.initialize(); | ||
| 409 | $(self).trigger("recordDeleted", [record]); | ||
| 410 | showInfoMessage("Deleted record..."); | ||
| 411 | }).fail(function (error) { | ||
| 412 | showErrorMessage(error); | ||
| 413 | }); | ||
| 414 | } | ||
| 415 | } | ||
| 416 | |||
| 417 | self.savePageRecord = function () { | ||
| 418 | switch (self.recordMode()) { | ||
| 419 | case RecordMode.Edit: | ||
| 420 | self.saveEditedRecord(); | ||
| 421 | break; | ||
| 422 | case RecordMode.AddNew: | ||
| 423 | self.saveNewRecord(); | ||
| 424 | break; | ||
| 425 | } | ||
| 426 | } | ||
| 427 | |||
| 428 | self.saveEditedRecord = function () { | ||
| 429 | if (!self.canEdit()) | ||
| 430 | return; | ||
| 431 | |||
| 432 | if (self.dataHubIsConnected()) { | ||
| 433 | self.deriveJSRecord().done(function (record) { | ||
| 434 | self.updateRecord(record).done(function () { | ||
| 435 | self.initialize(); | ||
| 436 | $(self).trigger("recordSaved", [record, false]); | ||
| 437 | showInfoMessage("Saved updated record..."); | ||
| 438 | }).fail(function (error) { | ||
| 439 | showErrorMessage(error); | ||
| 440 | }); | ||
| 441 | }); | ||
| 442 | } | ||
| 443 | } | ||
| 444 | |||
| 445 | self.saveNewRecord = function () { | ||
| 446 | if (!self.canAddNew()) | ||
| 447 | return; | ||
| 448 | |||
| 449 | if (self.dataHubIsConnected()) { | ||
| 450 | self.deriveJSRecord().done(function (record) { | ||
| 451 | self.addNewRecord(record).done(function () { | ||
| 452 | self.initialize(); | ||
| 453 | $(self).trigger("recordSaved", [record, true]); | ||
| 454 | showInfoMessage("Saved new record..."); | ||
| 455 | }).fail(function (error) { | ||
| 456 | showErrorMessage(error); | ||
| 457 | }); | ||
| 458 | }); | ||
| 459 | } | ||
| 460 | } | ||
| 461 | |||
| 462 | self.viewPageRecord = function (record) { | ||
| 463 | self.deriveObservableRecord(record).done(function (observableRecord) { | ||
| 464 | self.recordMode(RecordMode.View); | ||
| 465 | self.currentRecord(observableRecord); | ||
| 466 | $("#addNewEditDialog").modal("show"); | ||
| 467 | }); | ||
| 468 | } | ||
| 469 | |||
| 470 | self.editPageRecord = function (record) { | ||
| 471 | if (!self.canEdit()) | ||
| 472 | return; | ||
| 473 | |||
| 474 | self.deriveObservableRecord(record).done(function (observableRecord) { | ||
| 475 | self.recordMode(RecordMode.Edit); | ||
| 476 | self.currentRecord(observableRecord); | ||
| 477 | $("#addNewEditDialog").modal("show"); | ||
| 478 | }); | ||
| 479 | } | ||
| 480 | |||
| 481 | self.addPageRecord = function () { | ||
| 482 | if (!self.canAddNew()) | ||
| 483 | return; | ||
| 484 | |||
| 485 | if (self.dataHubIsConnected()) { | ||
| 486 | self.newRecord().done(function (emptyRecord) { | ||
| 487 | // Raise event to allow any new record initialization | ||
| 488 | $(self).trigger("newRecord", [emptyRecord]); | ||
| 489 | |||
| 490 | self.deriveObservableRecord(emptyRecord).done(function (observableRecord) { | ||
| 491 | self.recordMode(RecordMode.AddNew); | ||
| 492 | self.currentRecord(observableRecord); | ||
| 493 | $("#addNewEditDialog").modal("show"); | ||
| 494 | }); | ||
| 495 | }); | ||
| 496 | } | ||
| 497 | } | ||
| 498 | |||
| 499 | self.cancelPageRecord = function () { | ||
| 500 | if (!self.isDirty() || confirm("Are you sure you want to discard unsaved changes?")) | ||
| 501 | $("#addNewEditDialog").modal("hide"); | ||
| 502 | } | ||
| 503 | }; | ||
| 504 | |||
| 505 | // Define page scoped view model instance | ||
| 506 | var viewModel = new PagedViewModel(); | ||
| 507 | |||
| 508 | (function ($, viewPort) { | ||
| 509 | $("#bodyContainer").addClass("fill-height"); | ||
| 510 | |||
| 511 | $("#titleText").html("Records: <span data-bind='text: recordCount'>calculating...</span>"); | ||
| 512 | |||
| 513 | $("#firstPageButton").click(function () { | ||
| 514 | viewModel.currentPage(1); | ||
| 515 | }); | ||
| 516 | |||
| 517 | $("#previousPageButton").click(function () { | ||
| 518 | viewModel.previousPage(); | ||
| 519 | }); | ||
| 520 | |||
| 521 | $("#nextPageButton").click(function () { | ||
| 522 | viewModel.nextPage(); | ||
| 523 | }); | ||
| 524 | |||
| 525 | $("#lastPageButton").click(function () { | ||
| 526 | viewModel.currentPage(viewModel.totalPages()); | ||
| 527 | }); | ||
| 528 | |||
| 529 | $(window).on("hubConnected", function (event) { | ||
| 530 | viewModel.dataHubIsConnected(true); | ||
| 531 | viewModel.initialize(); | ||
| 532 | }); | ||
| 533 | |||
| 534 | $(window).on("hubDisconnected", function (event) { | ||
| 535 | viewModel.dataHubIsConnected(false); | ||
| 536 | }); | ||
| 537 | |||
| 538 | $(window).on("messageVisibiltyChanged", function (event) { | ||
| 539 | viewModel.calculatePageSize(); | ||
| 540 | }); | ||
| 541 | |||
| 542 | $("#addNewEditDialog").on("shown.bs.modal", function () { | ||
| 543 | viewModel.setFocusOnInitialField(); | ||
| 544 | $("[data-toggle='tooltip']").tooltip(); | ||
| 545 | }); | ||
| 546 | |||
| 547 | $(window).resize( | ||
| 548 | viewPort.changed(function () { | ||
| 549 | viewModel.calculatePageSize(); | ||
| 550 | })); | ||
| 551 | |||
| 552 | viewModel.calculatePageSize(); | ||
| 553 | |||
| 554 | ko.validation.rules.pattern.message = "Invalid"; | ||
| 555 | |||
| 556 | ko.validation.init({ | ||
| 557 | registerExtenders: true, | ||
| 558 | messagesOnModified: true, | ||
| 559 | insertMessages: true, | ||
| 560 | parseInputAttributes: true, | ||
| 561 | allowHtmlMessages: true, | ||
| 562 | messageTemplate: null, | ||
| 563 | grouping: { deep: true, observable: true, live: true } | ||
| 564 | }, true); | ||
| 565 | |||
| 566 | ko.applyBindings(viewModel); | ||
| 567 | |||
| 568 | })(jQuery, ResponsiveBootstrapToolkit); | ||
| @@ -0,0 +1,348 @@ | |||
| 1 | @******************************************************************************************************* | ||
| 2 | // PagedViewModel.cshtml - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 01/22/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //*****************************************************************************************************@ | ||
| 23 | @using System | ||
| 24 | @using System.Collections.Generic | ||
| 25 | @using System.Security.Policy | ||
| 26 | @using System.Text | ||
| 27 | @using GSF | ||
| 28 | @using GSF.Web | ||
| 29 | @using GSF.Web.Model | ||
| 30 | @using GSF.Web.Templating | ||
| 31 | @{ | ||
| 32 | if (ViewBag.PageControlScripts == null) | ||
| 33 | { | ||
| 34 | ViewBag.PageControlScripts = new StringBuilder(); | ||
| 35 | } | ||
| 36 | |||
| 37 | StringBuilder pageControlScripts = ViewBag.PageControlScripts; | ||
| 38 | |||
| 39 | // Setup page control scripts | ||
| 40 | pageControlScripts.Append(Scripts.Render("~/js.bundle/knockout")); | ||
| 41 | pageControlScripts.Append($"<script src=\"{Url.Content("~/Scripts/pagedViewModel.js")}\"></script>"); | ||
| 42 | |||
| 43 | pageControlScripts.Append(@" | ||
| 44 | <script id=""validationMessageTemplate"" type=""text/html""><!-- | ||
| 45 | --><em class=""validationPrompt"" data-bind=""validationMessage: field""></em> | ||
| 46 | </script> | ||
| 47 | <script> | ||
| 48 | $(function () { | ||
| 49 | $(""#clearSortOrder"").click(function () { | ||
| 50 | viewModel.updateSortOrder(viewModel.defaultSortField, viewModel.defaultSortAscending); | ||
| 51 | }); | ||
| 52 | "); | ||
| 53 | |||
| 54 | // Check for optional page behaviors | ||
| 55 | |||
| 56 | // Hide the add new button | ||
| 57 | bool hideAddNewButton = ViewBag.HideAddNewButton ?? false; | ||
| 58 | |||
| 59 | // Hide unauthorized controls as disallowed by Edit/AddNew/Delete roles - otherwise unauthorized controls will be disabled | ||
| 60 | bool hideUnauthorizedControls = ViewBag.HideUnauthorizedControls ?? false; | ||
| 61 | |||
| 62 | // Auto pop-up add new dialog | ||
| 63 | bool autoShowAddNew = ViewBag.RouteID?.Equals("AddNew", StringComparison.OrdinalIgnoreCase) ?? false; | ||
| 64 | |||
| 65 | bool showIsDeletedToggleLink = false; | ||
| 66 | bool showingDeletedRecords = false; | ||
| 67 | |||
| 68 | // Check for existence of ShowDeleted property to control toggle link visibility | ||
| 69 | if (ViewBag.ShowDeleted != null) | ||
| 70 | { | ||
| 71 | showIsDeletedToggleLink = true; | ||
| 72 | showingDeletedRecords = Convert.ToBoolean(ViewBag.ShowDeleted); | ||
| 73 | } | ||
| 74 | |||
| 75 | // Get current allowed create / update / delete states for user - default to none. | ||
| 76 | // Note that overriding view bag settings will only affect UI, back end security | ||
| 77 | // will still be applicable | ||
| 78 | bool canEdit = ViewBag.CanEdit ?? false; | ||
| 79 | bool canAddNew = ViewBag.CanAddNew ?? false; | ||
| 80 | bool canDelete = ViewBag.CanDelete ?? false; | ||
| 81 | } | ||
| 82 | @functions | ||
| 83 | { | ||
| 84 | string SingularForm(string value) | ||
| 85 | { | ||
| 86 | if (value.EndsWith("ies", StringComparison.Ordinal)) | ||
| 87 | return value.Substring(0, value.Length - 3) + "y"; | ||
| 88 | |||
| 89 | if (value.EndsWith("IES", StringComparison.Ordinal)) | ||
| 90 | return value.Substring(0, value.Length - 3) + "Y"; | ||
| 91 | |||
| 92 | if (value.EndsWith("es", StringComparison.OrdinalIgnoreCase)) | ||
| 93 | return value.Substring(0, value.Length - 2); | ||
| 94 | |||
| 95 | if (value.EndsWith("s", StringComparison.OrdinalIgnoreCase)) | ||
| 96 | return value.Substring(0, value.Length - 1); | ||
| 97 | |||
| 98 | return value; | ||
| 99 | } | ||
| 100 | |||
| 101 | string RemoveSpaces(string value) | ||
| 102 | { | ||
| 103 | return value.RemoveWhiteSpace().RemoveInvalidFileNameCharacters(); | ||
| 104 | } | ||
| 105 | |||
| 106 | string RenderSortScript(string fieldName, bool ascending) | ||
| 107 | { | ||
| 108 | const string ScriptFormat = @" | ||
| 109 | $(""#sort{0} #{1}"").click(function () {{ | ||
| 110 | viewModel.updateSortOrder(""{0}"", {2}); | ||
| 111 | }}); | ||
| 112 | "; | ||
| 113 | |||
| 114 | return string.Format(ScriptFormat, fieldName, ascending ? "asc" : "desc", ascending.ToString().ToLower()); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | @helper WriteFieldHeader(StringBuilder pageControlScripts, string fieldName, string labelName = null, string classes = null) | ||
| 118 | { | ||
| 119 | if (string.IsNullOrEmpty(fieldName)) | ||
| 120 | { | ||
| 121 | <th class="@Html.Raw(classes ?? "text-center")" nowrap>@Html.Raw(labelName ?? "")</th> | ||
| 122 | } | ||
| 123 | else | ||
| 124 | { | ||
| 125 | <th class="@Html.Raw(classes ?? "text-center")" id="sort@(fieldName)" nowrap>@Html.Raw(labelName ?? fieldName) | ||
| 126 | <div class="btn-group-vertical btn-group-sort"> | ||
| 127 | <button type="button" class="btn" id="asc" title="Sort ascending..." data-bind="css: {'btn-primary': isSortOrder('@fieldName', true)}, enable: dataHubIsConnected"><span class="glyphicon glyphicon-chevron-up"></span></button> | ||
| 128 | <button type="button" class="btn" id="desc" title="Sort descending..." data-bind="css: {'btn-primary': isSortOrder('@fieldName', false)}, enable: dataHubIsConnected"><span class="glyphicon glyphicon-chevron-down"></span></button> | ||
| 129 | </div> | ||
| 130 | </th> | ||
| 131 | |||
| 132 | pageControlScripts.Append(RenderSortScript(fieldName, true)); | ||
| 133 | pageControlScripts.Append(RenderSortScript(fieldName, false)); | ||
| 134 | } | ||
| 135 | } | ||
| 136 | <div class="well well-dynamic-content" id="contentWell" content-fill-height> | ||
| 137 | <div class="table-responsive" id="responsiveTableDiv"> | ||
| 138 | <table class="table table-condensed table-hover" id="recordsTable"> | ||
| 139 | <thead> | ||
| 140 | <tr> | ||
| 141 | @foreach (string[] headerColumn in ViewBag.HeaderColumns) { | ||
| 142 | @WriteFieldHeader(pageControlScripts, headerColumn[0], headerColumn[1], headerColumn[2]) | ||
| 143 | } | ||
| 144 | <th class="text-center"> | ||
| 145 | <button type="button" class="btn btn-link btn-xs" style="line-height: 1.1" id="clearSortOrder" data-bind="enable: dataHubIsConnected">Clear<br/>Sort</button> | ||
| 146 | </th> | ||
| 147 | </tr> | ||
| 148 | </thead> | ||
| 149 | <tbody data-bind="foreach: pageRecords"> | ||
| 150 | <tr style="visibility: hidden" id="recordRow"@Html.Raw(ViewBag.ShowDeleted ?? false ? $" data-bind=\"css: {{'danger': {ViewBag.IsDeletedField}}}\"" : "")> | ||
| 151 | @Html.Raw(ViewBag.BodyRows) | ||
| 152 | </tr> | ||
| 153 | </tbody> | ||
| 154 | </table> | ||
| 155 | <div id="loadingDataLabel"> | ||
| 156 | Loading <span class="glyphicon glyphicon-refresh glyphicon-spin"></span> | ||
| 157 | </div> | ||
| 158 | </div> | ||
| 159 | <div class="pull-right" id="pageControlsRow"> | ||
| 160 | @if (!hideAddNewButton && (canAddNew || !hideUnauthorizedControls)) { | ||
| 161 | <button type="button" class="btn btn-sm btn-primary pull-right" id="addRecordButton" @Html.Raw(canAddNew ? "data-bind=\"enable: dataHubIsConnected\"" : "disabled")> | ||
| 162 | <span class="glyphicon glyphicon-plus"></span> Add New | ||
| 163 | </button> | ||
| 164 | <br /> | ||
| 165 | <hr class="half-break" /> | ||
| 166 | } | ||
| 167 | <div class="btn-group btn-group-sm"> | ||
| 168 | <button type="button" class="btn btn-default" id="firstPageButton" data-bind="css: {'disabled': onFirstPage()}, enable: dataHubIsConnected() && !onFirstPage()"><span class="glyphicon glyphicon-backward"></span></button> | ||
| 169 | <button type="button" class="btn btn-default" id="previousPageButton" data-bind="css: {'disabled': onFirstPage()}, enable: dataHubIsConnected() && !onFirstPage()"><span class="glyphicon glyphicon-triangle-left"></span></button> | ||
| 170 | </div> | ||
| 171 | <input type="number" class="content input-sm" style="padding: 0 0 0 5px; width: 55px" id="selectedPage" data-bind="numeric, textInput: currentPage, enable: dataHubIsConnected" value="1"> | ||
| 172 | of | ||
| 173 | <span data-bind="text: totalPages">1</span> | ||
| 174 | | ||
| 175 | <div class="btn-group btn-group-sm"> | ||
| 176 | <button type="button" class="btn btn-default" id="nextPageButton" data-bind="css: {'disabled': onLastPage()}, enable: dataHubIsConnected() && !onLastPage()"><span class="glyphicon glyphicon-triangle-right"></span></button> | ||
| 177 | <button type="button" class="btn btn-default" id="lastPageButton" data-bind="css: {'disabled': onLastPage()}, enable: dataHubIsConnected() && !onLastPage()"><span class="glyphicon glyphicon-forward"></span></button> | ||
| 178 | </div> | ||
| 179 | </div> | ||
| 180 | </div> | ||
| 181 | <div id="addNewEditDialog" class="modal fade" role="dialog"> | ||
| 182 | <div class="modal-dialog modal-lg"> | ||
| 183 | <div class="modal-content"> | ||
| 184 | <div class="modal-header" data-bind="css: {'modal-readonly': recordMode()===RecordMode.View}"> | ||
| 185 | <button type="button" class="close" id="dismissDialogButton">×</button> | ||
| 186 | <h4 class="modal-title"> | ||
| 187 | <span data-bind="visible: recordMode()===RecordMode.View">View</span> | ||
| 188 | <span data-bind="visible: recordMode()===RecordMode.Edit">Edit</span> | ||
| 189 | <span data-bind="visible: recordMode()===RecordMode.AddNew">Add New</span> | ||
| 190 | @SingularForm(ViewBag.Title ?? "") | ||
| 191 | </h4> | ||
| 192 | </div> | ||
| 193 | <div class="modal-body auto-height" data-bind="with: currentRecord, validationOptions: {messageTemplate: 'validationMessageTemplate'}, css: {'modal-readonly': recordMode()===RecordMode.View}"> | ||
| 194 | <form role="form"> | ||
| 195 | @Html.Raw(ViewBag.AddNewEditDialog) | ||
| 196 | </form> | ||
| 197 | </div> | ||
| 198 | <div class="modal-footer" data-bind="css: {'modal-readonly': recordMode()===RecordMode.View}"> | ||
| 199 | <em data-bind="visible: unassignedFieldCount() > 0 || validationErrors() > 0"> | ||
| 200 | <span data-bind="visible: unassignedFieldCount() > 0">Missing <span data-bind="text: unassignedFieldCount"></span> required field<span data-bind="visible: unassignedFieldCount() > 1">s</span></span> | ||
| 201 | <span data-bind="visible: unassignedFieldCount() > 0 && validationErrors() > 0"> and </span> | ||
| 202 | <span data-bind="visible: validationErrors() > 0"><span data-bind="text: validationErrors()"></span> validation error<span data-bind="visible: validationErrors() > 1">s</span></span> | ||
| 203 | ... | ||
| 204 | </em> | ||
| 205 | @if (canEdit || !hideUnauthorizedControls) { | ||
| 206 | <button type="button" class="btn btn-info" data-bind="visible: recordMode()===RecordMode.View@(Html.Raw(canEdit ? ", enable: dataHubIsConnected" : ""))" id="editRecordButton"@Html.Raw(canEdit ? "" : " disabled")>Edit</button> | ||
| 207 | } | ||
| 208 | @if (canEdit || canAddNew || !hideUnauthorizedControls) { | ||
| 209 | <button type="submit" class="btn btn-primary" data-dismiss="modal" data-bind="visible: recordMode()!==RecordMode.View@(Html.Raw(canEdit || canAddNew ? ", disable: unassignedFieldCount() > 0 || validationErrors() > 0 || !dataHubIsConnected()" : ""))" id="saveRecordButton"@Html.Raw(canEdit || canAddNew ? "" : " disabled")>Save</button> | ||
| 210 | } | ||
| 211 | <button type="button" class="btn btn-default" id="cancelRecordButton">Cancel</button> | ||
| 212 | </div> | ||
| 213 | </div> | ||
| 214 | </div> | ||
| 215 | </div> | ||
| 216 | @{ | ||
| 217 | DataContext<CSharpEmbeddedResource> dataContext = ViewBag.DataContext; | ||
| 218 | |||
| 219 | if (autoShowAddNew) { | ||
| 220 | pageControlScripts.Append(@" | ||
| 221 | // Add new pop-up requested via URL parameter | ||
| 222 | setTimeout(viewModel.addPageRecord, 500); | ||
| 223 | "); | ||
| 224 | } | ||
| 225 | |||
| 226 | if (showIsDeletedToggleLink) { | ||
| 227 | string baseURL = Request?.Url?.AbsolutePath ?? "/"; | ||
| 228 | baseURL = baseURL.Substring(0, baseURL.Length - (ViewBag.RouteID ?? "").Length); | ||
| 229 | |||
| 230 | if (showingDeletedRecords) { | ||
| 231 | pageControlScripts.Append($@" | ||
| 232 | $(""#titleText"").append(""<br /><div style='margin-top: 3px' class='small text-center'><a href='{baseURL}'>Hide Deleted</a></div>""); | ||
| 233 | "); | ||
| 234 | } | ||
| 235 | else { | ||
| 236 | pageControlScripts.Append($@" | ||
| 237 | $(""#titleText"").append(""<br /><div style='margin-top: 3px' class='small text-center'><a href='{baseURL.EnsureEnd('/')}ShowDeleted'>Show Deleted</a></div>""); | ||
| 238 | "); | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | pageControlScripts.Append(@" | ||
| 243 | $(""#addRecordButton"").click(function () { | ||
| 244 | viewModel.addPageRecord(); | ||
| 245 | }); | ||
| 246 | |||
| 247 | $(""#editRecordButton"").click(function () { | ||
| 248 | viewModel.recordMode(RecordMode.Edit); | ||
| 249 | }); | ||
| 250 | |||
| 251 | $(""#saveRecordButton"").click(function () { | ||
| 252 | viewModel.savePageRecord(); | ||
| 253 | }); | ||
| 254 | |||
| 255 | $(""#cancelRecordButton"").click(function () { | ||
| 256 | viewModel.cancelPageRecord(); | ||
| 257 | }); | ||
| 258 | |||
| 259 | $(""#dismissDialogButton"").click(function () { | ||
| 260 | viewModel.cancelPageRecord(); | ||
| 261 | }); | ||
| 262 | |||
| 263 | $(""#addNewEditDialog"").modal({show: false, backdrop: ""static"", keyboard: false}); | ||
| 264 | |||
| 265 | $(""#addNewEditDialog"").on(""shown.bs.modal"", function () { | ||
| 266 | $(""#addNewEditDialog .input-group.date"").datepicker({ | ||
| 267 | todayBtn: ""linked"", | ||
| 268 | enableOnReadonly: false, | ||
| 269 | autoclose: true | ||
| 270 | }); | ||
| 271 | }); | ||
| 272 | |||
| 273 | $(window).on(""beforeunload"", function () { | ||
| 274 | if ($(""#addNewEditDialog"").hasClass(""in"") && viewModel.isDirty()) | ||
| 275 | return ""There are unsaved changes to this record.""; | ||
| 276 | |||
| 277 | return undefined; | ||
| 278 | }); | ||
| 279 | }); | ||
| 280 | "); | ||
| 281 | |||
| 282 | pageControlScripts.AppendFormat(@" | ||
| 283 | viewModel.canEdit({0}); | ||
| 284 | viewModel.canAddNew({1}); | ||
| 285 | viewModel.canDelete({2}); | ||
| 286 | |||
| 287 | viewModel.initialFocusField = ""{3}""; | ||
| 288 | viewModel.modelName = ""{4}"";", | ||
| 289 | canEdit.ToString().ToLower(), | ||
| 290 | canAddNew.ToString().ToLower(), | ||
| 291 | canDelete.ToString().ToLower(), | ||
| 292 | dataContext.InitialFocusField, | ||
| 293 | RemoveSpaces(ViewBag.Title)); | ||
| 294 | |||
| 295 | if (dataContext.FieldValueInitializers.Count > 0) { | ||
| 296 | pageControlScripts.AppendFormat(@" | ||
| 297 | |||
| 298 | $(viewModel).on(""newRecord"", function(event, newRecord) {{ | ||
| 299 | // Set initial field values"); | ||
| 300 | |||
| 301 | foreach (Tuple<string, string> initialValue in dataContext.FieldValueInitializers) { | ||
| 302 | pageControlScripts.AppendFormat("\r\n newRecord.{0} = {1};", | ||
| 303 | initialValue.Item1, initialValue.Item2); | ||
| 304 | } | ||
| 305 | |||
| 306 | pageControlScripts.Append("\r\n });"); | ||
| 307 | } | ||
| 308 | |||
| 309 | if (dataContext.DefinedDateFields.Count > 0) { | ||
| 310 | pageControlScripts.AppendFormat(@" | ||
| 311 | |||
| 312 | $(viewModel).on(""derivingObservableRecord"", function(event, observableRecord) {{ | ||
| 313 | // Convert date objects to strings"); | ||
| 314 | |||
| 315 | foreach (string dateField in dataContext.DefinedDateFields) { | ||
| 316 | pageControlScripts.AppendFormat("\r\n observableRecord.{0}(notNull(observableRecord.{0}()).formatDate(DateFormat));", dateField); | ||
| 317 | } | ||
| 318 | |||
| 319 | pageControlScripts.Append("\r\n });"); | ||
| 320 | |||
| 321 | pageControlScripts.AppendFormat(@" | ||
| 322 | |||
| 323 | $(viewModel).on(""derivingJSRecord"", function(event, observableRecord) {{ | ||
| 324 | // Convert date strings back to date objects before serialization"); | ||
| 325 | |||
| 326 | foreach (string dateField in dataContext.DefinedDateFields) { | ||
| 327 | pageControlScripts.AppendFormat("\r\n observableRecord.{0}(observableRecord.{0}().toDate());", dateField); | ||
| 328 | } | ||
| 329 | |||
| 330 | pageControlScripts.Append("\r\n });"); | ||
| 331 | } | ||
| 332 | |||
| 333 | if (dataContext.FieldValidationParameters.Count > 0) { | ||
| 334 | pageControlScripts.Append("\r\n\r\n viewModel.setApplyValidationParameters(function () {"); | ||
| 335 | |||
| 336 | foreach (KeyValuePair<string, Tuple<string, string>> fieldValidation in dataContext.FieldValidationParameters) { | ||
| 337 | pageControlScripts.AppendFormat("\r\n {0}.extend({{ pattern: {{ message: \"{1}\", params: \"{2}\" }} }});", | ||
| 338 | fieldValidation.Key, | ||
| 339 | fieldValidation.Value.Item2.JavaScriptEncode(), | ||
| 340 | fieldValidation.Value.Item1.JavaScriptEncode()); | ||
| 341 | } | ||
| 342 | |||
| 343 | pageControlScripts.Append("\r\n });"); | ||
| 344 | } | ||
| 345 | |||
| 346 | pageControlScripts.Append(@" | ||
| 347 | </script>"); | ||
| 348 | } | ||
| @@ -0,0 +1,39 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // AuthorizationCache.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 03/11/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.Collections.Concurrent; | ||
| 26 | |||
| 27 | namespace GSF.Web.Security | ||
| 28 | { | ||
| 29 | /// <summary> | ||
| 30 | /// Defines authorization cache. | ||
| 31 | /// </summary> | ||
| 32 | public class AuthorizationCache | ||
| 33 | { | ||
| 34 | /// <summary> | ||
| 35 | /// Gets the current user ID cache. | ||
| 36 | /// </summary> | ||
| 37 | public static readonly ConcurrentDictionary<string, Guid> UserIDs = new ConcurrentDictionary<string, Guid>(StringComparer.OrdinalIgnoreCase); | ||
| 38 | } | ||
| 39 | } | ||
| @@ -0,0 +1,156 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // RoleBasedSecurityAttribute.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/23/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.Linq; | ||
| 26 | using System.Security; | ||
| 27 | using System.Threading; | ||
| 28 | using System.Web.Mvc; | ||
| 29 | using GSF.Collections; | ||
| 30 | using GSF.Data; | ||
| 31 | using GSF.Identity; | ||
| 32 | using GSF.Security; | ||
| 33 | |||
| 34 | namespace GSF.Web.Security | ||
| 35 | { | ||
| 36 | /// <summary> | ||
| 37 | /// Defines an MVC filter attribute to handle the GSF role based security model. | ||
| 38 | /// </summary> | ||
| 39 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)] | ||
| 40 | public class AuthorizeControllerRoleAttribute : FilterAttribute, IAuthorizationFilter, IExceptionFilter | ||
| 41 | { | ||
| 42 | #region [ Members ] | ||
| 43 | |||
| 44 | // Fields | ||
| 45 | |||
| 46 | /// <summary> | ||
| 47 | /// Gets allowed roles as a string array. | ||
| 48 | /// </summary> | ||
| 49 | public readonly string[] AllowedRoles; | ||
| 50 | |||
| 51 | #endregion | ||
| 52 | |||
| 53 | #region [ Constructors ] | ||
| 54 | |||
| 55 | /// <summary> | ||
| 56 | /// Creates a new MVC based authorization attribute. | ||
| 57 | /// </summary> | ||
| 58 | public AuthorizeControllerRoleAttribute() | ||
| 59 | { | ||
| 60 | AllowedRoles = new string[0]; | ||
| 61 | SettingsCategory = "securityProvider"; | ||
| 62 | SecurityExceptionViewName = "SecurityError"; | ||
| 63 | } | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Creates a new MVC based authorization attribute for specified roles. | ||
| 67 | /// </summary> | ||
| 68 | public AuthorizeControllerRoleAttribute(string allowedRoles) | ||
| 69 | { | ||
| 70 | AllowedRoles = allowedRoles?.Split(',').Select(role => role.Trim()). | ||
| 71 | Where(role => !string.IsNullOrEmpty(role)).ToArray() ?? new string[0]; | ||
| 72 | |||
| 73 | SettingsCategory = "securityProvider"; | ||
| 74 | SecurityExceptionViewName = "SecurityError"; | ||
| 75 | } | ||
| 76 | |||
| 77 | #endregion | ||
| 78 | |||
| 79 | #region [ Properties ] | ||
| 80 | |||
| 81 | /// <summary> | ||
| 82 | /// Gets or sets settings category to use for loading data context for security info. | ||
| 83 | /// </summary> | ||
| 84 | public string SettingsCategory | ||
| 85 | { | ||
| 86 | get; set; | ||
| 87 | } | ||
| 88 | |||
| 89 | /// <summary> | ||
| 90 | /// Gets or sets view name to show for security exceptions. | ||
| 91 | /// </summary> | ||
| 92 | public string SecurityExceptionViewName | ||
| 93 | { | ||
| 94 | get; set; | ||
| 95 | } | ||
| 96 | |||
| 97 | #endregion | ||
| 98 | |||
| 99 | #region [ Methods ] | ||
| 100 | |||
| 101 | /// <summary> | ||
| 102 | /// Called when authorization is required. | ||
| 103 | /// </summary> | ||
| 104 | /// <param name="filterContext">The filter context.</param> | ||
| 105 | public void OnAuthorization(AuthorizationContext filterContext) | ||
| 106 | { | ||
| 107 | // Initialize the security principal from caller's windows identity if uninitialized. | ||
| 108 | if (SecurityProviderCache.CurrentProvider == null) | ||
| 109 | SecurityProviderCache.CurrentProvider = SecurityProviderUtility.CreateProvider(string.Empty); | ||
| 110 | |||
| 111 | // Setup the principal | ||
| 112 | filterContext.HttpContext.User = Thread.CurrentPrincipal; | ||
| 113 | |||
| 114 | // Get current user name | ||
| 115 | string userName = Thread.CurrentPrincipal.Identity.Name; | ||
| 116 | |||
| 117 | // Verify that the current thread principal has been authenticated. | ||
| 118 | if (!Thread.CurrentPrincipal.Identity.IsAuthenticated) | ||
| 119 | throw new SecurityException($"Authentication failed for user '{userName}': {SecurityProviderCache.CurrentProvider.AuthenticationFailureReason}"); | ||
| 120 | |||
| 121 | if (AllowedRoles.Length > 0 && !AllowedRoles.Any(role => filterContext.HttpContext.User.IsInRole(role))) | ||
| 122 | throw new SecurityException($"Access is denied for user '{userName}' defined: minimum required roles = {AllowedRoles.ToDelimitedString(", ")}."); | ||
| 123 | |||
| 124 | // Make sure current user ID is cached | ||
| 125 | if (!AuthorizationCache.UserIDs.ContainsKey(userName)) | ||
| 126 | { | ||
| 127 | using (AdoDataConnection connection = new AdoDataConnection(SettingsCategory)) | ||
| 128 | { | ||
| 129 | AuthorizationCache.UserIDs.TryAdd(userName, connection.ExecuteScalar<Guid?>("SELECT ID FROM UserAccount WHERE Name={0}", UserInfo.UserNameToSID(userName)) ?? Guid.Empty); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | /// <summary> | ||
| 135 | /// Called when an exception occurs. | ||
| 136 | /// </summary> | ||
| 137 | /// <param name="filterContext">The filter context.</param> | ||
| 138 | public void OnException(ExceptionContext filterContext) | ||
| 139 | { | ||
| 140 | if (filterContext.Exception is SecurityException) | ||
| 141 | { | ||
| 142 | filterContext.Result = new ViewResult | ||
| 143 | { | ||
| 144 | ViewName = SecurityExceptionViewName, | ||
| 145 | ViewData = new ViewDataDictionary | ||
| 146 | { | ||
| 147 | ["Exception"] = filterContext.Exception | ||
| 148 | } | ||
| 149 | }; | ||
| 150 | filterContext.ExceptionHandled = true; | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | #endregion | ||
| 155 | } | ||
| 156 | } | ||
| @@ -0,0 +1,138 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // AuthorizeHubRole.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/25/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.Linq; | ||
| 26 | using System.Security; | ||
| 27 | using System.Security.Principal; | ||
| 28 | using System.Threading; | ||
| 29 | using GSF.Collections; | ||
| 30 | using GSF.Data; | ||
| 31 | using GSF.Identity; | ||
| 32 | using GSF.Security; | ||
| 33 | using Microsoft.AspNet.SignalR; | ||
| 34 | |||
| 35 | namespace GSF.Web.Security | ||
| 36 | { | ||
| 37 | /// <summary> | ||
| 38 | /// Defines a SignalR authorization attribute to handle the GSF role based security model. | ||
| 39 | /// </summary> | ||
| 40 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)] | ||
| 41 | public class AuthorizeHubRoleAttribute : AuthorizeAttribute | ||
| 42 | { | ||
| 43 | #region [ Members ] | ||
| 44 | |||
| 45 | // Fields | ||
| 46 | private string[] m_allowedRoles; | ||
| 47 | |||
| 48 | #endregion | ||
| 49 | |||
| 50 | #region [ Constructors ] | ||
| 51 | |||
| 52 | /// <summary> | ||
| 53 | /// Creates a new <see cref="AuthorizeHubRoleAttribute"/>. | ||
| 54 | /// </summary> | ||
| 55 | public AuthorizeHubRoleAttribute() | ||
| 56 | { | ||
| 57 | SettingsCategory = "securityProvider"; | ||
| 58 | } | ||
| 59 | |||
| 60 | /// <summary> | ||
| 61 | /// Creates a new <see cref="AuthorizeHubRoleAttribute"/> with specified allowed roles. | ||
| 62 | /// </summary> | ||
| 63 | public AuthorizeHubRoleAttribute(string allowedRoles) | ||
| 64 | { | ||
| 65 | Roles = allowedRoles; | ||
| 66 | SettingsCategory = "securityProvider"; | ||
| 67 | } | ||
| 68 | |||
| 69 | #endregion | ||
| 70 | |||
| 71 | #region [ Properties ] | ||
| 72 | |||
| 73 | /// <summary> | ||
| 74 | /// Gets or sets settings category to use for loading data context for security info. | ||
| 75 | /// </summary> | ||
| 76 | public string SettingsCategory | ||
| 77 | { | ||
| 78 | get; set; | ||
| 79 | } | ||
| 80 | |||
| 81 | /// <summary> | ||
| 82 | /// Gets the allowed <see cref="AuthorizeAttribute.Roles"/> as a string array. | ||
| 83 | /// </summary> | ||
| 84 | public string[] AllowedRoles => m_allowedRoles ?? (m_allowedRoles = Roles?.Split(',').Select(role => role.Trim()).Where(role => !string.IsNullOrEmpty(role)).ToArray() ?? new string[0]); | ||
| 85 | |||
| 86 | #endregion | ||
| 87 | |||
| 88 | #region [ Methods ] | ||
| 89 | |||
| 90 | /// <summary> | ||
| 91 | /// Provides an entry point for custom authorization checks. | ||
| 92 | /// </summary> | ||
| 93 | /// <param name="user">The <see cref="IPrincipal"/> for the client being authorize</param> | ||
| 94 | /// <returns> | ||
| 95 | /// <c>true</c> if the user is authorized, otherwise, <c>false</c>. | ||
| 96 | /// </returns> | ||
| 97 | protected override bool UserAuthorized(IPrincipal user) | ||
| 98 | { | ||
| 99 | // Get current user name | ||
| 100 | string userName = user.Identity.Name; | ||
| 101 | |||
| 102 | // Initialize the security principal from caller's windows identity if uninitialized, note that | ||
| 103 | // simply by checking current provider any existing cached security principal will be restored | ||
| 104 | if (SecurityProviderCache.CurrentProvider == null) | ||
| 105 | { | ||
| 106 | lock (typeof(AuthorizeHubRoleAttribute)) | ||
| 107 | { | ||
| 108 | // Let's see if we won the race... | ||
| 109 | if (SecurityProviderCache.CurrentProvider == null) | ||
| 110 | SecurityProviderCache.CurrentProvider = SecurityProviderUtility.CreateProvider(userName); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | // Setup the principal | ||
| 115 | user = Thread.CurrentPrincipal; | ||
| 116 | |||
| 117 | // Verify that the current thread principal has been authenticated. | ||
| 118 | if (!Thread.CurrentPrincipal.Identity.IsAuthenticated) | ||
| 119 | throw new SecurityException($"Authentication failed for user '{userName}': {SecurityProviderCache.CurrentProvider.AuthenticationFailureReason}"); | ||
| 120 | |||
| 121 | if (AllowedRoles.Length > 0 && !AllowedRoles.Any(role => user.IsInRole(role))) | ||
| 122 | throw new SecurityException($"Access is denied for user '{userName}' defined: minimum required roles = {AllowedRoles.ToDelimitedString(", ")}."); | ||
| 123 | |||
| 124 | // Make sure current user ID is cached | ||
| 125 | if (!AuthorizationCache.UserIDs.ContainsKey(userName)) | ||
| 126 | { | ||
| 127 | using (AdoDataConnection connection = new AdoDataConnection(SettingsCategory)) | ||
| 128 | { | ||
| 129 | AuthorizationCache.UserIDs.TryAdd(userName, connection.ExecuteScalar<Guid?>("SELECT ID FROM UserAccount WHERE Name={0}", UserInfo.UserNameToSID(userName)) ?? Guid.Empty); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | return true; | ||
| 134 | } | ||
| 135 | |||
| 136 | #endregion | ||
| 137 | } | ||
| 138 | } | ||
| @@ -0,0 +1,292 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // SecurityHub.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 03/03/2016 - Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.Collections.Generic; | ||
| 26 | using GSF.Configuration; | ||
| 27 | using GSF.Data; | ||
| 28 | using GSF.Data.Model; | ||
| 29 | using GSF.Identity; | ||
| 30 | using GSF.Security.Model; | ||
| 31 | using GSF.Web.Model; | ||
| 32 | using Microsoft.AspNet.SignalR; | ||
| 33 | using Microsoft.AspNet.SignalR.Hubs; | ||
| 34 | |||
| 35 | namespace GSF.Web.Security | ||
| 36 | { | ||
| 37 | /// <summary> | ||
| 38 | /// Defines a SignalR security hub for managing users, groups and SID management. | ||
| 39 | /// </summary> | ||
| 40 | [AuthorizeHubRole] | ||
| 41 | public class SecurityHub : Hub | ||
| 42 | { | ||
| 43 | #region [ Members ] | ||
| 44 | |||
| 45 | // Fields | ||
| 46 | private readonly DataContext m_dataContext; | ||
| 47 | private bool m_disposed; | ||
| 48 | |||
| 49 | #endregion | ||
| 50 | |||
| 51 | #region [ Constructors ] | ||
| 52 | |||
| 53 | /// <summary> | ||
| 54 | /// Creates a new <see cref="SecurityHub"/>. | ||
| 55 | /// </summary> | ||
| 56 | public SecurityHub() | ||
| 57 | { | ||
| 58 | m_dataContext = new DataContext(); | ||
| 59 | } | ||
| 60 | |||
| 61 | #endregion | ||
| 62 | |||
| 63 | #region [ Methods ] | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Releases the unmanaged resources used by the <see cref="SecurityHub"/> object and optionally releases the managed resources. | ||
| 67 | /// </summary> | ||
| 68 | /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> | ||
| 69 | protected override void Dispose(bool disposing) | ||
| 70 | { | ||
| 71 | if (!m_disposed) | ||
| 72 | { | ||
| 73 | try | ||
| 74 | { | ||
| 75 | if (disposing) | ||
| 76 | m_dataContext?.Dispose(); | ||
| 77 | } | ||
| 78 | finally | ||
| 79 | { | ||
| 80 | m_disposed = true; // Prevent duplicate dispose. | ||
| 81 | base.Dispose(disposing); // Call base class Dispose(). | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | #endregion | ||
| 87 | |||
| 88 | // Client-side script functionality | ||
| 89 | |||
| 90 | /// <summary> | ||
| 91 | /// Gets the specified user account record. | ||
| 92 | /// </summary> | ||
| 93 | /// <param name="id">ID of requested user.</param> | ||
| 94 | /// <returns>Specified user account record.</returns> | ||
| 95 | public UserAccount QueryUserAccount(Guid id) | ||
| 96 | { | ||
| 97 | return m_dataContext.Table<UserAccount>().LoadRecord(id); | ||
| 98 | } | ||
| 99 | |||
| 100 | /// <summary> | ||
| 101 | /// Gets the current application role records. | ||
| 102 | /// </summary> | ||
| 103 | /// <returns>Current application role records.</returns> | ||
| 104 | public IEnumerable<ApplicationRole> QueryApplicationRoles() | ||
| 105 | { | ||
| 106 | return m_dataContext.Table<ApplicationRole>().QueryRecords("Name", new RecordRestriction("NodeID={0}", DefaultNodeID)); | ||
| 107 | } | ||
| 108 | |||
| 109 | /// <summary> | ||
| 110 | /// Determines if user is in role based on database ID values. | ||
| 111 | /// </summary> | ||
| 112 | /// <param name="userID">User ID value.</param> | ||
| 113 | /// <param name="roleID">Role ID value.</param> | ||
| 114 | /// <returns><c>true</c> if user is in role; otherwise, <c>false</c>.</returns> | ||
| 115 | public bool UserIsInRole(Guid userID, Guid roleID) | ||
| 116 | { | ||
| 117 | return m_dataContext.Table<ApplicationRoleUserAccount>().QueryRecordCount(new RecordRestriction("UserAccountID={0} AND ApplicationRoleID={1}", userID, roleID)) > 0; | ||
| 118 | } | ||
| 119 | |||
| 120 | /// <summary> | ||
| 121 | /// Adds user to a role. | ||
| 122 | /// </summary> | ||
| 123 | /// <param name="userID">User ID value.</param> | ||
| 124 | /// <param name="roleID">Role ID value.</param> | ||
| 125 | /// <returns><c>true</c> if user was added; otherwise <c>false</c>.</returns> | ||
| 126 | [AuthorizeHubRole("Administrator")] | ||
| 127 | public bool AddUserToRole(Guid userID, Guid roleID) | ||
| 128 | { | ||
| 129 | // Nothing to do if user is already in role | ||
| 130 | if (UserIsInRole(userID, roleID)) | ||
| 131 | return false; | ||
| 132 | |||
| 133 | return m_dataContext.Table<ApplicationRoleUserAccount>().AddNewRecord(new ApplicationRoleUserAccount | ||
| 134 | { | ||
| 135 | ApplicationRoleID = roleID, | ||
| 136 | UserAccountID = userID | ||
| 137 | }) > 0; | ||
| 138 | } | ||
| 139 | |||
| 140 | /// <summary> | ||
| 141 | /// Removes user from a role. | ||
| 142 | /// </summary> | ||
| 143 | /// <param name="userID">User ID value.</param> | ||
| 144 | /// <param name="roleID">Role ID value.</param> | ||
| 145 | /// <returns><c>true</c> if user was removed; otherwise <c>false</c>.</returns> | ||
| 146 | [AuthorizeHubRole("Administrator")] | ||
| 147 | public bool RemoveUserFromRole(Guid userID, Guid roleID) | ||
| 148 | { | ||
| 149 | // Nothing to do if user is not currently in role | ||
| 150 | if (!UserIsInRole(userID, roleID)) | ||
| 151 | return false; | ||
| 152 | |||
| 153 | return m_dataContext.Table<ApplicationRoleUserAccount>().DeleteRecord(new RecordRestriction("UserAccountID={0} AND ApplicationRoleID={1}", userID, roleID)) > 0; | ||
| 154 | } | ||
| 155 | |||
| 156 | /// <summary> | ||
| 157 | /// Gets SID for a given user name. | ||
| 158 | /// </summary> | ||
| 159 | /// <param name="userName">User name to convert to SID.</param> | ||
| 160 | /// <returns>SID for a given user name.</returns> | ||
| 161 | public string UserNameToSID(string userName) | ||
| 162 | { | ||
| 163 | return UserInfo.UserNameToSID(userName); | ||
| 164 | } | ||
| 165 | |||
| 166 | /// <summary> | ||
| 167 | /// Determines if SID is for a user. | ||
| 168 | /// </summary> | ||
| 169 | /// <param name="sid">Security identifier to test.</param> | ||
| 170 | /// <returns><c>true</c>if <paramref name="sid"/> is for a user; otherwise, <c>false</c>.</returns> | ||
| 171 | [HubMethodName("isUserSID")] | ||
| 172 | public bool IsUserSID(string sid) | ||
| 173 | { | ||
| 174 | return UserInfo.IsUserSID(sid); | ||
| 175 | } | ||
| 176 | |||
| 177 | /// <summary> | ||
| 178 | /// Determines if group is in role based on database ID values. | ||
| 179 | /// </summary> | ||
| 180 | /// <param name="groupID">Group ID value.</param> | ||
| 181 | /// <param name="roleID">Role ID value.</param> | ||
| 182 | /// <returns><c>true</c> if group is in role; otherwise, <c>false</c>.</returns> | ||
| 183 | public bool GroupIsInRole(Guid groupID, Guid roleID) | ||
| 184 | { | ||
| 185 | return m_dataContext.Table<ApplicationRoleSecurityGroup>().QueryRecordCount(new RecordRestriction("SecurityGroupID={0} AND ApplicationRoleID={1}", groupID, roleID)) > 0; | ||
| 186 | } | ||
| 187 | |||
| 188 | /// <summary> | ||
| 189 | /// Adds group to a role. | ||
| 190 | /// </summary> | ||
| 191 | /// <param name="groupID">Group ID value.</param> | ||
| 192 | /// <param name="roleID">Role ID value.</param> | ||
| 193 | /// <returns><c>true</c> if group was added; otherwise <c>false</c>.</returns> | ||
| 194 | [AuthorizeHubRole("Administrator")] | ||
| 195 | public bool AddGroupToRole(Guid groupID, Guid roleID) | ||
| 196 | { | ||
| 197 | // Nothing to do if group is already in role | ||
| 198 | if (GroupIsInRole(groupID, roleID)) | ||
| 199 | return false; | ||
| 200 | |||
| 201 | return m_dataContext.Table<ApplicationRoleSecurityGroup>().AddNewRecord(new ApplicationRoleSecurityGroup | ||
| 202 | { | ||
| 203 | ApplicationRoleID = roleID, | ||
| 204 | SecurityGroupID = groupID | ||
| 205 | }) > 0; | ||
| 206 | } | ||
| 207 | |||
| 208 | /// <summary> | ||
| 209 | /// Removes group from a role. | ||
| 210 | /// </summary> | ||
| 211 | /// <param name="groupID">Group ID value.</param> | ||
| 212 | /// <param name="roleID">Role ID value.</param> | ||
| 213 | /// <returns><c>true</c> if group was removed; otherwise <c>false</c>.</returns> | ||
| 214 | [AuthorizeHubRole("Administrator")] | ||
| 215 | public bool RemoveGroupFromRole(Guid groupID, Guid roleID) | ||
| 216 | { | ||
| 217 | // Nothing to do if group is not currently in role | ||
| 218 | if (!GroupIsInRole(groupID, roleID)) | ||
| 219 | return false; | ||
| 220 | |||
| 221 | return m_dataContext.Table<ApplicationRoleSecurityGroup>().DeleteRecord(new RecordRestriction("SecurityGroupID={0} AND ApplicationRoleID={1}", groupID, roleID)) > 0; | ||
| 222 | } | ||
| 223 | |||
| 224 | /// <summary> | ||
| 225 | /// Gets SID for a given group name. | ||
| 226 | /// </summary> | ||
| 227 | /// <param name="groupName">Group name to convert to SID.</param> | ||
| 228 | /// <returns>SID for a given group name.</returns> | ||
| 229 | public string GroupNameToSID(string groupName) | ||
| 230 | { | ||
| 231 | return UserInfo.GroupNameToSID(groupName ?? ""); | ||
| 232 | } | ||
| 233 | |||
| 234 | /// <summary> | ||
| 235 | /// Determines if SID is for a group. | ||
| 236 | /// </summary> | ||
| 237 | /// <param name="sid">Security identifier to test.</param> | ||
| 238 | /// <returns><c>true</c>if <paramref name="sid"/> is for a group; otherwise, <c>false</c>.</returns> | ||
| 239 | [HubMethodName("isGroupSID")] | ||
| 240 | public bool IsGroupSID(string sid) | ||
| 241 | { | ||
| 242 | return UserInfo.IsGroupSID(sid); | ||
| 243 | } | ||
| 244 | |||
| 245 | /// <summary> | ||
| 246 | /// Gets account name for a given SID. | ||
| 247 | /// </summary> | ||
| 248 | /// <param name="sid">SID to convert to a account name.</param> | ||
| 249 | /// <returns>Account name for a given SID.</returns> | ||
| 250 | [HubMethodName("sidToAccountName")] | ||
| 251 | public string SIDToAccountName(string sid) | ||
| 252 | { | ||
| 253 | return UserInfo.SIDToAccountName(sid ?? ""); | ||
| 254 | } | ||
| 255 | |||
| 256 | #region [ Static ] | ||
| 257 | |||
| 258 | // Static Fields | ||
| 259 | |||
| 260 | /// <summary> | ||
| 261 | /// Gets current default Node ID for security. | ||
| 262 | /// </summary> | ||
| 263 | public static readonly Guid DefaultNodeID; | ||
| 264 | |||
| 265 | // Static Constructor | ||
| 266 | static SecurityHub() | ||
| 267 | { | ||
| 268 | CategorizedSettingsElementCollection systemSettings = ConfigurationFile.Current.Settings["systemSettings"]; | ||
| 269 | |||
| 270 | // Retrieve default NodeID | ||
| 271 | DefaultNodeID = Guid.Parse(systemSettings["NodeID"].Value.ToNonNullString(Guid.NewGuid().ToString())); | ||
| 272 | |||
| 273 | // Determine whether the node exists in the database and create it if it doesn't | ||
| 274 | using (AdoDataConnection connection = new AdoDataConnection("systemSettings")) | ||
| 275 | { | ||
| 276 | const string NodeCountFormat = "SELECT COUNT(*) FROM Node"; | ||
| 277 | const string NodeInsertFormat = "INSERT INTO Node(Name, Description, Enabled) VALUES('Default', 'Default node', 1)"; | ||
| 278 | const string NodeUpdateFormat = "UPDATE Node SET ID = {0}"; | ||
| 279 | |||
| 280 | int nodeCount = connection.ExecuteScalar<int?>(NodeCountFormat) ?? 0; | ||
| 281 | |||
| 282 | if (nodeCount == 0) | ||
| 283 | { | ||
| 284 | connection.ExecuteNonQuery(NodeInsertFormat); | ||
| 285 | connection.ExecuteNonQuery(NodeUpdateFormat, connection.Guid(DefaultNodeID)); | ||
| 286 | } | ||
| 287 | } | ||
| 288 | } | ||
| 289 | |||
| 290 | #endregion | ||
| 291 | } | ||
| 292 | } | ||
| @@ -0,0 +1,370 @@ | |||
| 1 | @******************************************************************************************************* | ||
| 2 | // Groups.cshtml - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 03/03/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //*****************************************************************************************************@ | ||
| 23 | @using GSF.Data.Model | ||
| 24 | @using GSF.Security.Model | ||
| 25 | @using GSF.Web.Model | ||
| 26 | @section StyleSheets{ | ||
| 27 | <style> | ||
| 28 | .account-feedback-wrapper { | ||
| 29 | position: relative; | ||
| 30 | display: table | ||
| 31 | } | ||
| 32 | |||
| 33 | .account-feedback-message { | ||
| 34 | position: absolute; | ||
| 35 | vertical-align: middle; | ||
| 36 | display: table-cell; | ||
| 37 | left: 10px; | ||
| 38 | width: 425px; | ||
| 39 | display: none | ||
| 40 | } | ||
| 41 | |||
| 42 | .account-feedback-icon { | ||
| 43 | font-size: large; | ||
| 44 | vertical-align: middle; | ||
| 45 | margin-top: -5px | ||
| 46 | } | ||
| 47 | </style> | ||
| 48 | } | ||
| 49 | @{ | ||
| 50 | DataContext dataContext = ViewBag.DataContext; | ||
| 51 | |||
| 52 | // Define column headers, use null for field name to make column non-sortable | ||
| 53 | ViewBag.HeaderColumns = new[] | ||
| 54 | { // { "Field", "Label", "Classes" } | ||
| 55 | new[] { null, "Group Name", "text-center valign-middle" }, | ||
| 56 | new[] { null, "Is AD Group", "text-center" }, | ||
| 57 | new[] { "CreatedOn", "Date Added", "text-center" }, | ||
| 58 | new[] { null, "Group Roles", "text-center valign-middle" } | ||
| 59 | }; | ||
| 60 | |||
| 61 | ViewBag.BodyRows = BodyRows().ToString(); | ||
| 62 | ViewBag.AddNewEditDialog = AddNewEditDialog(dataContext).ToString(); | ||
| 63 | } | ||
| 64 | @helper BodyRows() | ||
| 65 | { | ||
| 66 | <td width="65%" class="text-center valign-middle" nowrap><button type="button" class="btn btn-link" data-bind="text: AccountName(), attr: {title: Name}, click: $parent.viewPageRecord"></button></td> | ||
| 67 | <td width="10%" class="text-center valign-middle"><input type="checkbox" id="checkEnabled" data-bind="checked: useADAuthentication(AccountName())" disabled></td> | ||
| 68 | <td width="10%" class="text-center valign-middle" data-bind="text: CreatedOn.formatDate(DateFormat)"></td> | ||
| 69 | <td width="10%" class="text-center valign-middle"><button type="button" class="btn btn-default btn-sm" data-bind="click: openRolesEditor.bind($data), enable: $parent.canEdit() && $parent.dataHubIsConnected()">Roles...</button></td> | ||
| 70 | <td width="5%" class="text-center valign-middle" nowrap> | ||
| 71 | <button type="button" class="btn btn-xs" data-bind="click: $parent.editPageRecord, enable: $parent.canEdit() && $parent.dataHubIsConnected()"><span class="glyphicon glyphicon-pencil"></span></button> | ||
| 72 | <button type="button" class="btn btn-xs" data-bind="click: $parent.removePageRecord, enable: $parent.canDelete() && $parent.dataHubIsConnected()"><span class="glyphicon glyphicon-remove"></span></button> | ||
| 73 | </td> | ||
| 74 | } | ||
| 75 | @helper AccountValidationFeedback() | ||
| 76 | { | ||
| 77 | <div class="pull-right account-feedback-wrapper"> | ||
| 78 | <span id="resolvingAccount" class="account-feedback-message"><span class="glyphicon glyphicon-refresh glyphicon-spin account-feedback-icon"></span> <em class="small">Resolving account details...</em></span> | ||
| 79 | <span id="accountValid" class="account-feedback-message"><span class="glyphicon glyphicon-ok-circle account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'green'}"></span> <em class="small">Resolved account name</em></span> | ||
| 80 | <span id="accountInvalid" class="account-feedback-message"><span class="glyphicon glyphicon-remove-circle account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'red'}"></span> <em class="small">Cannot resolve account name</em></span> | ||
| 81 | <span id="accountUnknown" class="account-feedback-message"><span class="glyphicon glyphicon-warning-sign account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'orange'}"></span> <em class="small">Valid account name is not a group</em></span> | ||
| 82 | </div> | ||
| 83 | } | ||
| 84 | @helper AddNewEditDialog(DataContext dataContext) | ||
| 85 | { | ||
| 86 | <div class="col-md-12"> | ||
| 87 | @Html.Raw(dataContext.AddInputField<SecurityGroup>("Name", fieldLabel: $"Name{AccountValidationFeedback().ToString().Trim()}", customDataBinding: "attr: {'acct-valid': validateAccount($data)}", initialFocus: true)) | ||
| 88 | <div class="panel panel-default" data-bind="css: {'panel-readonly': $root.recordMode()===RecordMode.View, 'panel-primary': UseDBAuthentication}"> | ||
| 89 | <div class="panel-heading"> | ||
| 90 | <div class="row"> | ||
| 91 | <div class="col-md-8"> | ||
| 92 | <div class="row"> | ||
| 93 | <div class="col-md-4"> | ||
| 94 | <label class="radio-inline"><input type="radio" name="radioUseDBAuthentication" data-bind="checkedValue: false, checked: UseDBAuthentication, attr: {'disabled': ($root.recordMode()===RecordMode.View ? true : undefined)}"> Active Directory Group</label> | ||
| 95 | </div> | ||
| 96 | <div class="col-md-8"> | ||
| 97 | <label class="radio-inline"><input type="radio" name="radioUseDBAuthentication" data-bind="checkedValue: true, checked: UseDBAuthentication, attr: {'disabled': ($root.recordMode()===RecordMode.View ? true : undefined)}"> Database Group</label> | ||
| 98 | </div> | ||
| 99 | </div> | ||
| 100 | </div> | ||
| 101 | <div class="col-md-4"> | ||
| 102 | </div> | ||
| 103 | </div> | ||
| 104 | </div> | ||
| 105 | <div class="panel-body" data-bind="visible: UseDBAuthentication"> | ||
| 106 | @Html.Raw(dataContext.AddTextAreaField<SecurityGroup>("Description", 4, dependencyFieldName: "UseDBAuthentication")) | ||
| 107 | </div> | ||
| 108 | </div> | ||
| 109 | </div> | ||
| 110 | } | ||
| 111 | @Html.Partial("PagedViewModel") | ||
| 112 | <div id="editRolesDialog" class="modal fade" role="dialog"> | ||
| 113 | <div class="modal-dialog"> | ||
| 114 | <div class="modal-content"> | ||
| 115 | <div class="modal-header"> | ||
| 116 | <button type="button" class="close" data-dismiss="modal">×</button> | ||
| 117 | <h4 class="modal-title">Edit Roles for <span id="currentGroupName"></span>:</h4> | ||
| 118 | </div> | ||
| 119 | <div class="modal-body"> | ||
| 120 | <div class="modal-body auto-height text-center"> | ||
| 121 | <form role="form"> | ||
| 122 | <input type="hidden" id="currentGroupID" /> | ||
| 123 | <div class="center-block"> | ||
| 124 | <div id="loadingRoles">Loading role assignments <span class="glyphicon glyphicon-refresh glyphicon-spin"></span><hr class="full-break" /></div> | ||
| 125 | <table class="table-condensed table-center"> | ||
| 126 | @foreach (ApplicationRole role in dataContext.Table<ApplicationRole>().QueryRecords("Name", new RecordRestriction("NodeID={0}", Model.Global.NodeID))) { | ||
| 127 | <tr> | ||
| 128 | <td class="text-left valign-middle"> | ||
| 129 | <div class="checkbox" style="margin: 0"> | ||
| 130 | <label for="@role.ID"><input type="checkbox" id="@role.ID"> @role.Description (@role.Name)</label> | ||
| 131 | </div> | ||
| 132 | </td> | ||
| 133 | </tr> | ||
| 134 | } | ||
| 135 | </table> | ||
| 136 | <br /> | ||
| 137 | <div class="text-center"> | ||
| 138 | <a href="@Url.Action("Help", "Main")#Roles" target="_blank">@Model.Global.ApplicationName Role Definitions</a> | ||
| 139 | </div> | ||
| 140 | </div> | ||
| 141 | </form> | ||
| 142 | </div> | ||
| 143 | </div> | ||
| 144 | <div class="modal-footer"> | ||
| 145 | <button type="submit" class="btn btn-primary" data-dismiss="modal" id="saveRolesButton" data-bind="enable: canEdit()">Save</button> | ||
| 146 | <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> | ||
| 147 | </div> | ||
| 148 | </div> | ||
| 149 | </div> | ||
| 150 | </div> | ||
| 151 | @section Scripts { | ||
| 152 | <script> | ||
| 153 | "use strict"; | ||
| 154 | |||
| 155 | @Html.Raw(dataContext.RenderViewModelConfiguration<SecurityGroup>(ViewBag)) | ||
| 156 | var AccountState = { | ||
| 157 | Invalid: 0, | ||
| 158 | Valid: 1, | ||
| 159 | Unknown: 2, | ||
| 160 | Resolving: 3 | ||
| 161 | } | ||
| 162 | |||
| 163 | function lookupAccountName(record) { | ||
| 164 | if (viewModel.dataHubIsConnected()) { | ||
| 165 | securityHub.sidToAccountName(record.Name).done(function (accountName) { | ||
| 166 | record.AccountName(accountName); | ||
| 167 | }); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | function validateAccount(observableRecord) { | ||
| 172 | showAccountNameValidityFeedback(AccountState.Resolving); | ||
| 173 | |||
| 174 | if (observableRecord.UseDBAuthentication == null || observableRecord.UseDBAuthentication()) { | ||
| 175 | hideAccountNameValidityFeedback(); | ||
| 176 | } | ||
| 177 | else { | ||
| 178 | if (viewModel.dataHubIsConnected()) { | ||
| 179 | const accountName = observableRecord.Name(); | ||
| 180 | |||
| 181 | if (isEmpty(accountName)) { | ||
| 182 | showAccountNameValidityFeedback(AccountState.Invalid); | ||
| 183 | } else { | ||
| 184 | securityHub.groupNameToSID(accountName).done(function (sid) { | ||
| 185 | if (accountName !== sid && accountName.countOccurrences("\\") < 2) { | ||
| 186 | if (accountName.startsWith("NT AUTHORITY")) { | ||
| 187 | // This is a valid group source for things like "Authenticated Users", but is | ||
| 188 | // not technically a "group" - since SID validated, we report a valid account | ||
| 189 | showAccountNameValidityFeedback(AccountState.Valid); | ||
| 190 | } else { | ||
| 191 | securityHub.isGroupSID(sid).done(function (isGroup) { | ||
| 192 | showAccountNameValidityFeedback(isGroup ? AccountState.Valid : AccountState.Unknown); | ||
| 193 | }); | ||
| 194 | } | ||
| 195 | } else { | ||
| 196 | showAccountNameValidityFeedback(AccountState.Invalid); | ||
| 197 | } | ||
| 198 | }); | ||
| 199 | } | ||
| 200 | } | ||
| 201 | else { | ||
| 202 | hideAccountNameValidityFeedback(); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | return undefined; | ||
| 207 | } | ||
| 208 | |||
| 209 | var messages = []; | ||
| 210 | var nextMessageName = undefined; | ||
| 211 | var showingMessage = false; | ||
| 212 | messages.push("resolvingAccount"); | ||
| 213 | messages.push("accountValid"); | ||
| 214 | messages.push("accountInvalid"); | ||
| 215 | messages.push("accountUnknown"); | ||
| 216 | |||
| 217 | function showFeedbackMessage(messageName) { | ||
| 218 | if (showingMessage) { | ||
| 219 | nextMessageName = messageName; | ||
| 220 | } else { | ||
| 221 | showingMessage = true; | ||
| 222 | |||
| 223 | hideAccountNameValidityFeedback(messageName); | ||
| 224 | |||
| 225 | if (messageName == null) | ||
| 226 | showingMessage = false; | ||
| 227 | else | ||
| 228 | $("#" + messageName).fadeIn(function () { | ||
| 229 | showingMessage = false; | ||
| 230 | |||
| 231 | if (nextMessageName != undefined) { | ||
| 232 | messageName = nextMessageName; | ||
| 233 | nextMessageName = undefined; | ||
| 234 | showFeedbackMessage(messageName); | ||
| 235 | } | ||
| 236 | }); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | function hideAccountNameValidityFeedback(messageName) { | ||
| 241 | for (let i = 0; i < messages.length; i++) { | ||
| 242 | if (messages[i] !== messageName) | ||
| 243 | $("#" + messages[i]).hide(); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | function showAccountNameValidityFeedback(accountState) { | ||
| 248 | switch (accountState) { | ||
| 249 | case AccountState.Valid: | ||
| 250 | showFeedbackMessage("accountValid"); | ||
| 251 | break; | ||
| 252 | case AccountState.Invalid: | ||
| 253 | showFeedbackMessage("accountInvalid"); | ||
| 254 | break; | ||
| 255 | case AccountState.Unknown: | ||
| 256 | showFeedbackMessage("accountUnknown"); | ||
| 257 | break; | ||
| 258 | case AccountState.Resolving: | ||
| 259 | showFeedbackMessage("resolvingAccount"); | ||
| 260 | break; | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 264 | function useADAuthentication(accountName) | ||
| 265 | { | ||
| 266 | const groupName = notNull(accountName); | ||
| 267 | |||
| 268 | // Just deriving if group is AD based by delimiters in group name | ||
| 269 | if (groupName.length > 0) | ||
| 270 | return (groupName.indexOf("\\") > 0 || groupName.indexOf("@@") > 0 || groupName.startsWith("S-")); | ||
| 271 | |||
| 272 | return true; // Default to AD group | ||
| 273 | } | ||
| 274 | |||
| 275 | $(viewModel).on("pageRecordsQueried", function (event, records) { | ||
| 276 | // Add a virtual observable field called "AccountName" that will resolve SID values to account names | ||
| 277 | for (var i = 0; i < records.length; i++) { | ||
| 278 | records[i].AccountName = ko.observable("..."); | ||
| 279 | lookupAccountName(records[i]); | ||
| 280 | } | ||
| 281 | }); | ||
| 282 | |||
| 283 | $(viewModel).on("newRecord", function (event, newRecord) { | ||
| 284 | // Make sure new records get virtual fields | ||
| 285 | newRecord.AccountName = ko.observable(""); | ||
| 286 | }); | ||
| 287 | |||
| 288 | $(viewModel).on("derivingObservableRecord", function (event, observableRecord) { | ||
| 289 | // Load human readable account name into Name field | ||
| 290 | if (observableRecord.AccountName) | ||
| 291 | observableRecord.Name(observableRecord.AccountName()); | ||
| 292 | |||
| 293 | // Create a virtual UseDBAuthentication field | ||
| 294 | observableRecord.UseDBAuthentication = ko.observable(!useADAuthentication(notNull(observableRecord.Name()))); | ||
| 295 | }); | ||
| 296 | |||
| 297 | $(viewModel).on("derivingJSRecord", function (event, observableRecord, promises) { | ||
| 298 | const isADGroup = !observableRecord.UseDBAuthentication(); | ||
| 299 | |||
| 300 | // Remove virtual observable fields before serialization for record updates | ||
| 301 | delete observableRecord.UseDBAuthentication; | ||
| 302 | delete observableRecord.AccountName; | ||
| 303 | |||
| 304 | // Push promise to convert group name back to SID before updating record | ||
| 305 | if (viewModel.dataHubIsConnected() && isADGroup) { | ||
| 306 | promises.push($.Deferred(function (deferred) { | ||
| 307 | securityHub.groupNameToSID(observableRecord.Name()).done(function (sid) { | ||
| 308 | observableRecord.Name(sid); | ||
| 309 | deferred.resolve(); | ||
| 310 | }); | ||
| 311 | }).promise()); | ||
| 312 | } | ||
| 313 | }); | ||
| 314 | |||
| 315 | function openRolesEditor(record) { | ||
| 316 | $("#currentGroupID").text(record.ID); | ||
| 317 | $("#currentGroupName").text(record.AccountName()); | ||
| 318 | $("#editRolesDialog input:checkbox").prop("checked", false); | ||
| 319 | |||
| 320 | if (viewModel.dataHubIsConnected()) { | ||
| 321 | securityHub.queryApplicationRoles().done(function (roles) { | ||
| 322 | var evaluations = roles.length; | ||
| 323 | |||
| 324 | if (evaluations > 0) | ||
| 325 | $("#loadingRoles").show(); | ||
| 326 | |||
| 327 | for (var i = 0; i < roles.length; i++) { | ||
| 328 | securityHub.groupIsInRole(record.ID, roles[i].ID).done(function (roleID) { | ||
| 329 | return function (result) { | ||
| 330 | $("#" + roleID).prop("checked", result); | ||
| 331 | |||
| 332 | evaluations--; | ||
| 333 | |||
| 334 | if (evaluations <= 0) | ||
| 335 | $("#loadingRoles").hide(); | ||
| 336 | }; | ||
| 337 | }(roles[i].ID)); | ||
| 338 | } | ||
| 339 | $("#editRolesDialog").modal("show"); | ||
| 340 | }); | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | $(function () { | ||
| 345 | $("#editRolesDialog").modal({ show: false, backdrop: "static" }); | ||
| 346 | |||
| 347 | $("#saveRolesButton").click(function () { | ||
| 348 | if (viewModel.dataHubIsConnected()) { | ||
| 349 | const groupID = $("#currentGroupID").text(); | ||
| 350 | const roles = $("#editRolesDialog input:checkbox"); | ||
| 351 | |||
| 352 | for (var i = 0; i < roles.length; i++) { | ||
| 353 | const element = $(roles[i]); | ||
| 354 | const roleID = element.attr("id"); | ||
| 355 | |||
| 356 | if (element.prop("checked")) { | ||
| 357 | securityHub.addGroupToRole(groupID, roleID).fail(function (error) { | ||
| 358 | showErrorMessage(error); | ||
| 359 | }); | ||
| 360 | } else { | ||
| 361 | securityHub.removeGroupFromRole(groupID, roleID).fail(function (error) { | ||
| 362 | showErrorMessage(error); | ||
| 363 | }); | ||
| 364 | } | ||
| 365 | } | ||
| 366 | } | ||
| 367 | }); | ||
| 368 | }); | ||
| 369 | </script> | ||
| 370 | } | ||
| @@ -0,0 +1,381 @@ | |||
| 1 | @******************************************************************************************************* | ||
| 2 | // Users.cshtml - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 03/03/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //*****************************************************************************************************@ | ||
| 23 | @using GSF.Data.Model | ||
| 24 | @using GSF.Security.Model | ||
| 25 | @using GSF.Web.Model | ||
| 26 | @section StyleSheets{ | ||
| 27 | <style> | ||
| 28 | .account-feedback-wrapper { | ||
| 29 | position: relative; | ||
| 30 | display: table | ||
| 31 | } | ||
| 32 | |||
| 33 | .account-feedback-message { | ||
| 34 | position: absolute; | ||
| 35 | vertical-align: middle; | ||
| 36 | display: table-cell; | ||
| 37 | left: 10px; | ||
| 38 | width: 425px; | ||
| 39 | display: none | ||
| 40 | } | ||
| 41 | |||
| 42 | .account-feedback-icon { | ||
| 43 | font-size: large; | ||
| 44 | vertical-align: middle; | ||
| 45 | margin-top: -5px | ||
| 46 | } | ||
| 47 | </style> | ||
| 48 | } | ||
| 49 | @{ | ||
| 50 | DataContext dataContext = Model.DataContext; | ||
| 51 | |||
| 52 | // Define column headers, use null for field name to make column non-sortable | ||
| 53 | ViewBag.HeaderColumns = new[] | ||
| 54 | { // { "Field", "Label", "Classes" } | ||
| 55 | new[] { null, "User Name", "text-center valign-middle" }, | ||
| 56 | new[] { "UseADAuthentication", "Is AD User", "text-center" }, | ||
| 57 | new[] { "CreatedOn", "Date Added", "text-center" }, | ||
| 58 | new[] { null, "User Roles", "text-center valign-middle" } | ||
| 59 | }; | ||
| 60 | |||
| 61 | ViewBag.BodyRows = BodyRows().ToString(); | ||
| 62 | ViewBag.AddNewEditDialog = AddNewEditDialog(dataContext).ToString(); | ||
| 63 | } | ||
| 64 | @helper BodyRows() | ||
| 65 | { | ||
| 66 | <td width="65%" class="text-center valign-middle" nowrap><button type="button" class="btn btn-link" data-bind="text: AccountName(), attr: {title: Name}, click: $parent.viewPageRecord"></button></td> | ||
| 67 | <td width="10%" class="text-center valign-middle"><input type="checkbox" id="checkEnabled" data-bind="checked: UseADAuthentication" disabled></td> | ||
| 68 | <td width="10%" class="text-center valign-middle" data-bind="text: CreatedOn.formatDate(DateFormat)"></td> | ||
| 69 | <td width="10%" class="text-center valign-middle"><button type="button" class="btn btn-default btn-sm" data-bind="click: openRolesEditor.bind($data), enable: $parent.canEdit() && $parent.dataHubIsConnected()">Roles...</button></td> | ||
| 70 | <td width="5%" class="text-center valign-middle" nowrap> | ||
| 71 | <button type="button" class="btn btn-xs" data-bind="click: $parent.editPageRecord, enable: $parent.canEdit() && $parent.dataHubIsConnected()"><span class="glyphicon glyphicon-pencil"></span></button> | ||
| 72 | <button type="button" class="btn btn-xs" data-bind="click: $parent.removePageRecord, enable: $parent.canDelete() && $parent.dataHubIsConnected()"><span class="glyphicon glyphicon-remove"></span></button> | ||
| 73 | </td> | ||
| 74 | } | ||
| 75 | @helper AccountValidationFeedback() | ||
| 76 | { | ||
| 77 | <div class="pull-right account-feedback-wrapper"> | ||
| 78 | <span id="resolvingAccount" class="account-feedback-message"><span class="glyphicon glyphicon-refresh glyphicon-spin account-feedback-icon"></span> <em class="small">Resolving account details...</em></span> | ||
| 79 | <span id="accountValid" class="account-feedback-message"><span class="glyphicon glyphicon-ok-circle account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'green'}"></span> <em class="small">Resolved account name</em></span> | ||
| 80 | <span id="accountInvalid" class="account-feedback-message"><span class="glyphicon glyphicon-remove-circle account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'red'}"></span> <em class="small">Cannot resolve account name</em></span> | ||
| 81 | <span id="accountUnknown" class="account-feedback-message"><span class="glyphicon glyphicon-warning-sign account-feedback-icon" data-bind="style: {color: $root.recordMode()===RecordMode.View ? 'gray' : 'orange'}"></span> <em class="small">Valid account name is not a user or Active Directory access is limited</em></span> | ||
| 82 | </div> | ||
| 83 | } | ||
| 84 | @helper AddNewEditDialog(DataContext dataContext) | ||
| 85 | { | ||
| 86 | <div class="col-md-12"> | ||
| 87 | @Html.Raw(dataContext.AddInputField<UserAccount>("Name", fieldLabel: $"Name{AccountValidationFeedback().ToString().Trim()}", customDataBinding: "attr: {'acct-valid': validateAccount($data)}", initialFocus: true)) | ||
| 88 | <div class="panel panel-default" data-bind="css: {'panel-readonly': $root.recordMode()===RecordMode.View, 'panel-primary': UseDBAuthentication}"> | ||
| 89 | <div class="panel-heading"> | ||
| 90 | @* Adding actual UseADAuthentication field check-box, but hidden, to pick-up any model based field initializers *@ | ||
| 91 | <div style="display: none"> | ||
| 92 | @Html.Raw(dataContext.AddCheckBoxField<UserAccount>("UseADAuthentication")) | ||
| 93 | </div> | ||
| 94 | @* Adding radio buttons bound to virtual UseDBAuthentication that models negated state of UseADAuthentication field for simplified dependency field initialization *@ | ||
| 95 | <div class="row"> | ||
| 96 | <div class="col-md-8"> | ||
| 97 | <div class="row"> | ||
| 98 | <div class="col-md-4"> | ||
| 99 | <label class="radio-inline"><input type="radio" name="radioUseDBAuthentication" data-bind="checkedValue: false, checked: UseDBAuthentication, attr: {'disabled': ($root.recordMode()===RecordMode.View ? true : undefined)}"> Active Directory Group</label> | ||
| 100 | </div> | ||
| 101 | <div class="col-md-8"> | ||
| 102 | <label class="radio-inline"><input type="radio" name="radioUseDBAuthentication" data-bind="checkedValue: true, checked: UseDBAuthentication, attr: {'disabled': ($root.recordMode()===RecordMode.View ? true : undefined)}"> Database Group</label> | ||
| 103 | </div> | ||
| 104 | </div> | ||
| 105 | </div> | ||
| 106 | <div class="col-md-4"> | ||
| 107 | </div> | ||
| 108 | </div> | ||
| 109 | </div> | ||
| 110 | <div class="panel-body" data-bind="visible: UseDBAuthentication"> | ||
| 111 | <div class="row"> | ||
| 112 | <div class="col-md-6"> | ||
| 113 | @Html.Raw(dataContext.AddInputField<UserAccount>("Password", "password", dependencyFieldName: "UseDBAuthentication")) | ||
| 114 | @Html.Raw(dataContext.AddInputField<UserAccount>("FirstName", dependencyFieldName: "UseDBAuthentication")) | ||
| 115 | @Html.Raw(dataContext.AddInputField<UserAccount>("LastName", dependencyFieldName: "UseDBAuthentication")) | ||
| 116 | </div> | ||
| 117 | <div class="col-md-6"> | ||
| 118 | @Html.Raw(dataContext.AddInputField<UserAccount>("Phone", dependencyFieldName: "UseDBAuthentication")) | ||
| 119 | @Html.Raw(dataContext.AddInputField<UserAccount>("Email", dependencyFieldName: "UseDBAuthentication")) | ||
| 120 | @Html.Raw(dataContext.AddDateField<UserAccount>("ChangePasswordOn", dependencyFieldName: "UseDBAuthentication")) | ||
| 121 | @{ | ||
| 122 | Model.AddPageDefinedFieldInitialization("Password", "\"\"", | ||
| 123 | /* RegEx Pattern: */ Model.Global.PasswordRequirementsRegex, | ||
| 124 | /* Error Message: */ Model.Global.PasswordRequirementsError); | ||
| 125 | Model.AddPageDefinedFieldInitialization("ChangePasswordOn", "(new Date()).addDays(90)"); | ||
| 126 | } | ||
| 127 | </div> | ||
| 128 | </div> | ||
| 129 | </div> | ||
| 130 | </div> | ||
| 131 | <div class="pull-right"> | ||
| 132 | @Html.Raw(dataContext.AddCheckBoxField<UserAccount>("LockedOut")) | ||
| 133 | </div> | ||
| 134 | </div> | ||
| 135 | } | ||
| 136 | @Html.Partial("PagedViewModel") | ||
| 137 | <div id="editRolesDialog" class="modal fade" role="dialog"> | ||
| 138 | <div class="modal-dialog"> | ||
| 139 | <div class="modal-content"> | ||
| 140 | <div class="modal-header"> | ||
| 141 | <button type="button" class="close" data-dismiss="modal">×</button> | ||
| 142 | <h4 class="modal-title">Edit Roles for <span id="currentUserName"></span>:</h4> | ||
| 143 | </div> | ||
| 144 | <div class="modal-body"> | ||
| 145 | <div class="modal-body auto-height text-center"> | ||
| 146 | <form role="form"> | ||
| 147 | <input type="hidden" id="currentUserID" /> | ||
| 148 | <div class="center-block"> | ||
| 149 | <div id="loadingRoles">Loading role assignments <span class="glyphicon glyphicon-refresh glyphicon-spin"></span><hr class="full-break" /></div> | ||
| 150 | <table class="table-condensed table-center"> | ||
| 151 | @foreach (ApplicationRole role in dataContext.Table<ApplicationRole>().QueryRecords("Name", new RecordRestriction("NodeID={0}", Model.Global.NodeID))) { | ||
| 152 | <tr> | ||
| 153 | <td class="text-left valign-middle"> | ||
| 154 | <div class="checkbox" style="margin: 0"> | ||
| 155 | <label for="@role.ID"><input type="checkbox" id="@role.ID"> @role.Description (@role.Name)</label> | ||
| 156 | </div> | ||
| 157 | </td> | ||
| 158 | </tr> | ||
| 159 | } | ||
| 160 | </table> | ||
| 161 | <br /> | ||
| 162 | <div class="text-center"> | ||
| 163 | <a href="@Url.Action("Help", "Main")#Roles" target="_blank">@Model.Global.ApplicationName Role Definitions</a> | ||
| 164 | </div> | ||
| 165 | </div> | ||
| 166 | </form> | ||
| 167 | </div> | ||
| 168 | </div> | ||
| 169 | <div class="modal-footer"> | ||
| 170 | <button type="submit" class="btn btn-primary" data-dismiss="modal" id="saveRolesButton" data-bind="enable: canEdit()">Save</button> | ||
| 171 | <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> | ||
| 172 | </div> | ||
| 173 | </div> | ||
| 174 | </div> | ||
| 175 | </div> | ||
| 176 | @section Scripts { | ||
| 177 | <script> | ||
| 178 | "use strict"; | ||
| 179 | |||
| 180 | @Html.Raw(dataContext.RenderViewModelConfiguration<UserAccount>(ViewBag)) | ||
| 181 | var AccountState = { | ||
| 182 | Invalid: 0, | ||
| 183 | Valid: 1, | ||
| 184 | Unknown: 2, | ||
| 185 | Resolving: 3 | ||
| 186 | } | ||
| 187 | |||
| 188 | function lookupAccountName(record) { | ||
| 189 | if (viewModel.dataHubIsConnected()) { | ||
| 190 | securityHub.sidToAccountName(record.Name).done(function(accountName) { | ||
| 191 | record.AccountName(accountName); | ||
| 192 | }); | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | function validateAccount(observableRecord) { | ||
| 197 | showAccountNameValidityFeedback(AccountState.Resolving); | ||
| 198 | |||
| 199 | if (observableRecord.UseDBAuthentication == null || observableRecord.UseDBAuthentication()) { | ||
| 200 | hideAccountNameValidityFeedback(); | ||
| 201 | } else { | ||
| 202 | if (viewModel.dataHubIsConnected()) { | ||
| 203 | const accountName = observableRecord.Name(); | ||
| 204 | |||
| 205 | if (isEmpty(accountName)) { | ||
| 206 | showAccountNameValidityFeedback(AccountState.Invalid); | ||
| 207 | } else { | ||
| 208 | securityHub.userNameToSID(accountName).done(function(sid) { | ||
| 209 | if (accountName !== sid && accountName.countOccurrences("\\") < 2) { | ||
| 210 | securityHub.isUserSID(sid).done(function(isUser) { | ||
| 211 | showAccountNameValidityFeedback(isUser ? AccountState.Valid : AccountState.Unknown); | ||
| 212 | }); | ||
| 213 | } else { | ||
| 214 | showAccountNameValidityFeedback(AccountState.Invalid); | ||
| 215 | } | ||
| 216 | }); | ||
| 217 | } | ||
| 218 | } else { | ||
| 219 | hideAccountNameValidityFeedback(); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | return undefined; | ||
| 224 | } | ||
| 225 | |||
| 226 | var messages = []; | ||
| 227 | var nextMessageName = undefined; | ||
| 228 | var showingMessage = false; | ||
| 229 | messages.push("resolvingAccount"); | ||
| 230 | messages.push("accountValid"); | ||
| 231 | messages.push("accountInvalid"); | ||
| 232 | messages.push("accountUnknown"); | ||
| 233 | |||
| 234 | function showFeedbackMessage(messageName) { | ||
| 235 | if (showingMessage) { | ||
| 236 | nextMessageName = messageName; | ||
| 237 | } else { | ||
| 238 | showingMessage = true; | ||
| 239 | |||
| 240 | hideAccountNameValidityFeedback(messageName); | ||
| 241 | |||
| 242 | if (messageName == null) | ||
| 243 | showingMessage = false; | ||
| 244 | else | ||
| 245 | $("#" + messageName).fadeIn(function () { | ||
| 246 | showingMessage = false; | ||
| 247 | |||
| 248 | if (nextMessageName != undefined) { | ||
| 249 | messageName = nextMessageName; | ||
| 250 | nextMessageName = undefined; | ||
| 251 | showFeedbackMessage(messageName); | ||
| 252 | } | ||
| 253 | }); | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | function hideAccountNameValidityFeedback(messageName) { | ||
| 258 | for (let i = 0; i < messages.length; i++) { | ||
| 259 | if (messages[i] !== messageName) | ||
| 260 | $("#" + messages[i]).hide(); | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 264 | function showAccountNameValidityFeedback(accountState) { | ||
| 265 | switch (accountState) { | ||
| 266 | case AccountState.Valid: | ||
| 267 | showFeedbackMessage("accountValid"); | ||
| 268 | break; | ||
| 269 | case AccountState.Invalid: | ||
| 270 | showFeedbackMessage("accountInvalid"); | ||
| 271 | break; | ||
| 272 | case AccountState.Unknown: | ||
| 273 | showFeedbackMessage("accountUnknown"); | ||
| 274 | break; | ||
| 275 | case AccountState.Resolving: | ||
| 276 | showFeedbackMessage("resolvingAccount"); | ||
| 277 | break; | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | $(viewModel).on("pageRecordsQueried", function(event, records) { | ||
| 282 | // Add a virtual observable field called "AccountName" that will resolve SID values to account names | ||
| 283 | for (var i = 0; i < records.length; i++) { | ||
| 284 | records[i].AccountName = ko.observable("..."); | ||
| 285 | lookupAccountName(records[i]); | ||
| 286 | } | ||
| 287 | }); | ||
| 288 | |||
| 289 | $(viewModel).on("newRecord", function(event, newRecord) { | ||
| 290 | // Make sure new records get virtual field | ||
| 291 | newRecord.AccountName = ko.observable(""); | ||
| 292 | }); | ||
| 293 | |||
| 294 | $(viewModel).on("derivingObservableRecord", function(event, observableRecord) { | ||
| 295 | // Create a virtual UseDBAuthentication field that is negated state of actual UseADAuthentication field | ||
| 296 | observableRecord.UseDBAuthentication = ko.observable(!observableRecord.UseADAuthentication()); | ||
| 297 | |||
| 298 | // Load human readable account name into Name field | ||
| 299 | if (observableRecord.AccountName) | ||
| 300 | observableRecord.Name(observableRecord.AccountName()); | ||
| 301 | |||
| 302 | @* Cannot decrypt a password hash, have to clear it and force re-entry *@ | ||
| 303 | if (!observableRecord.UseADAuthentication()) | ||
| 304 | observableRecord.Password(null); | ||
| 305 | }); | ||
| 306 | |||
| 307 | $(viewModel).on("derivingJSRecord", function(event, observableRecord, promises) { | ||
| 308 | // Set state of actual field to be negated state of virtual field | ||
| 309 | observableRecord.UseADAuthentication(!observableRecord.UseDBAuthentication()); | ||
| 310 | |||
| 311 | // Remove virtual observable fields before serialization for record updates | ||
| 312 | delete observableRecord.UseDBAuthentication; | ||
| 313 | delete observableRecord.AccountName; | ||
| 314 | |||
| 315 | // Push promise to convert user name back to SID before updating record | ||
| 316 | if (viewModel.dataHubIsConnected() && observableRecord.UseADAuthentication()) { | ||
| 317 | promises.push($.Deferred(function(deferred) { | ||
| 318 | securityHub.userNameToSID(observableRecord.Name()).done(function(sid) { | ||
| 319 | observableRecord.Name(sid); | ||
| 320 | deferred.resolve(); | ||
| 321 | }); | ||
| 322 | }).promise()); | ||
| 323 | } | ||
| 324 | }); | ||
| 325 | |||
| 326 | function openRolesEditor(record) { | ||
| 327 | $("#currentUserID").text(record.ID); | ||
| 328 | $("#currentUserName").text(record.AccountName()); | ||
| 329 | $("#editRolesDialog input:checkbox").prop("checked", false); | ||
| 330 | |||
| 331 | if (viewModel.dataHubIsConnected()) { | ||
| 332 | securityHub.queryApplicationRoles().done(function(roles) { | ||
| 333 | var evaluations = roles.length; | ||
| 334 | |||
| 335 | if (evaluations > 0) | ||
| 336 | $("#loadingRoles").show(); | ||
| 337 | |||
| 338 | for (var i = 0; i < roles.length; i++) { | ||
| 339 | securityHub.userIsInRole(record.ID, roles[i].ID).done(function(roleID) { | ||
| 340 | return function(result) { | ||
| 341 | $("#" + roleID).prop("checked", result); | ||
| 342 | |||
| 343 | evaluations--; | ||
| 344 | |||
| 345 | if (evaluations <= 0) | ||
| 346 | $("#loadingRoles").hide(); | ||
| 347 | }; | ||
| 348 | }(roles[i].ID)); | ||
| 349 | } | ||
| 350 | $("#editRolesDialog").modal("show"); | ||
| 351 | }); | ||
| 352 | } | ||
| 353 | } | ||
| 354 | |||
| 355 | $(function() { | ||
| 356 | $("#editRolesDialog").modal({ show: false, backdrop: "static" }); | ||
| 357 | |||
| 358 | $("#saveRolesButton").click(function() { | ||
| 359 | if (viewModel.dataHubIsConnected()) { | ||
| 360 | const userID = $("#currentUserID").text(); | ||
| 361 | const roles = $("#editRolesDialog input:checkbox"); | ||
| 362 | |||
| 363 | for (var i = 0; i < roles.length; i++) { | ||
| 364 | const element = $(roles[i]); | ||
| 365 | const roleID = element.attr("id"); | ||
| 366 | |||
| 367 | if (element.prop("checked")) { | ||
| 368 | securityHub.addUserToRole(userID, roleID).fail(function(error) { | ||
| 369 | showErrorMessage(error); | ||
| 370 | }); | ||
| 371 | } else { | ||
| 372 | securityHub.removeUserFromRole(userID, roleID).fail(function(error) { | ||
| 373 | showErrorMessage(error); | ||
| 374 | }); | ||
| 375 | } | ||
| 376 | } | ||
| 377 | } | ||
| 378 | }); | ||
| 379 | }); | ||
| 380 | </script> | ||
| 381 | } | ||
| @@ -0,0 +1,35 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // NamespaceDoc.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://www.opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 03/14/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System.Runtime.CompilerServices; | ||
| 25 | |||
| 26 | namespace GSF.Web.Templating | ||
| 27 | { | ||
| 28 | /// <summary> | ||
| 29 | /// Contains classes that provide template based functionality using Razor syntax. | ||
| 30 | /// </summary> | ||
| 31 | [CompilerGenerated] | ||
| 32 | class NamespaceDoc | ||
| 33 | { | ||
| 34 | } | ||
| 35 | } | ||
| @@ -0,0 +1,461 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // RazorView.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 01/13/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.Collections.Generic; | ||
| 26 | using System.IO; | ||
| 27 | using System.Net.Http; | ||
| 28 | using System.Reflection; | ||
| 29 | using System.Threading.Tasks; | ||
| 30 | using System.Web; | ||
| 31 | using System.Web.Hosting; | ||
| 32 | using GSF.Collections; | ||
| 33 | using GSF.Configuration; | ||
| 34 | using GSF.IO; | ||
| 35 | using GSF.Web.Model; | ||
| 36 | using RazorEngine; | ||
| 37 | using RazorEngine.Configuration; | ||
| 38 | using RazorEngine.Templating; | ||
| 39 | |||
| 40 | // ReSharper disable StaticMemberInGenericType | ||
| 41 | namespace GSF.Web.Templating | ||
| 42 | { | ||
| 43 | /// <summary> | ||
| 44 | /// Defines resolutions modes for Razor view template files. | ||
| 45 | /// </summary> | ||
| 46 | public enum RazorViewResolutionMode | ||
| 47 | { | ||
| 48 | /// <summary> | ||
| 49 | /// Resolves template files from a path. | ||
| 50 | /// </summary> | ||
| 51 | ResolvePath, | ||
| 52 | /// <summary> | ||
| 53 | /// Resolves template files from a path and watches for changes. | ||
| 54 | /// </summary> | ||
| 55 | /// <remarks> | ||
| 56 | /// This should only be used in debug mode. | ||
| 57 | /// </remarks> | ||
| 58 | WatchingResolvePath, | ||
| 59 | /// <summary> | ||
| 60 | /// Resolves template files from an embedded resource. | ||
| 61 | /// </summary> | ||
| 62 | EmbeddedResource | ||
| 63 | } | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Defines a language constraint for a <see cref="RazorView{TLanguage}"/>. | ||
| 67 | /// </summary> | ||
| 68 | public abstract class LanguageContraint | ||
| 69 | { | ||
| 70 | /// <summary> | ||
| 71 | /// Gets target language for the <see cref="LanguageContraint"/> implementation. | ||
| 72 | /// </summary> | ||
| 73 | public abstract Language TargetLanguage | ||
| 74 | { | ||
| 75 | get; | ||
| 76 | } | ||
| 77 | |||
| 78 | /// <summary> | ||
| 79 | /// Gets resolution mode of <see cref="RazorView{TLanguage}"/>. | ||
| 80 | /// </summary> | ||
| 81 | public virtual RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.ResolvePath; | ||
| 82 | } | ||
| 83 | |||
| 84 | /// <summary> | ||
| 85 | /// Defines a C# based language constraint. | ||
| 86 | /// </summary> | ||
| 87 | public class CSharp : LanguageContraint | ||
| 88 | { | ||
| 89 | /// <summary> | ||
| 90 | /// Gets target language for the <see cref="LanguageContraint"/> implementation - C#. | ||
| 91 | /// </summary> | ||
| 92 | public override Language TargetLanguage => Language.CSharp; | ||
| 93 | } | ||
| 94 | |||
| 95 | /// <summary> | ||
| 96 | /// Defines a C# based language constraint in debug mode. | ||
| 97 | /// </summary> | ||
| 98 | public class CSharpDebug : CSharp | ||
| 99 | { | ||
| 100 | /// <summary> | ||
| 101 | /// Gets resolution mode of <see cref="RazorView{TLanguage}"/> - watching resolve path for debugging operations. | ||
| 102 | /// </summary> | ||
| 103 | public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.WatchingResolvePath; | ||
| 104 | } | ||
| 105 | |||
| 106 | /// <summary> | ||
| 107 | /// Defines a C# based language constraint for embedded resources. | ||
| 108 | /// </summary> | ||
| 109 | public class CSharpEmbeddedResource : CSharp | ||
| 110 | { | ||
| 111 | /// <summary> | ||
| 112 | /// Gets resolution mode of <see cref="RazorView{TLanguage}"/> - embedded resources. | ||
| 113 | /// </summary> | ||
| 114 | public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.EmbeddedResource; | ||
| 115 | } | ||
| 116 | |||
| 117 | /// <summary> | ||
| 118 | /// Defines a Visual Basic based language constraint. | ||
| 119 | /// </summary> | ||
| 120 | public class VisualBasic : LanguageContraint | ||
| 121 | { | ||
| 122 | /// <summary> | ||
| 123 | /// Gets target language for the <see cref="LanguageContraint"/> implementation - Visual Basic. | ||
| 124 | /// </summary> | ||
| 125 | public override Language TargetLanguage => Language.VisualBasic; | ||
| 126 | } | ||
| 127 | |||
| 128 | /// <summary> | ||
| 129 | /// Defines a Visual Basic based language constraint in debug mode. | ||
| 130 | /// </summary> | ||
| 131 | public class VisualBasicDebug : VisualBasic | ||
| 132 | { | ||
| 133 | /// <summary> | ||
| 134 | /// Gets resolution mode of <see cref="RazorView{TLanguage}"/> - watching resolve path for debugging operations. | ||
| 135 | /// </summary> | ||
| 136 | public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.WatchingResolvePath; | ||
| 137 | } | ||
| 138 | |||
| 139 | /// <summary> | ||
| 140 | /// Defines a Visual Basic based language constraint for embedded resources. | ||
| 141 | /// </summary> | ||
| 142 | public class VisualBasicEmbeddedResource : CSharp | ||
| 143 | { | ||
| 144 | /// <summary> | ||
| 145 | /// Gets resolution mode of <see cref="RazorView{TLanguage}"/> - embedded resources. | ||
| 146 | /// </summary> | ||
| 147 | public override RazorViewResolutionMode ResolutionMode => RazorViewResolutionMode.EmbeddedResource; | ||
| 148 | } | ||
| 149 | |||
| 150 | /// <summary> | ||
| 151 | /// Defines a view class for data context based Razor template implementations. | ||
| 152 | /// </summary> | ||
| 153 | /// <typeparam name="TLanguage"><see cref="LanguageContraint"/> type to use for Razor views.</typeparam> | ||
| 154 | public class RazorView<TLanguage> where TLanguage : LanguageContraint, new() | ||
| 155 | { | ||
| 156 | #region [ Members ] | ||
| 157 | |||
| 158 | // Fields | ||
| 159 | private readonly DynamicViewBag m_viewBag = new DynamicViewBag(); | ||
| 160 | private Dictionary<string, string> m_parameters; | ||
| 161 | |||
| 162 | #endregion | ||
| 163 | |||
| 164 | #region [ Constructors ] | ||
| 165 | |||
| 166 | /// <summary> | ||
| 167 | /// Creates a new <see cref="RazorView{TLanguage}"/>. | ||
| 168 | /// </summary> | ||
| 169 | public RazorView() | ||
| 170 | { | ||
| 171 | } | ||
| 172 | |||
| 173 | /// <summary> | ||
| 174 | /// Creates a new <see cref="RazorView{TLanguage}"/> with the specified exception handler. | ||
| 175 | /// </summary> | ||
| 176 | /// <param name="templateName">Name of template file, typically a .cshtml or .vbhtml file.</param> | ||
| 177 | /// <param name="exceptionHandler">Delegate to handle exceptions, if any.</param> | ||
| 178 | public RazorView(string templateName, Action<Exception> exceptionHandler = null) | ||
| 179 | { | ||
| 180 | TemplateName = templateName; | ||
| 181 | ExceptionHandler = exceptionHandler; | ||
| 182 | } | ||
| 183 | |||
| 184 | /// <summary> | ||
| 185 | /// Creates a new <see cref="RazorView{TLanguage}"/> with the specified parameters. | ||
| 186 | /// </summary> | ||
| 187 | /// <param name="templateName">Name of template file, typically a .cshtml or .vbhtml file.</param> | ||
| 188 | /// <param name="model">Reference to model to use when rendering template.</param> | ||
| 189 | /// <param name="modelType">Type of <paramref name="model"/>.</param> | ||
| 190 | /// <param name="exceptionHandler">Delegate to handle exceptions, if any.</param> | ||
| 191 | public RazorView(string templateName, object model, Type modelType, Action<Exception> exceptionHandler = null) : this(templateName, exceptionHandler) | ||
| 192 | { | ||
| 193 | Model = model; | ||
| 194 | ModelType = modelType; | ||
| 195 | } | ||
| 196 | |||
| 197 | #endregion | ||
| 198 | |||
| 199 | #region [ Properties ] | ||
| 200 | |||
| 201 | /// <summary> | ||
| 202 | /// Gets or sets name of template file. | ||
| 203 | /// </summary> | ||
| 204 | public string TemplateName | ||
| 205 | { | ||
| 206 | get; set; | ||
| 207 | } | ||
| 208 | |||
| 209 | /// <summary> | ||
| 210 | /// Gets or sets reference to model to use when rendering template. | ||
| 211 | /// </summary> | ||
| 212 | public object Model | ||
| 213 | { | ||
| 214 | get; set; | ||
| 215 | } | ||
| 216 | |||
| 217 | /// <summary> | ||
| 218 | /// Gets or sets type of <see cref="Model"/>. | ||
| 219 | /// </summary> | ||
| 220 | public Type ModelType | ||
| 221 | { | ||
| 222 | get; set; | ||
| 223 | } | ||
| 224 | |||
| 225 | /// <summary> | ||
| 226 | /// Gets or sets delegate used to handle exceptions. | ||
| 227 | /// </summary> | ||
| 228 | public Action<Exception> ExceptionHandler | ||
| 229 | { | ||
| 230 | get; set; | ||
| 231 | } | ||
| 232 | |||
| 233 | /// <summary> | ||
| 234 | /// Gets reference to view bag used when rendering template. | ||
| 235 | /// </summary> | ||
| 236 | public dynamic ViewBag => m_viewBag; | ||
| 237 | |||
| 238 | /// <summary> | ||
| 239 | /// Gets query string parameter specified by <paramref name="key"/>. | ||
| 240 | /// </summary> | ||
| 241 | /// <param name="key">Name of query string parameter to retrieve.</param> | ||
| 242 | /// <returns>Query string parameter specified by <paramref name="key"/>.</returns> | ||
| 243 | public string this[string key] => Parameters[key]; | ||
| 244 | |||
| 245 | /// <summary> | ||
| 246 | /// Gets a dictionary of query string parameters passed to rendered view. | ||
| 247 | /// </summary> | ||
| 248 | public Dictionary<string, string> Parameters | ||
| 249 | { | ||
| 250 | get | ||
| 251 | { | ||
| 252 | if ((object)m_parameters == null) | ||
| 253 | { | ||
| 254 | HttpRequestMessage request = ViewBag.Request; | ||
| 255 | m_parameters = HttpUtility.ParseQueryString(request.RequestUri.Query).ToDictionary(); | ||
| 256 | } | ||
| 257 | |||
| 258 | return m_parameters; | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | #endregion | ||
| 263 | |||
| 264 | #region [ Methods ] | ||
| 265 | |||
| 266 | /// <summary> | ||
| 267 | /// Compiles and executes view template. | ||
| 268 | /// </summary> | ||
| 269 | /// <returns>Rendered result.</returns> | ||
| 270 | public string Execute() | ||
| 271 | { | ||
| 272 | using (DataContext<TLanguage> dataContext = new DataContext<TLanguage>(exceptionHandler: ExceptionHandler)) | ||
| 273 | { | ||
| 274 | m_viewBag.AddValue("DataContext", dataContext); | ||
| 275 | return s_engineService.RunCompile(TemplateName, ModelType, Model, m_viewBag); | ||
| 276 | } | ||
| 277 | } | ||
| 278 | |||
| 279 | /// <summary> | ||
| 280 | /// Compiles and executes view template for specified request message and post data. | ||
| 281 | /// </summary> | ||
| 282 | /// <returns>Rendered result.</returns> | ||
| 283 | public string Execute(HttpRequestMessage requestMessage, dynamic postData) | ||
| 284 | { | ||
| 285 | using (DataContext<TLanguage> dataContext = new DataContext<TLanguage>(exceptionHandler: ExceptionHandler)) | ||
| 286 | { | ||
| 287 | m_viewBag.AddValue("DataContext", dataContext); | ||
| 288 | m_viewBag.AddValue("Request", requestMessage); | ||
| 289 | |||
| 290 | if ((object)postData == null) | ||
| 291 | { | ||
| 292 | m_viewBag.AddValue("IsPost", false); | ||
| 293 | } | ||
| 294 | else | ||
| 295 | { | ||
| 296 | m_viewBag.AddValue("IsPost", true); | ||
| 297 | m_viewBag.AddValue("PostData", postData); | ||
| 298 | } | ||
| 299 | |||
| 300 | return s_engineService.RunCompile(TemplateName, ModelType, Model, m_viewBag); | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | /// <summary> | ||
| 305 | /// Asynchronously compiles and executes view template for specified request message and post data. | ||
| 306 | /// </summary> | ||
| 307 | /// <returns>Task that will provide rendered result.</returns> | ||
| 308 | public Task ExecuteAsync(HttpRequestMessage requestMessage, dynamic postData) | ||
| 309 | { | ||
| 310 | return Task.Run(() => Execute(requestMessage, postData)); | ||
| 311 | } | ||
| 312 | |||
| 313 | #endregion | ||
| 314 | |||
| 315 | #region [ Static ] | ||
| 316 | |||
| 317 | // Static Fields | ||
| 318 | private static readonly IRazorEngineService s_engineService; | ||
| 319 | |||
| 320 | /// <summary> | ||
| 321 | /// Defines default settings path for template files. | ||
| 322 | /// </summary> | ||
| 323 | public static readonly string TemplatePath; | ||
| 324 | |||
| 325 | // Static Constructor | ||
| 326 | static RazorView() | ||
| 327 | { | ||
| 328 | TLanguage languageType = new TLanguage(); | ||
| 329 | |||
| 330 | // Get configured template path | ||
| 331 | CategorizedSettingsElementCollection systemSettings = ConfigurationFile.Current.Settings["systemSettings"]; | ||
| 332 | |||
| 333 | switch (languageType.ResolutionMode) | ||
| 334 | { | ||
| 335 | case RazorViewResolutionMode.ResolvePath: | ||
| 336 | case RazorViewResolutionMode.WatchingResolvePath: | ||
| 337 | if (Common.GetApplicationType() == ApplicationType.Web) | ||
| 338 | { | ||
| 339 | systemSettings.Add("TemplatePath", "~/Views/Shared/Templates/", "Path for data context based razor field templates."); | ||
| 340 | TemplatePath = HostingEnvironment.MapPath(systemSettings["TemplatePath"].Value).EnsureEnd('/'); | ||
| 341 | } | ||
| 342 | else | ||
| 343 | { | ||
| 344 | systemSettings.Add("TemplatePath", "wwwroot/Templates/", "Path for data context based razor field templates."); | ||
| 345 | TemplatePath = FilePath.GetAbsolutePath(systemSettings["TemplatePath"].Value); | ||
| 346 | } | ||
| 347 | |||
| 348 | if (languageType.ResolutionMode == RazorViewResolutionMode.ResolvePath) | ||
| 349 | { | ||
| 350 | s_engineService = RazorEngineService.Create(new TemplateServiceConfiguration | ||
| 351 | { | ||
| 352 | Language = languageType.TargetLanguage, | ||
| 353 | TemplateManager = new ResolvePathTemplateManager(new[] { TemplatePath }), | ||
| 354 | Debug = false | ||
| 355 | }); | ||
| 356 | } | ||
| 357 | else | ||
| 358 | { | ||
| 359 | // The watching resolve path template manager should not be used in production since | ||
| 360 | // assemblies cannot be unloaded from an AppDomain. Every time a change to a .cshtml | ||
| 361 | // file is picked up by the watcher it is compiled and loaded into the AppDomain and | ||
| 362 | // the old one cannot be removed (.NET restriction), the net result is a memory leak | ||
| 363 | InvalidatingCachingProvider cachingProvider = new InvalidatingCachingProvider(); | ||
| 364 | |||
| 365 | s_engineService = RazorEngineService.Create(new TemplateServiceConfiguration | ||
| 366 | { | ||
| 367 | Language = languageType.TargetLanguage, | ||
| 368 | CachingProvider = cachingProvider, | ||
| 369 | TemplateManager = new WatchingResolvePathTemplateManager(new[] { TemplatePath }, cachingProvider), | ||
| 370 | Debug = true | ||
| 371 | }); | ||
| 372 | } | ||
| 373 | break; | ||
| 374 | case RazorViewResolutionMode.EmbeddedResource: | ||
| 375 | systemSettings.Add("EmbeddedTemplatePath", "GSF.Web.Templating.Views.", "Embedded name space path for data context based razor field templates."); | ||
| 376 | TemplatePath = systemSettings["EmbeddedTemplatePath"].Value.EnsureEnd('.'); | ||
| 377 | |||
| 378 | s_engineService = RazorEngineService.Create(new TemplateServiceConfiguration | ||
| 379 | { | ||
| 380 | Language = languageType.TargetLanguage, | ||
| 381 | TemplateManager = new DelegateTemplateManager(name => | ||
| 382 | { | ||
| 383 | string resourceName = name; | ||
| 384 | |||
| 385 | if (!resourceName.Contains(".")) | ||
| 386 | resourceName = TemplatePath + name; | ||
| 387 | |||
| 388 | Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); | ||
| 389 | |||
| 390 | if ((object)stream == null) | ||
| 391 | return ""; | ||
| 392 | |||
| 393 | using (StreamReader reader = new StreamReader(stream)) | ||
| 394 | { | ||
| 395 | return reader.ReadToEnd(); | ||
| 396 | } | ||
| 397 | }), | ||
| 398 | Debug = false | ||
| 399 | }); | ||
| 400 | |||
| 401 | |||
| 402 | |||
| 403 | break; | ||
| 404 | default: | ||
| 405 | throw new ArgumentOutOfRangeException(""); | ||
| 406 | } | ||
| 407 | } | ||
| 408 | |||
| 409 | /// <summary> | ||
| 410 | /// Kicks off a task to pre-compile Razor templates. | ||
| 411 | /// </summary> | ||
| 412 | /// <param name="exceptionHandler">Exception handler used to report issues, if any.</param> | ||
| 413 | public static Task PreCompile(Action<Exception> exceptionHandler = null) | ||
| 414 | { | ||
| 415 | return Task.Run(() => | ||
| 416 | { | ||
| 417 | TLanguage languageType = new TLanguage(); | ||
| 418 | |||
| 419 | if (languageType.ResolutionMode == RazorViewResolutionMode.EmbeddedResource) | ||
| 420 | { | ||
| 421 | foreach (string fileName in Assembly.GetExecutingAssembly().GetManifestResourceNames()) | ||
| 422 | { | ||
| 423 | if (fileName.StartsWith(TemplatePath)) | ||
| 424 | { | ||
| 425 | try | ||
| 426 | { | ||
| 427 | s_engineService.Compile(fileName.Substring(TemplatePath.Length)); | ||
| 428 | } | ||
| 429 | catch (Exception ex) | ||
| 430 | { | ||
| 431 | if ((object)exceptionHandler != null) | ||
| 432 | exceptionHandler(new InvalidOperationException($"Failed to pre-compile razor template \"{fileName}\": {ex.Message}", ex)); | ||
| 433 | } | ||
| 434 | } | ||
| 435 | |||
| 436 | } | ||
| 437 | } | ||
| 438 | else | ||
| 439 | { | ||
| 440 | string webRootFolder = FilePath.AddPathSuffix(TemplatePath); | ||
| 441 | string[] razorFiles = FilePath.GetFileList($"{webRootFolder}*.{(languageType.TargetLanguage == Language.CSharp ? "cs" : "vb")}html"); | ||
| 442 | |||
| 443 | foreach (string fileName in razorFiles) | ||
| 444 | { | ||
| 445 | try | ||
| 446 | { | ||
| 447 | s_engineService.Compile(FilePath.GetFileName(fileName)); | ||
| 448 | } | ||
| 449 | catch (Exception ex) | ||
| 450 | { | ||
| 451 | if ((object)exceptionHandler != null) | ||
| 452 | exceptionHandler(new InvalidOperationException($"Failed to pre-compile razor template \"{fileName}\": {ex.Message}", ex)); | ||
| 453 | } | ||
| 454 | } | ||
| 455 | } | ||
| 456 | }); | ||
| 457 | } | ||
| 458 | |||
| 459 | #endregion | ||
| 460 | } | ||
| 461 | } | ||
| @@ -0,0 +1,70 @@ | |||
| 1 | @******************************************************************************************************* | ||
| 2 | // AddCheckBoxField.cshtml - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/01/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //*****************************************************************************************************@ | ||
| 23 | @using RazorEngine.Templating | ||
| 24 | @inherits TemplateBase | ||
| 25 | @{ | ||
| 26 | string fieldName = ViewBag.FieldName; | ||
| 27 | string fieldLabel = ViewBag.FieldLabel; | ||
| 28 | string fieldID = ViewBag.FieldID; | ||
| 29 | string groupDataBinding = ViewBag.GroupDataBinding; | ||
| 30 | string labelDataBinding = ViewBag.LabelDataBinding; | ||
| 31 | string customDataBinding = ViewBag.CustomDataBinding; | ||
| 32 | string dependencyFieldName = ViewBag.DependencyFieldName; | ||
| 33 | string toolTip = ViewBag.ToolTip; | ||
| 34 | |||
| 35 | if (!string.IsNullOrEmpty(dependencyFieldName)) | ||
| 36 | { | ||
| 37 | if (string.IsNullOrEmpty(customDataBinding)) | ||
| 38 | { | ||
| 39 | customDataBinding = string.Format("enable: {0}", dependencyFieldName); | ||
| 40 | } | ||
| 41 | |||
| 42 | if (string.IsNullOrEmpty(labelDataBinding)) | ||
| 43 | { | ||
| 44 | labelDataBinding = string.Format("css: {{disabled: !{0}()}}", dependencyFieldName); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | if (!string.IsNullOrEmpty(groupDataBinding)) | ||
| 49 | { | ||
| 50 | groupDataBinding = string.Format(" data-bind=\"{0}\"", groupDataBinding); | ||
| 51 | } | ||
| 52 | |||
| 53 | if (!string.IsNullOrEmpty(labelDataBinding)) | ||
| 54 | { | ||
| 55 | labelDataBinding = string.Format(" data-bind=\"{0}\"", labelDataBinding); | ||
| 56 | } | ||
| 57 | |||
| 58 | if (!string.IsNullOrEmpty(customDataBinding)) | ||
| 59 | { | ||
| 60 | customDataBinding = string.Format(", {0}", customDataBinding); | ||
| 61 | } | ||
| 62 | |||
| 63 | if (!string.IsNullOrEmpty(toolTip)) | ||
| 64 | { | ||
| 65 | toolTip = string.Format(" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"{0}\"", toolTip); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | <div class="checkbox"@Raw(groupDataBinding)@Raw(toolTip)> | ||
| 69 | <label for="@Raw(fieldID)"@Raw(labelDataBinding)><input type="checkbox" id="@Raw(fieldID)" data-bind="checked: @Raw(fieldName), attr: {'disabled': ($root.recordMode()===RecordMode.View ? true : undefined)}@Raw(customDataBinding)"> @Raw(fieldLabel)</label> | ||
| 70 | </div> | ||
| @@ -0,0 +1,109 @@ | |||
| 1 | @******************************************************************************************************* | ||
| 2 | // AddDateField.cshtml - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/01/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //*****************************************************************************************************@ | ||
| 23 | @using RazorEngine.Templating | ||
| 24 | @inherits TemplateBase | ||
| 25 | @{ | ||
| 26 | string fieldName = ViewBag.FieldName; | ||
| 27 | bool required = ViewBag.Required; | ||
| 28 | int maxLength = ViewBag.MaxLength; | ||
| 29 | string inputType = ViewBag.InputType; | ||
| 30 | string fieldLabel = ViewBag.FieldLabel; | ||
| 31 | string fieldID = ViewBag.FieldID; | ||
| 32 | string groupDataBinding = ViewBag.GroupDataBinding; | ||
| 33 | string labelDataBinding = ViewBag.LabelDataBinding; | ||
| 34 | string requiredDataBinding = ViewBag.RequiredDataBinding; | ||
| 35 | string customDataBinding = ViewBag.CustomDataBinding; | ||
| 36 | string dependencyFieldName = ViewBag.DependencyFieldName; | ||
| 37 | string toolTip = ViewBag.ToolTip; | ||
| 38 | string inputSizeRestriction = maxLength > 0 ? string.Format(" maxlength=\"{0}\" size=\"{0}\"", maxLength) : ""; | ||
| 39 | |||
| 40 | if (!string.IsNullOrEmpty(dependencyFieldName)) | ||
| 41 | { | ||
| 42 | if (string.IsNullOrEmpty(customDataBinding)) | ||
| 43 | { | ||
| 44 | customDataBinding = string.Format("enable: {0}", dependencyFieldName); | ||
| 45 | } | ||
| 46 | |||
| 47 | if (string.IsNullOrEmpty(labelDataBinding)) | ||
| 48 | { | ||
| 49 | labelDataBinding = string.Format("css: {{disabled: !{0}()}}", dependencyFieldName); | ||
| 50 | } | ||
| 51 | |||
| 52 | if (required && string.IsNullOrEmpty(requiredDataBinding)) | ||
| 53 | { | ||
| 54 | requiredDataBinding = string.Format("{0}()", dependencyFieldName); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | if (!string.IsNullOrEmpty(groupDataBinding)) | ||
| 59 | { | ||
| 60 | groupDataBinding = string.Format(" data-bind=\"{0}\"", groupDataBinding); | ||
| 61 | } | ||
| 62 | |||
| 63 | if (!string.IsNullOrEmpty(labelDataBinding)) | ||
| 64 | { | ||
| 65 | labelDataBinding = string.Format(" data-bind=\"{0}\"", labelDataBinding); | ||
| 66 | } | ||
| 67 | |||
| 68 | if (!string.IsNullOrEmpty(requiredDataBinding)) | ||
| 69 | { | ||
| 70 | requiredDataBinding = string.Format(" && {0}", requiredDataBinding); | ||
| 71 | } | ||
| 72 | |||
| 73 | if (!string.IsNullOrEmpty(customDataBinding)) | ||
| 74 | { | ||
| 75 | customDataBinding = string.Format(", {0}", customDataBinding); | ||
| 76 | } | ||
| 77 | |||
| 78 | if (!string.IsNullOrEmpty(toolTip)) | ||
| 79 | { | ||
| 80 | toolTip = string.Format(" data-toggle=\"tooltip\" data-trigger=\"focus\" data-placement=\"auto bottom\" title=\"{0}\"", toolTip); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | @if (required) | ||
| 84 | { | ||
| 85 | @Raw(string.IsNullOrEmpty(groupDataBinding) ? "" : string.Format("<div{0}>\r\n", groupDataBinding)) | ||
| 86 | <div class="form-group" data-bind="css: {'has-error': isEmpty(@(Raw(fieldName))())@(Raw(requiredDataBinding)), 'has-feedback': isEmpty(@(Raw(fieldName))())@(Raw(requiredDataBinding))}"@Raw(toolTip)> | ||
| 87 | @if (fieldLabel.Length > 0) { | ||
| 88 | <label for="@Raw(fieldID)" @Raw(labelDataBinding)>@Raw(fieldLabel):</label> | ||
| 89 | } | ||
| 90 | <div class="input-group date" data-provide="datepicker"> | ||
| 91 | <input type="@Raw(inputType)" class="form-control" data-bind="textInput: @Raw(fieldName), attr: {'readonly': ($root.recordMode()===RecordMode.View ? true : undefined)}@Raw(customDataBinding)" id="@Raw(fieldID)"@Raw(inputSizeRestriction)@(string.IsNullOrEmpty(requiredDataBinding) ? " required" : "")> | ||
| 92 | <span class="input-group-addon" data-bind="style: {'cursor': ($root.recordMode()===RecordMode.View ? 'not-allowed' : 'pointer')}"><i class="glyphicon glyphicon-calendar"></i></span> | ||
| 93 | <span class="glyphicon glyphicon-remove form-control-feedback" style="right: 36px" data-bind="visible: isEmpty(@(Raw(fieldName))())@(Raw(requiredDataBinding))"></span> | ||
| 94 | </div> | ||
| 95 | </div> | ||
| 96 | @Raw(string.IsNullOrEmpty(groupDataBinding) ? "" : "</div>\r\n") | ||
| 97 | } | ||
| 98 | else | ||
| 99 | { | ||
| 100 | <div class="form-group"@Raw(groupDataBinding)@Raw(toolTip)> | ||
| 101 | @if (fieldLabel.Length > 0) { | ||
| 102 | <label for="@Raw(fieldID)" @Raw(labelDataBinding)>@Raw(fieldLabel):</label> | ||
| 103 | } | ||
| 104 | <div class="input-group date" data-provide="datepicker"> | ||
| 105 | <input type="@Raw(inputType)" class="form-control" data-bind="textInput: @Raw(fieldName), attr: {'readonly': ($root.recordMode()===RecordMode.View ? true : undefined)}@Raw(customDataBinding)" id="@Raw(fieldID)"@Raw(inputSizeRestriction)> | ||
| 106 | <span class="input-group-addon" data-bind="style: {'cursor': ($root.recordMode()===RecordMode.View ? 'not-allowed' : 'pointer')}"><i class="glyphicon glyphicon-calendar"></i></span> | ||
| 107 | </div> | ||
| 108 | </div> | ||
| 109 | } | ||
| @@ -0,0 +1,103 @@ | |||
| 1 | @******************************************************************************************************* | ||
| 2 | // AddInputField.cshtml - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/01/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //*****************************************************************************************************@ | ||
| 23 | @using RazorEngine.Templating | ||
| 24 | @inherits TemplateBase | ||
| 25 | @{ | ||
| 26 | string fieldName = ViewBag.FieldName; | ||
| 27 | bool required = ViewBag.Required; | ||
| 28 | int maxLength = ViewBag.MaxLength; | ||
| 29 | string inputType = ViewBag.InputType; | ||
| 30 | string fieldLabel = ViewBag.FieldLabel; | ||
| 31 | string fieldID = ViewBag.FieldID; | ||
| 32 | string groupDataBinding = ViewBag.GroupDataBinding; | ||
| 33 | string labelDataBinding = ViewBag.LabelDataBinding; | ||
| 34 | string requiredDataBinding = ViewBag.RequiredDataBinding; | ||
| 35 | string customDataBinding = ViewBag.CustomDataBinding; | ||
| 36 | string dependencyFieldName = ViewBag.DependencyFieldName; | ||
| 37 | string toolTip = ViewBag.ToolTip; | ||
| 38 | string inputSizeRestriction = maxLength > 0 ? string.Format(" maxlength=\"{0}\" size=\"{0}\"", maxLength) : ""; | ||
| 39 | |||
| 40 | if (!string.IsNullOrEmpty(dependencyFieldName)) | ||
| 41 | { | ||
| 42 | if (string.IsNullOrEmpty(customDataBinding)) | ||
| 43 | { | ||
| 44 | customDataBinding = string.Format("enable: {0}", dependencyFieldName); | ||
| 45 | } | ||
| 46 | |||
| 47 | if (string.IsNullOrEmpty(labelDataBinding)) | ||
| 48 | { | ||
| 49 | labelDataBinding = string.Format("css: {{disabled: !{0}()}}", dependencyFieldName); | ||
| 50 | } | ||
| 51 | |||
| 52 | if (required && string.IsNullOrEmpty(requiredDataBinding)) | ||
| 53 | { | ||
| 54 | requiredDataBinding = string.Format("{0}()", dependencyFieldName); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | if (!string.IsNullOrEmpty(groupDataBinding)) | ||
| 59 | { | ||
| 60 | groupDataBinding = string.Format(" data-bind=\"{0}\"", groupDataBinding); | ||
| 61 | } | ||
| 62 | |||
| 63 | if (!string.IsNullOrEmpty(labelDataBinding)) | ||
| 64 | { | ||
| 65 | labelDataBinding = string.Format(" data-bind=\"{0}\"", labelDataBinding); | ||
| 66 | } | ||
| 67 | |||
| 68 | if (!string.IsNullOrEmpty(requiredDataBinding)) | ||
| 69 | { | ||
| 70 | requiredDataBinding = string.Format(" && {0}", requiredDataBinding); | ||
| 71 | } | ||
| 72 | |||
| 73 | if (!string.IsNullOrEmpty(customDataBinding)) | ||
| 74 | { | ||
| 75 | customDataBinding = string.Format(", {0}", customDataBinding); | ||
| 76 | } | ||
| 77 | |||
| 78 | if (!string.IsNullOrEmpty(toolTip)) | ||
| 79 | { | ||
| 80 | toolTip = string.Format(" data-toggle=\"tooltip\" data-trigger=\"focus\" data-placement=\"auto bottom\" title=\"{0}\"", toolTip); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | @if (required) | ||
| 84 | { | ||
| 85 | @Raw(string.IsNullOrEmpty(groupDataBinding) ? "" : string.Format("<div{0}>\r\n", groupDataBinding)) | ||
| 86 | <div class="form-group" data-bind="css: {'has-error': isEmpty(@(Raw(fieldName))())@(Raw(requiredDataBinding)), 'has-feedback': isEmpty(@(Raw(fieldName))())@(Raw(requiredDataBinding))}"@Raw(toolTip)> | ||
| 87 | @if (fieldLabel.Length > 0) { | ||
| 88 | <label for="@Raw(fieldID)"@Raw(labelDataBinding)>@Raw(fieldLabel):</label> | ||
| 89 | } | ||
| 90 | <input type="@Raw(inputType)" class="form-control" data-bind="textInput: @Raw(fieldName), attr: {'readonly': ($root.recordMode()===RecordMode.View ? true : undefined)}@Raw(customDataBinding)" id="@Raw(fieldID)"@Raw(inputSizeRestriction)@(string.IsNullOrEmpty(requiredDataBinding) ? " required" : "")> | ||
| 91 | <span class="glyphicon glyphicon-remove form-control-feedback" data-bind="visible: isEmpty(@(Raw(fieldName))())@(Raw(requiredDataBinding))"></span> | ||
| 92 | </div> | ||
| 93 | @Raw(string.IsNullOrEmpty(groupDataBinding) ? "" : "</div>\r\n") | ||
| 94 | } | ||
| 95 | else | ||
| 96 | { | ||
| 97 | <div class="form-group"@Raw(groupDataBinding)@Raw(toolTip)> | ||
| 98 | @if (fieldLabel.Length > 0) { | ||
| 99 | <label for="@Raw(fieldID)"@Raw(labelDataBinding)>@Raw(fieldLabel):</label> | ||
| 100 | } | ||
| 101 | <input type="@Raw(inputType)" class="form-control" data-bind="textInput: @Raw(fieldName), attr: {'readonly': ($root.recordMode()===RecordMode.View ? true : undefined)}@Raw(customDataBinding)" id="@Raw(fieldID)"@Raw(inputSizeRestriction)> | ||
| 102 | </div> | ||
| 103 | } | ||
| @@ -0,0 +1,86 @@ | |||
| 1 | @******************************************************************************************************* | ||
| 2 | // AddSelectField.cshtml - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/01/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //*****************************************************************************************************@ | ||
| 23 | @using System.Collections.Generic | ||
| 24 | @using RazorEngine.Templating | ||
| 25 | @inherits TemplateBase | ||
| 26 | @{ | ||
| 27 | string fieldName = ViewBag.FieldName; | ||
| 28 | bool required = ViewBag.Required; | ||
| 29 | string fieldLabel = ViewBag.FieldLabel; | ||
| 30 | string fieldID = ViewBag.FieldID; | ||
| 31 | string groupDataBinding = ViewBag.GroupDataBinding; | ||
| 32 | string labelDataBinding = ViewBag.LabelDataBinding; | ||
| 33 | string customDataBinding = ViewBag.CustomDataBinding; | ||
| 34 | string dependencyFieldName = ViewBag.DependencyFieldName; | ||
| 35 | string toolTip = ViewBag.ToolTip; | ||
| 36 | string optionDataBinding = ViewBag.OptionDataBinding; | ||
| 37 | Dictionary<string, string> options = ViewBag.Options; | ||
| 38 | |||
| 39 | if (!string.IsNullOrEmpty(dependencyFieldName)) | ||
| 40 | { | ||
| 41 | if (string.IsNullOrEmpty(customDataBinding)) | ||
| 42 | { | ||
| 43 | customDataBinding = string.Format("enable: {0}", dependencyFieldName); | ||
| 44 | } | ||
| 45 | |||
| 46 | if (string.IsNullOrEmpty(labelDataBinding)) | ||
| 47 | { | ||
| 48 | labelDataBinding = string.Format("css: {{disabled: !{0}()}}", dependencyFieldName); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | if (!string.IsNullOrEmpty(groupDataBinding)) | ||
| 53 | { | ||
| 54 | groupDataBinding = string.Format(" data-bind=\"{0}\"", groupDataBinding); | ||
| 55 | } | ||
| 56 | |||
| 57 | if (!string.IsNullOrEmpty(labelDataBinding)) | ||
| 58 | { | ||
| 59 | labelDataBinding = string.Format(" data-bind=\"{0}\"", labelDataBinding); | ||
| 60 | } | ||
| 61 | |||
| 62 | if (!string.IsNullOrEmpty(customDataBinding)) | ||
| 63 | { | ||
| 64 | customDataBinding = string.Format(", {0}", customDataBinding); | ||
| 65 | } | ||
| 66 | |||
| 67 | if (!string.IsNullOrEmpty(toolTip)) | ||
| 68 | { | ||
| 69 | toolTip = string.Format(" data-toggle=\"tooltip\" data-placement=\"bottom\" title=\"{0}\"", toolTip); | ||
| 70 | } | ||
| 71 | |||
| 72 | if (!string.IsNullOrEmpty(optionDataBinding)) | ||
| 73 | { | ||
| 74 | optionDataBinding = string.Format(" data-bind=\"{0}\"", optionDataBinding); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | <div class="form-group"@Raw(groupDataBinding)@Raw(toolTip)> | ||
| 78 | @if (fieldLabel.Length > 0) { | ||
| 79 | <label for="@Raw(fieldID)" @Raw(labelDataBinding)>@Raw(fieldLabel):</label> | ||
| 80 | } | ||
| 81 | <select class="form-control" id="@Raw(fieldID)" data-bind="value: @Raw(fieldName), valueAllowUnset: @((!required).ToString().ToLower()), attr: {'disabled': ($root.recordMode()===RecordMode.View ? true : undefined)}@Raw(customDataBinding)"> | ||
| 82 | @foreach (KeyValuePair<string, string> item in options) { | ||
| 83 | <option value="@item.Key"@Raw(optionDataBinding)>@item.Value</option> | ||
| 84 | } | ||
| 85 | </select> | ||
| 86 | </div> | ||
| @@ -0,0 +1,103 @@ | |||
| 1 | @******************************************************************************************************* | ||
| 2 | // AddTextAreaField.cshtml - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/01/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //*****************************************************************************************************@ | ||
| 23 | @using RazorEngine.Templating | ||
| 24 | @inherits TemplateBase | ||
| 25 | @{ | ||
| 26 | string fieldName = ViewBag.FieldName; | ||
| 27 | bool required = ViewBag.Required; | ||
| 28 | int maxLength = ViewBag.MaxLength; | ||
| 29 | int rows = ViewBag.Rows; | ||
| 30 | string fieldLabel = ViewBag.FieldLabel; | ||
| 31 | string fieldID = ViewBag.FieldID; | ||
| 32 | string groupDataBinding = ViewBag.GroupDataBinding; | ||
| 33 | string labelDataBinding = ViewBag.LabelDataBinding; | ||
| 34 | string requiredDataBinding = ViewBag.RequiredDataBinding; | ||
| 35 | string customDataBinding = ViewBag.CustomDataBinding; | ||
| 36 | string dependencyFieldName = ViewBag.DependencyFieldName; | ||
| 37 | string toolTip = ViewBag.ToolTip; | ||
| 38 | string inputSizeRestriction = maxLength > 0 ? string.Format(" maxlength=\"{0}\" size=\"{0}\"", maxLength) : ""; | ||
| 39 | |||
| 40 | if (!string.IsNullOrEmpty(dependencyFieldName)) | ||
| 41 | { | ||
| 42 | if (string.IsNullOrEmpty(customDataBinding)) | ||
| 43 | { | ||
| 44 | customDataBinding = string.Format("enable: {0}", dependencyFieldName); | ||
| 45 | } | ||
| 46 | |||
| 47 | if (string.IsNullOrEmpty(labelDataBinding)) | ||
| 48 | { | ||
| 49 | labelDataBinding = string.Format("css: {{disabled: !{0}()}}", dependencyFieldName); | ||
| 50 | } | ||
| 51 | |||
| 52 | if (required && string.IsNullOrEmpty(requiredDataBinding)) | ||
| 53 | { | ||
| 54 | requiredDataBinding = string.Format("{0}()", dependencyFieldName); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | if (!string.IsNullOrEmpty(groupDataBinding)) | ||
| 59 | { | ||
| 60 | groupDataBinding = string.Format(" data-bind=\"{0}\"", groupDataBinding); | ||
| 61 | } | ||
| 62 | |||
| 63 | if (!string.IsNullOrEmpty(labelDataBinding)) | ||
| 64 | { | ||
| 65 | labelDataBinding = string.Format(" data-bind=\"{0}\"", labelDataBinding); | ||
| 66 | } | ||
| 67 | |||
| 68 | if (!string.IsNullOrEmpty(requiredDataBinding)) | ||
| 69 | { | ||
| 70 | requiredDataBinding = string.Format(" && {0}", requiredDataBinding); | ||
| 71 | } | ||
| 72 | |||
| 73 | if (!string.IsNullOrEmpty(customDataBinding)) | ||
| 74 | { | ||
| 75 | customDataBinding = string.Format(", {0}", customDataBinding); | ||
| 76 | } | ||
| 77 | |||
| 78 | if (!string.IsNullOrEmpty(toolTip)) | ||
| 79 | { | ||
| 80 | toolTip = string.Format(" data-toggle=\"tooltip\" data-trigger=\"focus\" data-placement=\"auto bottom\" title=\"{0}\"", toolTip); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | @if (required) | ||
| 84 | { | ||
| 85 | @Raw(string.IsNullOrEmpty(groupDataBinding) ? "" : string.Format("<div{0}>\r\n", groupDataBinding)) | ||
| 86 | <div class="form-group" data-bind="css: {'has-error': isEmpty(@(Raw(fieldName))())@(Raw(requiredDataBinding)), 'has-feedback': isEmpty(@(Raw(fieldName))())@(Raw(requiredDataBinding))}"@Raw(toolTip)> | ||
| 87 | @if (fieldLabel.Length > 0) { | ||
| 88 | <label for="@Raw(fieldID)" @Raw(labelDataBinding)>@Raw(fieldLabel):</label> | ||
| 89 | } | ||
| 90 | <textarea rows="@rows" class="form-control" data-bind="textInput: @Raw(fieldName), attr: {'readonly': ($root.recordMode()===RecordMode.View ? true : undefined)}@Raw(customDataBinding)" id="@Raw(fieldID)"@Raw(inputSizeRestriction)@(string.IsNullOrEmpty(requiredDataBinding) ? " required" : "")></textarea> | ||
| 91 | <span class="glyphicon glyphicon-remove form-control-feedback" data-bind="visible: isEmpty(@(Raw(fieldName))())@(Raw(requiredDataBinding))"></span> | ||
| 92 | </div> | ||
| 93 | @Raw(string.IsNullOrEmpty(groupDataBinding) ? "" : "</div>\r\n") | ||
| 94 | } | ||
| 95 | else | ||
| 96 | { | ||
| 97 | <div class="form-group"@Raw(groupDataBinding)@Raw(toolTip)> | ||
| 98 | @if (fieldLabel.Length > 0) { | ||
| 99 | <label for="@Raw(fieldID)" @Raw(labelDataBinding)>@Raw(fieldLabel):</label> | ||
| 100 | } | ||
| 101 | <textarea rows="@rows" class="form-control" data-bind="textInput: @Raw(fieldName), attr: {'readonly': ($root.recordMode()===RecordMode.View ? true : undefined)}@Raw(customDataBinding)" id="@Raw(fieldID)"@Raw(inputSizeRestriction)></textarea> | ||
| 102 | </div> | ||
| 103 | } | ||
| @@ -0,0 +1,102 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // WebExtensions.cs - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 02/17/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | |||
| 24 | using System; | ||
| 25 | using System.Collections.Generic; | ||
| 26 | using System.Linq; | ||
| 27 | using System.Net.Http; | ||
| 28 | using System.Web; | ||
| 29 | using GSF.Collections; | ||
| 30 | |||
| 31 | namespace GSF.Web | ||
| 32 | { | ||
| 33 | /// <summary> | ||
| 34 | /// Defines extension methods useful for web applications. | ||
| 35 | /// </summary> | ||
| 36 | public static class WebExtensions | ||
| 37 | { | ||
| 38 | /// <summary> | ||
| 39 | /// Performs JavaScript encoding on given string. | ||
| 40 | /// </summary> | ||
| 41 | /// <param name="text">The string to be encoded.</param> | ||
| 42 | public static string JavaScriptEncode(this string text) | ||
| 43 | { | ||
| 44 | return HttpUtility.JavaScriptStringEncode(text.ToNonNullString()); | ||
| 45 | } | ||
| 46 | |||
| 47 | /// <summary> | ||
| 48 | /// Performs HTML encoding on the given string. | ||
| 49 | /// </summary> | ||
| 50 | /// <param name="text">The string to be encoded.</param> | ||
| 51 | public static string HtmlEncode(this string text) | ||
| 52 | { | ||
| 53 | return HttpUtility.HtmlEncode(text.ToNonNullString()); | ||
| 54 | } | ||
| 55 | |||
| 56 | /// <summary> | ||
| 57 | /// Performs URL encoding on the given string. | ||
| 58 | /// </summary> | ||
| 59 | /// <param name="text">The string to be encoded.</param> | ||
| 60 | public static string UrlEncode(this string text) | ||
| 61 | { | ||
| 62 | return HttpUtility.UrlEncode(text.ToNonNullString()); | ||
| 63 | } | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Gets query parameters for current request message | ||
| 67 | /// </summary> | ||
| 68 | /// <param name="request"></param> | ||
| 69 | /// <returns></returns> | ||
| 70 | public static Dictionary<string, string> QueryParameters(this HttpRequestMessage request) | ||
| 71 | { | ||
| 72 | return request.GetQueryNameValuePairs().ToDictionary(kvp => kvp.Key, kvp => kvp.Value); | ||
| 73 | } | ||
| 74 | |||
| 75 | /// <summary> | ||
| 76 | /// Corrects script alignment with a desired number of forward spaces. | ||
| 77 | /// </summary> | ||
| 78 | /// <param name="script">Script text.</param> | ||
| 79 | /// <param name="spaces">Desired forward spaces.</param> | ||
| 80 | /// <returns>Script with corrected forward alignment.</returns> | ||
| 81 | public static string FixForwardSpacing(this string script, int spaces = 4) | ||
| 82 | { | ||
| 83 | Tuple<string, int>[] linesAndLengths = script | ||
| 84 | .Split(new[] { Environment.NewLine }, StringSplitOptions.None) | ||
| 85 | .Select(line => new Tuple<string, int>(line, line.Length - line.TrimStart(' ').Length)) | ||
| 86 | .ToArray(); | ||
| 87 | |||
| 88 | int minLength = linesAndLengths | ||
| 89 | .Select(lineAndLength => (int?)lineAndLength.Item2) | ||
| 90 | .Where(length => length.GetValueOrDefault() > 0) | ||
| 91 | .Min() ?? 0; | ||
| 92 | |||
| 93 | string forwardSpacing = new string(' ', spaces); | ||
| 94 | |||
| 95 | return linesAndLengths | ||
| 96 | .Select(lineAndLength => lineAndLength.Item2 > 0 ? | ||
| 97 | $"{forwardSpacing}{(lineAndLength.Item2 > minLength ? lineAndLength.Item1.Substring(minLength) : lineAndLength.Item1)}" : | ||
| 98 | lineAndLength.Item1.ToNonNullNorEmptyString()) | ||
| 99 | .ToDelimitedString(Environment.NewLine); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | } | ||
| @@ -0,0 +1,20 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <packages> | ||
| 3 | <package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net46" /> | ||
| 4 | <package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net46" /> | ||
| 5 | <package id="Microsoft.AspNet.SignalR" version="2.2.0" targetFramework="net46" /> | ||
| 6 | <package id="Microsoft.AspNet.SignalR.Core" version="2.2.0" targetFramework="net46" /> | ||
| 7 | <package id="Microsoft.AspNet.SignalR.JS" version="2.2.0" targetFramework="net46" /> | ||
| 8 | <package id="Microsoft.AspNet.SignalR.SystemWeb" version="2.2.0" targetFramework="net46" /> | ||
| 9 | <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net46" /> | ||
| 10 | <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net46" /> | ||
| 11 | <package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net46" /> | ||
| 12 | <package id="Microsoft.Owin" version="2.1.0" targetFramework="net46" /> | ||
| 13 | <package id="Microsoft.Owin.Host.SystemWeb" version="2.1.0" targetFramework="net46" /> | ||
| 14 | <package id="Microsoft.Owin.Security" version="2.1.0" targetFramework="net46" /> | ||
| 15 | <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net46" /> | ||
| 16 | <package id="Newtonsoft.Json" version="6.0.4" targetFramework="net46" /> | ||
| 17 | <package id="Owin" version="1.0" targetFramework="net46" /> | ||
| 18 | <package id="RazorEngine" version="3.7.7" targetFramework="net46" /> | ||
| 19 | <package id="System.Net.Http" version="2.0.20126.16343" targetFramework="net46" /> | ||
| 20 | </packages> | ||