History

(1) GSF.Security: Added modeled security tables. (2) GSF.Web: Added security, data context, view template and scripts related to modeled table operations

Ritchie Carroll committed 10 years ago

Commit a02edca1 parent 46782aa32 files · +5897 −0Browse files

Unified · Split

32 files
  1. Source/Libraries/GSF.Core/Collections/CollectionExtensions.cs +11 −0
  2. Source/Libraries/GSF.Security/GSF.Security.csproj +10 −0
  3. Source/Libraries/GSF.Security/Model/AccessLog.cs +58 −0
  4. Source/Libraries/GSF.Security/Model/ApplicationRole.cs +79 −0
  5. Source/Libraries/GSF.Security/Model/ApplicationRoleSecurityGroup.cs +52 −0
  6. Source/Libraries/GSF.Security/Model/ApplicationRoleUserAccount.cs +52 −0
  7. Source/Libraries/GSF.Security/Model/ErrorLog.cs +87 −0
  8. Source/Libraries/GSF.Security/Model/Node.cs +80 −0
  9. Source/Libraries/GSF.Security/Model/SecurityGroup.cs +78 −0
  10. Source/Libraries/GSF.Security/Model/SecurityGroupUserAccount.cs +52 −0
  11. Source/Libraries/GSF.Security/Model/UserAccount.cs +139 −0
  12. Source/Libraries/GSF.Web/GSF.Web.csproj +105 −0
  13. Source/Libraries/GSF.Web/Model/DataContext.cs +955 −0
  14. Source/Libraries/GSF.Web/Model/RecordOperationsCache.cs +91 −0
  15. Source/Libraries/GSF.Web/Model/Scripts/gsf.web.client.js +667 −0
  16. Source/Libraries/GSF.Web/Model/Scripts/pagedViewModel.js +568 −0
  17. Source/Libraries/GSF.Web/Model/Views/PagedViewModel.cshtml +348 −0
  18. Source/Libraries/GSF.Web/Security/AuthorizationCache.cs +39 −0
  19. Source/Libraries/GSF.Web/Security/AuthorizeControllerRoleAttribute.cs +156 −0
  20. Source/Libraries/GSF.Web/Security/AuthorizeHubRoleAttribute.cs +138 −0
  21. Source/Libraries/GSF.Web/Security/SecurityHub.cs +292 −0
  22. Source/Libraries/GSF.Web/Security/Views/Groups.cshtml +370 −0
  23. Source/Libraries/GSF.Web/Security/Views/Users.cshtml +381 −0
  24. Source/Libraries/GSF.Web/Templating/NamespaceDoc.cs +35 −0
  25. Source/Libraries/GSF.Web/Templating/RazorView.cs +461 −0
  26. Source/Libraries/GSF.Web/Templating/Views/AddCheckBoxField.cshtml +70 −0
  27. Source/Libraries/GSF.Web/Templating/Views/AddDateField.cshtml +109 −0
  28. Source/Libraries/GSF.Web/Templating/Views/AddInputField.cshtml +103 −0
  29. Source/Libraries/GSF.Web/Templating/Views/AddSelectField.cshtml +86 −0
  30. Source/Libraries/GSF.Web/Templating/Views/AddTextAreaField.cshtml +103 −0
  31. Source/Libraries/GSF.Web/WebExtensions.cs +102 −0
  32. Source/Libraries/GSF.Web/packages.config +20 −0
Source/Libraries/GSF.Core/Collections/CollectionExtensions.cs+11−0
@@ -58,6 +58,7 @@ · 57 unmodified lines
58using System;58using System;
59using System.Collections;59using System.Collections;
60using System.Collections.Generic;60using System.Collections.Generic;
61using System.Collections.Specialized;
61using System.Linq;62using System.Linq;
62using System.Text;63using System.Text;
63using Random = GSF.Security.Cryptography.Random;64using 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>
Source/Libraries/GSF.Security/GSF.Security.csproj+10−0
@@ -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" />
Source/Libraries/GSF.Security/Model/AccessLog.cs+58−0
@@ -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
24using System;
25using System.ComponentModel.DataAnnotations;
26using GSF.Data.Model;
27
28namespace 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}
Source/Libraries/GSF.Security/Model/ApplicationRole.cs+79−0
@@ -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
24using System;
25using System.ComponentModel.DataAnnotations;
26using GSF.Data.Model;
27
28namespace 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}
Source/Libraries/GSF.Security/Model/ApplicationRoleSecurityGroup.cs+52−0
@@ -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
24using System;
25using System.ComponentModel.DataAnnotations;
26
27namespace 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}
Source/Libraries/GSF.Security/Model/ApplicationRoleUserAccount.cs+52−0
@@ -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
24using System;
25using System.ComponentModel.DataAnnotations;
26
27namespace 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}
Source/Libraries/GSF.Security/Model/ErrorLog.cs+87−0
@@ -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
24using System;
25using System.ComponentModel.DataAnnotations;
26using GSF.Data.Model;
27
28namespace 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}
Source/Libraries/GSF.Security/Model/Node.cs+80−0
@@ -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
24using System;
25using System.ComponentModel.DataAnnotations;
26using GSF.Data.Model;
27
28namespace 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}
Source/Libraries/GSF.Security/Model/SecurityGroup.cs+78−0
@@ -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
24using System;
25using System.ComponentModel.DataAnnotations;
26using GSF.Data.Model;
27
28namespace 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}
Source/Libraries/GSF.Security/Model/SecurityGroupUserAccount.cs+52−0
@@ -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
24using System;
25using System.ComponentModel.DataAnnotations;
26
27namespace 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}
Source/Libraries/GSF.Security/Model/UserAccount.cs+139−0
@@ -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
24using System;
25using System.ComponentModel.DataAnnotations;
26using GSF.Data.Model;
27
28namespace 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}
Source/Libraries/GSF.Web/GSF.Web.csproj+105−0
@@ -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.
Source/Libraries/GSF.Web/Model/DataContext.cs+955−0
@@ -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
24using System;
25using System.Collections.Generic;
26using System.ComponentModel.DataAnnotations;
27using System.Linq;
28using System.Text;
29using GSF.Collections;
30using GSF.Data;
31using GSF.Data.Model;
32using GSF.Reflection;
33using GSF.Web.Templating;
34using RazorEngine.Templating;
35
36namespace 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}
Source/Libraries/GSF.Web/Model/RecordOperationsCache.cs+91−0
@@ -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
24using System;
25using System.Collections.Generic;
26using System.Reflection;
27using GSF.Collections;
28using GSF.Data.Model;
29using GSF.Reflection;
30using GSF.Web.Security;
31
32namespace 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}
Source/Libraries/GSF.Web/Model/Scripts/gsf.web.client.js+667−0
@@ -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
28const isIE = detectIE();
29var 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
40function notNull(value, nonNullValue) {
41 return value || (nonNullValue || "");
42}
43
44function 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
72function isNumber(val) {
73 return !isNaN(parseFloat(val)) && isFinite(val);
74}
75
76function 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
84function getBool(val) {
85 const num = +val;
86 return !isNaN(num) ? !!num : !!String(val).toLowerCase().replace(false, "");
87}
88
89// Number functions
90Number.prototype.truncate = function() {
91 if (typeof Math.trunc != "function")
92 return parseInt(this.toString());
93
94 return Math.trunc(this);
95}
96
97Number.prototype.padLeft = function (totalWidth, paddingChar) {
98 return this.truncate().toString().padLeft(totalWidth, paddingChar || "0");
99}
100
101Number.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.
110function 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
144Array.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
149function 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
288Dictionary.fromObservableDictionary = function (observableDictionary) {
289 var dictionary = new Dictionary();
290 dictionary.pushAll(observableDictionary.toJSON());
291 return dictionary;
292}
293
294// String functions
295function isEmpty(str) {
296 return !str || String(str).length === 0;
297}
298
299String.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
308String.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
314String.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
321String.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
328String.prototype.countOccurrences = function (searchString) {
329 return (this.split(searchString).length - 1);
330}
331
332if (!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
339String.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.
345String.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
485Date.prototype.addDays = function (days) {
486 return new Date(this.setDate(this.getDate() + days));
487}
488
489Date.prototype.toUTC = function () {
490 this.setMinutes(this.getMinutes() - this.getTimezoneOffset());
491 return this;
492}
493
494Date.prototype.daysBetween = function (startDate) {
495 const millisecondsPerDay = 24 * 60 * 60 * 1000;
496 return (this.toUTC() - startDate.toUTC()) / millisecondsPerDay;
497}
498
499String.prototype.toDate = function () {
500 return new Date(Date.parse(this));
501};
502
503String.prototype.formatDate = function (format, utc) {
504 return formatDate(this.toDate(), format, utc);
505};
506
507Date.prototype.formatDate = function (format, utc) {
508 return formatDate(this, format, utc);
509};
510
511function 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}
Source/Libraries/GSF.Web/Model/Scripts/pagedViewModel.js+568−0
@@ -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
27var RecordMode = {
28 View: 0,
29 Edit: 1,
30 AddNew: 2
31}
32
33// Define paged view model class
34function 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
506var 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);
Source/Libraries/GSF.Web/Model/Views/PagedViewModel.cshtml+348−0
@@ -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)&nbsp;
126 <div class="btn-group-vertical btn-group-sort">
127 <button type="button" class="btn" id="asc" title="Sort&nbsp;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&nbsp;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&nbsp;&nbsp;<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>&nbsp;&nbsp;Add&nbsp;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 &nbsp;of&nbsp;
173 <span data-bind="text: totalPages">1</span>
174 &nbsp;
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">&times;</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}
Source/Libraries/GSF.Web/Security/AuthorizationCache.cs+39−0
@@ -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
24using System;
25using System.Collections.Concurrent;
26
27namespace 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}
Source/Libraries/GSF.Web/Security/AuthorizeControllerRoleAttribute.cs+156−0
@@ -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
24using System;
25using System.Linq;
26using System.Security;
27using System.Threading;
28using System.Web.Mvc;
29using GSF.Collections;
30using GSF.Data;
31using GSF.Identity;
32using GSF.Security;
33
34namespace 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}
Source/Libraries/GSF.Web/Security/AuthorizeHubRoleAttribute.cs+138−0
@@ -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
24using System;
25using System.Linq;
26using System.Security;
27using System.Security.Principal;
28using System.Threading;
29using GSF.Collections;
30using GSF.Data;
31using GSF.Identity;
32using GSF.Security;
33using Microsoft.AspNet.SignalR;
34
35namespace 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}
Source/Libraries/GSF.Web/Security/SecurityHub.cs+292−0
@@ -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
24using System;
25using System.Collections.Generic;
26using GSF.Configuration;
27using GSF.Data;
28using GSF.Data.Model;
29using GSF.Identity;
30using GSF.Security.Model;
31using GSF.Web.Model;
32using Microsoft.AspNet.SignalR;
33using Microsoft.AspNet.SignalR.Hubs;
34
35namespace 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}
Source/Libraries/GSF.Web/Security/Views/Groups.cshtml+370−0
@@ -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&nbsp;AD&nbsp;Group", "text-center" },
57 new[] { "CreatedOn", "Date&nbsp;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>&nbsp;<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>&nbsp;<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>&nbsp;<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>&nbsp;<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)}">&nbsp;Active&nbsp;Directory&nbsp;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)}">&nbsp;Database&nbsp;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">&times;</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&nbsp;&nbsp;<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}
Source/Libraries/GSF.Web/Security/Views/Users.cshtml+381−0
@@ -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&nbsp;AD&nbsp;User", "text-center" },
57 new[] { "CreatedOn", "Date&nbsp;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>&nbsp;<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>&nbsp;<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>&nbsp;<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>&nbsp;<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)}">&nbsp;Active&nbsp;Directory&nbsp;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)}">&nbsp;Database&nbsp;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">&times;</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&nbsp;&nbsp;<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}
Source/Libraries/GSF.Web/Templating/NamespaceDoc.cs+35−0
@@ -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
24using System.Runtime.CompilerServices;
25
26namespace 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}
Source/Libraries/GSF.Web/Templating/RazorView.cs+461−0
@@ -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
24using System;
25using System.Collections.Generic;
26using System.IO;
27using System.Net.Http;
28using System.Reflection;
29using System.Threading.Tasks;
30using System.Web;
31using System.Web.Hosting;
32using GSF.Collections;
33using GSF.Configuration;
34using GSF.IO;
35using GSF.Web.Model;
36using RazorEngine;
37using RazorEngine.Configuration;
38using RazorEngine.Templating;
39
40// ReSharper disable StaticMemberInGenericType
41namespace 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}
Source/Libraries/GSF.Web/Templating/Views/AddCheckBoxField.cshtml+70−0
@@ -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>
Source/Libraries/GSF.Web/Templating/Views/AddDateField.cshtml+109−0
@@ -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}
98else
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}
Source/Libraries/GSF.Web/Templating/Views/AddInputField.cshtml+103−0
@@ -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}
95else
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}
Source/Libraries/GSF.Web/Templating/Views/AddSelectField.cshtml+86−0
@@ -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>
Source/Libraries/GSF.Web/Templating/Views/AddTextAreaField.cshtml+103−0
@@ -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}
95else
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}
Source/Libraries/GSF.Web/WebExtensions.cs+102−0
@@ -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
24using System;
25using System.Collections.Generic;
26using System.Linq;
27using System.Net.Http;
28using System.Web;
29using GSF.Collections;
30
31namespace 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}
Source/Libraries/GSF.Web/packages.config+20−0
@@ -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>

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t