├── .gitignore ├── ArcGIS Server Monitor.sln ├── PopularExtents.html ├── ReadMe.md ├── ServicesPerformance.html ├── ServicesUsage.html ├── config ├── MBIE-config.js ├── Northland-config.js ├── Testing-config.js ├── Wairarapa-config.js ├── WestCoast-config.js └── config.js ├── css └── main.css ├── images └── Screenshot.jpg ├── libraries └── bootstrap │ └── 3.0.2 │ ├── css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.min.css │ ├── bootstrap.css │ └── bootstrap.min.css │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff │ └── js │ ├── bootstrap.js │ └── bootstrap.min.js ├── proxy ├── Web.config ├── proxy.ashx ├── proxy.config └── proxy.xsd ├── scripts └── main.js └── web.config /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | *_i.c 48 | *_p.c 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.vspscc 63 | .builds 64 | *.dotCover 65 | 66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 67 | #packages/ 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | 80 | # ReSharper is a .NET coding add-in 81 | _ReSharper* 82 | 83 | # Installshield output folder 84 | [Ee]xpress 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish 98 | 99 | # Others 100 | [Bb]in 101 | [Oo]bj 102 | sql 103 | TestResults 104 | *.Cache 105 | ClientBin 106 | stylecop.* 107 | ~$* 108 | *.dbmdl 109 | Generated_Code #added for RIA/Silverlight projects 110 | 111 | # Backup & report files from converting an old project file to a newer 112 | # Visual Studio version. Backup files are not needed, because we have git ;-) 113 | _UpgradeReport_Files/ 114 | Backup*/ 115 | UpgradeLog*.XML 116 | 117 | 118 | 119 | ############ 120 | ## Windows 121 | ############ 122 | 123 | # Windows image file caches 124 | Thumbs.db 125 | 126 | # Folder config file 127 | Desktop.ini 128 | 129 | 130 | ############# 131 | ## Python 132 | ############# 133 | 134 | *.py[co] 135 | 136 | # Packages 137 | *.egg 138 | *.egg-info 139 | dist 140 | build 141 | eggs 142 | parts 143 | bin 144 | var 145 | sdist 146 | develop-eggs 147 | .installed.cfg 148 | 149 | # Installer logs 150 | pip-log.txt 151 | 152 | # Unit test / coverage reports 153 | .coverage 154 | .tox 155 | 156 | #Translations 157 | *.mo 158 | 159 | #Mr Developer 160 | .mr.developer.cfg 161 | 162 | # Mac crap 163 | .DS_Store 164 | -------------------------------------------------------------------------------- /ArcGIS Server Monitor.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "ArcGIS Server Monitor", ".", "{472DF0DF-54E6-49CC-8A48-B19405756CE3}" 5 | ProjectSection(WebsiteProperties) = preProject 6 | TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5" 7 | Debug.AspNetCompiler.VirtualPath = "/ArcGIS Server Monitor" 8 | Debug.AspNetCompiler.PhysicalPath = "..\ArcGIS Server Monitor\" 9 | Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\ArcGIS Server Monitor\" 10 | Debug.AspNetCompiler.Updateable = "true" 11 | Debug.AspNetCompiler.ForceOverwrite = "true" 12 | Debug.AspNetCompiler.FixedNames = "false" 13 | Debug.AspNetCompiler.Debug = "True" 14 | Release.AspNetCompiler.VirtualPath = "/ArcGIS Server Monitor" 15 | Release.AspNetCompiler.PhysicalPath = "..\ArcGIS Server Monitor\" 16 | Release.AspNetCompiler.TargetPath = "PrecompiledWeb\ArcGIS Server Monitor\" 17 | Release.AspNetCompiler.Updateable = "true" 18 | Release.AspNetCompiler.ForceOverwrite = "true" 19 | Release.AspNetCompiler.FixedNames = "false" 20 | Release.AspNetCompiler.Debug = "False" 21 | VWDPort = "65374" 22 | EndProjectSection 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {472DF0DF-54E6-49CC-8A48-B19405756CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {472DF0DF-54E6-49CC-8A48-B19405756CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | EndGlobalSection 32 | GlobalSection(SolutionProperties) = preSolution 33 | HideSolutionNode = FALSE 34 | EndGlobalSection 35 | EndGlobal 36 | -------------------------------------------------------------------------------- /PopularExtents.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 |
26 |

27 |
28 | 29 | 30 | 50 | 51 | 52 |
53 |
54 |

Popular Extents

55 |

56 | 57 |
58 | Dynamic Map Service -
59 | 60 | 61 | 62 | 63 |
64 |
65 | Filter -
66 | 67 | 68 | 69 | 70 |
71 |
72 | Graphic -
73 | 74 | 75 | 76 | 77 |
78 |
79 |

Number of draw requests - 0

80 |
81 |
82 |
83 |
84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # ArcGIS Server Monitor 2 | 3 | At ArcGIS for Server 10.1 a new architecture was implemented where all communication with the server was done over HTTP. A new 4 | server admin API was built enabling access to administrative information about the server, services and usage. A number of applications 5 | have been built using Javascript that can query this API enabling the ArcGIS administrator to view and analyse useful information 6 | about the servers usage and performance. This information is displayed in the form of charts, maps and textual information. 7 | 8 | Based off an application built [here](http://blogs.esri.com/esri/arcgis/2013/08/23/demo-applications-arcgis-server-for-administrators-sessions-at-the-2013-user-conference) 9 | 10 | [Demo](http://gis.mstn.govt.nz/AGSMonitor) 11 | 12 | ![Screenshot](/images/Screenshot.jpg) 13 | 14 | 15 | ## Features 16 | 17 | * Service average response time 18 | * Service average draw time 19 | * Service success and failures 20 | * Number of draws for the past 30 days 21 | * Map extent requests 22 | * Filter statistics by time 23 | 24 | 25 | ## Libraries Used 26 | 27 | * ArcGIS API for Javascript 3.7 28 | * Dojo toolkit 1.8.3 29 | * Bootstrap 3.0.2 30 | * jQuery Javascript library 1.10.2 31 | 32 | 33 | ## Content Delivery Networks Used 34 | 35 | * http://js.arcgis.com 36 | * http://code.jquery.com 37 | 38 | 39 | ## Browser Support 40 | 41 | * Internet Explorer 7+ 42 | * Google Chrome 1+ 43 | * Mozilla Firefox 1+ 44 | * Safari 3+ 45 | * Opera 1+ 46 | 47 | 48 | ## Requirements 49 | 50 | * Notepad or your favorite HTML editor 51 | * Web browser with access to the Internet 52 | * ArcGIS for Server 10.1+ (Not 10.3) 53 | * ArcGIS for Server logs set to FINE 54 | * Admin access enabled on [SERVER]/arcgis or [SERVER]:6080/arcgis 55 | * Web server to host on 56 | 57 | 58 | ## Installation Instructions 59 | 60 | * Install proxy on web server 61 | * Copy the proxy folder to C:\inetpub\wwwroot 62 | * Convert directory to application in IIS 63 | * Update the parameter in the configuration file ("proxyUrl") to point to the proxy e.g. "'[SERVER]'/proxy/proxy.ashx" 64 | * Set the ArcGIS for Server logs to be FINE 65 | * Enable HTTP GET and POST requests with credentials on ArcGIS Server 66 | * More info here - http://server.arcgis.com/en/server/latest/administer/windows/enable-token-acquisition-through-an-http-get-request.htm 67 | * Set the allowHttpPostQueryParams property to true. 68 | * Set the allowHttpGet property to true. 69 | * Configure other options in the config.js to the environment 70 | * Install ArcGIS Server Monitor on web server 71 | * Copy the folder to C:\inetpub\wwwroot 72 | * Convert directory to application in IIS 73 | 74 | 75 | ## Resources 76 | 77 | * [LinkedIn](http://www.linkedin.com/in/sfweston) 78 | * [GitHub](https://github.com/WestonSF) 79 | * [Twitter](https://twitter.com/Westonelli) 80 | * [Blog](http://westonelli.wordpress.com) 81 | * [ArcGIS API for Javascript](https://developers.arcgis.com/en/javascript) 82 | * [Python for ArcGIS](http://resources.arcgis.com/en/communities/python) 83 | 84 | 85 | ## Issues 86 | 87 | Find a bug or want to request a new feature? Please let me know by submitting an issue. 88 | 89 | 90 | ## Contributing 91 | 92 | Anyone and everyone is welcome to contribute. 93 | 94 | 95 | ## Licensing 96 | Copyright 2015 - Shaun Weston 97 | 98 | Licensed under the Apache License, Version 2.0 (the "License"); 99 | you may not use this file except in compliance with the License. 100 | You may obtain a copy of the License at 101 | 102 | http://www.apache.org/licenses/LICENSE-2.0 103 | 104 | Unless required by applicable law or agreed to in writing, software 105 | distributed under the License is distributed on an "AS IS" BASIS, 106 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 107 | See the License for the specific language governing permissions and 108 | limitations under the License. -------------------------------------------------------------------------------- /ServicesPerformance.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 |
26 |

27 |
28 | 29 | 30 | 50 | 51 | 52 |
53 |
54 |

Services Performance

55 |

56 | 57 |
58 | Service -
59 | 60 | 61 | 62 | 63 |
64 |
65 | Filter -
66 | 67 | 68 | 69 | 70 |
71 | 72 |
73 |

Availability

74 | Status 75 |
76 | Cache Status 77 |
78 | Maximum Instances - 0 79 |
80 | Instances Available - 0 81 |
82 | 83 |
84 |

Success Rate

85 |

Success rate for all records logged for this service.

86 |
87 |
88 |

Number of records logged - 0

89 |
90 |
91 |
92 |

Process Time

93 |

Average time to process all requests for this service.

94 |
95 |
96 |

Number of requests - 0

97 |

Average Process Time (seconds) - 0.0

98 |
99 |
100 |
101 |

Draw Time

102 |

Average time to process draw requests for this dynamic map service.

103 |
104 |
105 |

Number of draw requests - 0

106 |

Average Draw Time (seconds) - 0.0

107 |
108 |
109 |
110 |

Query Time

111 |

Average time to process query, identify or find requests on this map service.

112 |
113 |
114 |

Number of query requests - 0

115 |

Average Query Time (seconds) - 0.0

116 |
117 | 118 |
119 |
120 |
121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /ServicesUsage.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 |
26 |

27 |
28 | 29 | 30 | 50 | 51 | 52 |
53 |
54 |

Services Usage

55 |

56 | 57 |
58 | Dynamic Map Service -
59 | 60 | 61 | 62 | 63 |
64 |
65 | 66 |
67 |

Daily Draw Requests (Last 30 Days)

68 |
69 |
70 | 71 |
72 | 75 |
76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /config/MBIE-config.js: -------------------------------------------------------------------------------- 1 | var configOptions; 2 | 3 | function initVariables() { 4 | configOptions = { 5 | // Title for site 6 | title: "MBIE ArcGIS Server Performance & Usage Stats", 7 | 8 | // Description for site 9 | description: "This site contains a number of useful applications enabling the ArcGIS administrator to view and analyse useful information about an ArcGIS Server site usage and performance. This information is displayed in the form of charts, maps and textual information.", 10 | 11 | // ArcGIS server site 12 | agsSite: { 13 | url: "http://nzpam-extwebsit.wd.govt.nz:6080/arcgis", // PROD - http://nzpam-arcgis.wd.govt.nz:6080/arcgis, PRE-PROD - http://nzpam-arcgispre.wd.govt.nz:6080/arcgis, SIT - http://nzpam-extwebsit.wd.govt.nz:6080/arcgis 14 | username: "siteadmin", 15 | password: "*****" 16 | }, 17 | 18 | // Proxy to use 19 | proxyUrl: "http://nzpam-intwebsit.wd.govt.nz/proxy/proxy.ashx", // PROD - http://nzpam-intweb.wd.govt.nz/proxy/proxy.ashx, PRE-PROD - http://nzpam-intwebpre.wd.govt.nz/proxy/proxy.ashx, SIT - http://nzpam-intwebsit.wd.govt.nz/proxy/proxy.ashx 20 | 21 | // Initial map extent 22 | initialExtent: { xmin: 89980, xmax: 3249111, ymin: 5313295, ymax: 6419256 }, 23 | 24 | // Spatial reference 25 | spatialReference: { WKID: 2193, name: "NZTM" }, 26 | 27 | // Basemap wrap around 28 | wraparound180: false, 29 | 30 | // Basemap to be used 31 | basemap: { id: "Streets", url: "http://services.arcgisonline.co.nz/arcgis/rest/services/Generic/newzealand/MapServer" }, 32 | 33 | // Default dropdown options 34 | defaultService: "", // Full service name e.g. Environment/BathingSites.MapServer 35 | defaultFilter: "Last 24 Hours", // Last Hour, Last 24 Hours, Last Week or Last 30 Days 36 | defaultGraphic: "Polygon", // Polygon, Point or Hot Spot 37 | 38 | // Popular extent graphics 39 | polygonSymbol: new esri.symbol.SimpleFillSymbol(esri.symbol.SimpleFillSymbol.STYLE_SOLID, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 2), new dojo.Color([255, 0, 0, 0.0])), 40 | pointSymbol: new esri.symbol.SimpleMarkerSymbol(esri.symbol.SimpleMarkerSymbol.STYLE_CIRCLE, 14, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 1), new dojo.Color([255, 0, 0, 0.25])), 41 | 42 | // ArcGIS Online hot spot analysis service or custom hot spot analysis service 43 | hotSpotAnalysisService: { 44 | enable: false, 45 | url: "http://analysis.arcgis.com/arcgis/rest/services/tasks/GPServer/FindHotSpots", 46 | secure: true, 47 | tokenURL: "https://*****.maps.arcgis.com/sharing", 48 | // Username/password for ArcGIS Online - Needs to be an organisation account 49 | username: "*****", 50 | password: "*****" 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /config/Northland-config.js: -------------------------------------------------------------------------------- 1 | var configOptions; 2 | 3 | function initVariables() { 4 | configOptions = { 5 | // Title for site 6 | title: "NRC ArcGIS Server Performance & Usage Stats", 7 | 8 | // Description for site 9 | description: "This site contains a number of useful applications enabling the ArcGIS administrator to view and analyse useful information about an ArcGIS Server site usage and performance. This information is displayed in the form of charts, maps and textual information.", 10 | 11 | // ArcGIS server site 12 | agsSite: { 13 | url: "http://gis.nrc.govt.nz/arcgis", 14 | username: "siteadmin", 15 | password: "*****" 16 | }, 17 | 18 | // Proxy to use 19 | proxyUrl: "http://gis.nrc.govt.nz/proxy/proxy.ashx", // For testing use "proxy/proxy.ashx" 20 | 21 | // Initial map extent 22 | initialExtent: { xmin: 1585000, xmax: 1813000, ymin: 5980000, ymax: 6127000 }, 23 | 24 | // Spatial reference 25 | spatialReference: { WKID: 2193, name: "NZTM" }, 26 | 27 | // Basemap wrap around 28 | wraparound180: false, 29 | 30 | // Basemap to be used 31 | basemap: { id: "Topographic", url: "http://gis.nrc.govt.nz/arcgis/rest/services/Basemaps/TopoEsri/MapServer" }, 32 | 33 | // Default dropdown options 34 | defaultService: "Environment/BathingSites.MapServer", // Full service name e.g. Environment/BathingSites.MapServer 35 | defaultFilter: "Last 24 Hours", // Last Hour, Last 24 Hours, Last Week or Last 30 Days 36 | defaultGraphic: "Polygon", // Polygon, Point or Hot Spot 37 | 38 | // Popular extent graphics 39 | polygonSymbol: new esri.symbol.SimpleFillSymbol(esri.symbol.SimpleFillSymbol.STYLE_SOLID, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 2), new dojo.Color([255, 0, 0, 0.0])), 40 | pointSymbol: new esri.symbol.SimpleMarkerSymbol(esri.symbol.SimpleMarkerSymbol.STYLE_CIRCLE, 14, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 1), new dojo.Color([255, 0, 0, 0.25])), 41 | 42 | // ArcGIS Online hot spot analysis service or custom hot spot analysis service 43 | hotSpotAnalysisService: { 44 | enable: true, 45 | url: "http://analysis.arcgis.com/arcgis/rest/services/tasks/GPServer/FindHotSpots", 46 | secure: true, 47 | tokenURL: "https://nrcgis.maps.arcgis.com/sharing", 48 | // Username/password for ArcGIS Online - Needs to be an organisation account 49 | username: "EagleTechnologyNRC", 50 | password: "*****" 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /config/Testing-config.js: -------------------------------------------------------------------------------- 1 | var configOptions; 2 | 3 | function initVariables() { 4 | configOptions = { 5 | // Title for site 6 | title: "ArcGIS Server Performance & Usage Stats", 7 | 8 | // Description for site 9 | description: "This site contains a number of useful applications enabling the ArcGIS administrator to view and analyse useful information about an ArcGIS Server site usage and performance. This information is displayed in the form of charts, maps and textual information.", 10 | 11 | // ArcGIS server site 12 | agsSite: { 13 | url: "http://laptop-sfw.etgnz.eagle.co.nz:6080/arcgis", 14 | username: "siteadmin", 15 | password: "adm1n" 16 | }, 17 | 18 | // Proxy to use 19 | proxyUrl: "proxy/proxy.ashx", // For testing use "proxy/proxy.ashx" 20 | 21 | // Initial map extent 22 | initialExtent: { xmin: 1778634, xmax: 1856528, ymin: 5433543, ymax: 5473165 }, 23 | 24 | // Spatial reference 25 | spatialReference: { WKID: 2193, name: "NZTM" }, 26 | 27 | // Basemap wrap around 28 | wraparound180: false, 29 | 30 | // Basemap to be used 31 | basemap: { id: "Streets", url: "http://services.arcgisonline.co.nz/arcgis/rest/services/Generic/newzealand/MapServer" }, 32 | 33 | // Default dropdown options 34 | defaultService: "Wellington/Carparking.MapServer", // Full service name e.g. PropertyAndBoundaries/PropertyInternal.MapServer 35 | defaultFilter: "Last 24 Hours", // Last Hour, Last 24 Hours, Last Week or Last 30 Days 36 | defaultGraphic: "Polygon", // Polygon, Point or Hot Spot 37 | 38 | // Popular extent graphics 39 | polygonSymbol: new esri.symbol.SimpleFillSymbol(esri.symbol.SimpleFillSymbol.STYLE_SOLID, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 2), new dojo.Color([255, 0, 0, 0.0])), 40 | pointSymbol: new esri.symbol.SimpleMarkerSymbol(esri.symbol.SimpleMarkerSymbol.STYLE_CIRCLE, 14, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 1), new dojo.Color([255, 0, 0, 0.25])), 41 | 42 | // ArcGIS Online hot spot analysis service or custom hot spot analysis service 43 | hotSpotAnalysisService: { 44 | enable: false, 45 | url: "http://analysis.arcgis.com/arcgis/rest/services/tasks/GPServer/FindHotSpots", 46 | secure: true, 47 | tokenURL: "https://wairarapa.maps.arcgis.com/sharing", 48 | // Username/password for ArcGIS Online - Needs to be an organisation account 49 | username: "EagleTechnologyWDC", 50 | password: "*****" 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /config/Wairarapa-config.js: -------------------------------------------------------------------------------- 1 | var configOptions; 2 | 3 | function initVariables() { 4 | configOptions = { 5 | // Title for site 6 | title: "Wairarapa ArcGIS Server Performance & Usage Stats", 7 | 8 | // Description for site 9 | description: "This site contains a number of useful applications enabling the ArcGIS administrator to view and analyse useful information about an ArcGIS Server site usage and performance. This information is displayed in the form of charts, maps and textual information.", 10 | 11 | // ArcGIS server site 12 | agsSite: { 13 | url: "http://gis.mstn.govt.nz/arcgis", 14 | username: "administrator", 15 | password: "*****" 16 | }, 17 | 18 | // Proxy to use 19 | proxyUrl: "http://gis.mstn.govt.nz/proxy/proxy.ashx", // For testing use "proxy/proxy.ashx" 20 | 21 | // Initial map extent 22 | initialExtent: { xmin: 1778634, xmax: 1856528, ymin: 5433543, ymax: 5473165 }, 23 | 24 | // Spatial reference 25 | spatialReference: { WKID: 2193, name: "NZTM" }, 26 | 27 | // Basemap wrap around 28 | wraparound180: false, 29 | 30 | // Basemap to be used 31 | basemap: { id: "Topographic", url: "http://gis.mstn.govt.nz/arcgis/rest/services/Basemaps/TopographicEsri/MapServer" }, 32 | 33 | // Default dropdown options 34 | defaultService: "PropertyAndBoundaries/PropertyInternal.MapServer", // Full service name e.g. PropertyAndBoundaries/PropertyInternal.MapServer 35 | defaultFilter: "Last 24 Hours", // Last Hour, Last 24 Hours, Last Week or Last 30 Days 36 | defaultGraphic: "Polygon", // Polygon, Point or Hot Spot 37 | 38 | // Popular extent graphics 39 | polygonSymbol: new esri.symbol.SimpleFillSymbol(esri.symbol.SimpleFillSymbol.STYLE_SOLID, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 2), new dojo.Color([255, 0, 0, 0.0])), 40 | pointSymbol: new esri.symbol.SimpleMarkerSymbol(esri.symbol.SimpleMarkerSymbol.STYLE_CIRCLE, 14, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 1), new dojo.Color([255, 0, 0, 0.25])), 41 | 42 | // ArcGIS Online hot spot analysis service or custom hot spot analysis service 43 | hotSpotAnalysisService: { 44 | enable: true, 45 | url: "http://analysis.arcgis.com/arcgis/rest/services/tasks/GPServer/FindHotSpots", 46 | secure: true, 47 | tokenURL: "https://wairarapa.maps.arcgis.com/sharing", 48 | // Username/password for ArcGIS Online - Needs to be an organisation account 49 | username: "WairarapaCouncil", 50 | password: "*****" 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /config/WestCoast-config.js: -------------------------------------------------------------------------------- 1 | var configOptions; 2 | 3 | function initVariables() { 4 | configOptions = { 5 | // Title for site 6 | title: "WCRC ArcGIS Server Performance & Usage Stats", 7 | 8 | // Description for site 9 | description: "This site contains a number of useful applications enabling the ArcGIS administrator to view and analyse useful information about an ArcGIS Server site usage and performance. This information is displayed in the form of charts, maps and textual information.", 10 | 11 | // ArcGIS server site 12 | agsSite: { 13 | url: "http://gis.wcrc.govt.nz/arcgis", 14 | username: "administrator", 15 | password: "*****" 16 | }, 17 | 18 | // Proxy to use 19 | proxyUrl: "http://gis.wcrc.govt.nz/proxy/proxy.ashx", // For testing use "proxy/proxy.ashx" 20 | 21 | // Initial map extent 22 | initialExtent: { xmin: 1044662, xmax: 1759038, ymin: 5052697, ymax: 5528948 }, 23 | 24 | // Spatial reference 25 | spatialReference: { WKID: 2193, name: "NZTM" }, 26 | 27 | // Basemap wrap around 28 | wraparound180: false, 29 | 30 | // Basemap to be used 31 | basemap: { id: "Topographic", url: "http://gis.wcrc.govt.nz/arcgis/rest/services/Basemaps/TopoEsri/MapServer" }, 32 | 33 | // Default dropdown options 34 | defaultService: "PropertyAndBoundaries/Property.MapServer", // Full service name e.g. Environment/BathingSites.MapServer 35 | defaultFilter: "Last 24 Hours", // Last Hour, Last 24 Hours, Last Week or Last 30 Days 36 | defaultGraphic: "Polygon", // Polygon, Point or Hot Spot 37 | 38 | // Popular extent graphics 39 | polygonSymbol: new esri.symbol.SimpleFillSymbol(esri.symbol.SimpleFillSymbol.STYLE_SOLID, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 2), new dojo.Color([255, 0, 0, 0.0])), 40 | pointSymbol: new esri.symbol.SimpleMarkerSymbol(esri.symbol.SimpleMarkerSymbol.STYLE_CIRCLE, 14, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 1), new dojo.Color([255, 0, 0, 0.25])), 41 | 42 | // ArcGIS Online hot spot analysis service or custom hot spot analysis service 43 | hotSpotAnalysisService: { 44 | enable: true, 45 | url: "http://analysis.arcgis.com/arcgis/rest/services/tasks/GPServer/FindHotSpots", 46 | secure: true, 47 | tokenURL: "https://wcrc.maps.arcgis.com/sharing", 48 | // Username/password for ArcGIS Online - Needs to be an organisation account 49 | username: "EagleTechnologyWCRC", 50 | password: "*****" 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | var configOptions; 2 | 3 | function initVariables() { 4 | configOptions = { 5 | // Title for site 6 | title: "ArcGIS Server Performance & Usage Stats", 7 | 8 | // Description for site 9 | description: "This site contains a number of useful applications enabling the ArcGIS administrator to view and analyse useful information about an ArcGIS Server site usage and performance. This information is displayed in the form of charts, maps and textual information.", 10 | 11 | // ArcGIS server site 12 | agsSite: { 13 | url: "http://laptop-sfw.etgnz.eagle.co.nz:6080/arcgis", 14 | username: "siteadmin", 15 | password: "adm1n" 16 | }, 17 | 18 | // Proxy to use 19 | proxyUrl: "proxy/proxy.ashx", // For testing use "proxy/proxy.ashx" 20 | 21 | // Initial map extent 22 | initialExtent: { xmin: 1778634, xmax: 1856528, ymin: 5433543, ymax: 5473165 }, 23 | 24 | // Spatial reference 25 | spatialReference: { WKID: 2193, name: "NZTM" }, 26 | 27 | // Basemap wrap around 28 | wraparound180: false, 29 | 30 | // Basemap to be used 31 | basemap: { id: "Streets", url: "http://services.arcgisonline.co.nz/arcgis/rest/services/Generic/newzealand/MapServer" }, 32 | 33 | // Default dropdown options 34 | defaultService: "Wellington/Carparking.MapServer", // Full service name e.g. PropertyAndBoundaries/PropertyInternal.MapServer 35 | defaultFilter: "Last 24 Hours", // Last Hour, Last 24 Hours, Last Week or Last 30 Days 36 | defaultGraphic: "Polygon", // Polygon, Point or Hot Spot 37 | 38 | // Popular extent graphics 39 | polygonSymbol: new esri.symbol.SimpleFillSymbol(esri.symbol.SimpleFillSymbol.STYLE_SOLID, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 2), new dojo.Color([255, 0, 0, 0.0])), 40 | pointSymbol: new esri.symbol.SimpleMarkerSymbol(esri.symbol.SimpleMarkerSymbol.STYLE_CIRCLE, 14, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 1), new dojo.Color([255, 0, 0, 0.25])), 41 | 42 | // ArcGIS Online hot spot analysis service or custom hot spot analysis service 43 | hotSpotAnalysisService: { 44 | enable: false, 45 | url: "http://analysis.arcgis.com/arcgis/rest/services/tasks/GPServer/FindHotSpots", 46 | secure: true, 47 | tokenURL: "https://wairarapa.maps.arcgis.com/sharing", 48 | // Username/password for ArcGIS Online - Needs to be an organisation account 49 | username: "WairarapaCouncil", 50 | password: "*****" 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | body{ 2 | height:100%; 3 | width:100%; 4 | margin:0; 5 | padding:0; 6 | } 7 | 8 | /* Service use line chart */ 9 | #servicesUseChart{ 10 | top: 10px; 11 | width: 100%; 12 | height: 400px; 13 | overflow: hidden; 14 | position: relative; 15 | background: #1e5799; 16 | background: -moz-linear-gradient(top, #1e5799 0%, #2989d8 30%, #0082e5 100%); 17 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#1e5799), color-stop(30%,#2989d8), color-stop(100%,#0082e5)); 18 | background: -webkit-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 19 | background: -o-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 20 | background: -ms-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 21 | background: linear-gradient(to bottom, #1e5799 0%,#2989d8 30%,#0082e5 100%); 22 | border: 2px solid #006699; 23 | } 24 | 25 | /* Log records pie chart */ 26 | #logRecordsPieChart{ 27 | top: 10px; 28 | width: 380px; 29 | height: 280px; 30 | overflow: hidden; 31 | position: relative; 32 | background: #1e5799; 33 | background: -moz-linear-gradient(top, #1e5799 0%, #2989d8 30%, #0082e5 100%); 34 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#1e5799), color-stop(30%,#2989d8), color-stop(100%,#0082e5)); 35 | background: -webkit-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 36 | background: -o-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 37 | background: -ms-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 38 | background: linear-gradient(to bottom, #1e5799 0%,#2989d8 30%,#0082e5 100%); 39 | border: 2px solid #006699; 40 | } 41 | 42 | /* Average process time gauge */ 43 | #processTimeGauge{ 44 | top: 20px; 45 | width: 350px; 46 | height: 200px; 47 | overflow: hidden; 48 | position: relative; 49 | background: #1e5799; 50 | background: -moz-linear-gradient(top, #1e5799 0%, #2989d8 30%, #0082e5 100%); 51 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#1e5799), color-stop(30%,#2989d8), color-stop(100%,#0082e5)); 52 | background: -webkit-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 53 | background: -o-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 54 | background: -ms-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 55 | background: linear-gradient(to bottom, #1e5799 0%,#2989d8 30%,#0082e5 100%); 56 | border: 2px solid #006699; 57 | } 58 | 59 | /* Average draw time gauge */ 60 | #drawTimeGauge{ 61 | top: 20px; 62 | width: 350px; 63 | height: 200px; 64 | overflow: hidden; 65 | position: relative; 66 | background: #1e5799; 67 | background: -moz-linear-gradient(top, #1e5799 0%, #2989d8 30%, #0082e5 100%); 68 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#1e5799), color-stop(30%,#2989d8), color-stop(100%,#0082e5)); 69 | background: -webkit-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 70 | background: -o-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 71 | background: -ms-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 72 | background: linear-gradient(to bottom, #1e5799 0%,#2989d8 30%,#0082e5 100%); 73 | border: 2px solid #006699; 74 | } 75 | 76 | /* Average query time gauge */ 77 | #queryTimeGauge{ 78 | top: 20px; 79 | width: 350px; 80 | height: 200px; 81 | overflow: hidden; 82 | position: relative; 83 | background: #1e5799; 84 | background: -moz-linear-gradient(top, #1e5799 0%, #2989d8 30%, #0082e5 100%); 85 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#1e5799), color-stop(30%,#2989d8), color-stop(100%,#0082e5)); 86 | background: -webkit-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 87 | background: -o-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 88 | background: -ms-linear-gradient(top, #1e5799 0%,#2989d8 30%,#0082e5 100%); 89 | background: linear-gradient(to bottom, #1e5799 0%,#2989d8 30%,#0082e5 100%); 90 | border: 2px solid #006699; 91 | } 92 | 93 | /* Application loading bar */ 94 | #appLoadBar { 95 | position: fixed; 96 | top: 50%; 97 | left: 50%; 98 | margin-left:-150px; 99 | margin-top:-25px; 100 | width: 300px; 101 | height: 50px; 102 | z-index:1000; 103 | } 104 | .progress-bar { 105 | position: fixed; 106 | top: 50%; 107 | left: 50%; 108 | margin-left:-150px; 109 | margin-top:-25px; 110 | width: 300px; 111 | height: 50px; 112 | z-index:1000; 113 | } 114 | #progressbarText { 115 | font-family: arial, verdana, tahoma; 116 | font-size: 1.2em; 117 | font-weight: bold; 118 | } 119 | #progressbarLogsText { 120 | font-family: arial, verdana, tahoma; 121 | font-size: 1.2em; 122 | font-weight: bold; 123 | } 124 | 125 | /* Secure services login popup */ 126 | .esriSignInDialog { 127 | background:#FFFFFF; 128 | color: #000000; 129 | border:1px solid #bfbfbf; 130 | font-family: arial, verdana, tahoma; 131 | font-size: 1.0em; 132 | padding: 3px 3px; 133 | } 134 | 135 | .dijitReset { 136 | margin: 0; 137 | border: 0; 138 | padding: 0; 139 | font: inherit; 140 | line-height: normal; 141 | color: inherit; 142 | } 143 | 144 | .dj_a11y .dijitReset { 145 | -moz-appearance: none; 146 | } 147 | 148 | .dijitInline { 149 | display: inline-block; 150 | #zoom: 1; 151 | #display:inline; 152 | border: 0; 153 | padding: 0; 154 | vertical-align: middle; 155 | #vertical-align: auto; 156 | } 157 | 158 | table.dijitInline { 159 | display: inline-table; 160 | box-sizing: content-box; 161 | -moz-box-sizing: content-box; 162 | } 163 | 164 | .dijitSelect .dijitButtonText { 165 | float: left; 166 | vertical-align: top; 167 | } 168 | 169 | .dijitSelect input.dijitInputField, .dijitTextBox input.dijitInputField { 170 | padding-left: 0 !important; 171 | padding-right: 0 !important; 172 | } 173 | 174 | .dijitValidationTextBox .dijitValidationContainer { 175 | display: none; 176 | } 177 | 178 | .dijitTeeny { 179 | font-size: 1px; 180 | line-height: 1px; 181 | } 182 | 183 | .dijitOffScreen { 184 | position: absolute !important; 185 | left: 50% !important; 186 | top: -10000px !important; 187 | } 188 | 189 | .dijitPopup { 190 | position: absolute; 191 | background-color: transparent; 192 | margin: 0; 193 | border: 0; 194 | padding: 0; 195 | } 196 | 197 | .dijitDisplayNone { 198 | display: none !important; 199 | } 200 | 201 | .dijitContainer { 202 | overflow: hidden; 203 | } 204 | 205 | .dj_a11y .dijitButtonNode { 206 | border: black outset medium !important; 207 | padding: 0 !important; 208 | } 209 | 210 | .dj_a11y .dijitArrowButton { 211 | padding: 0 !important; 212 | } 213 | 214 | .dj_a11y .dijitButtonContents { 215 | margin: 0.15em; 216 | } 217 | 218 | .dj_a11y .dijitTextBoxReadOnly .dijitInputField, .dj_a11y .dijitTextBoxReadOnly .dijitButtonNode { 219 | border-style: outset!important; 220 | border-width: medium!important; 221 | border-color: #999 !important; 222 | color: #999 !important; 223 | } 224 | 225 | .dijitButtonNode * { 226 | vertical-align: middle; 227 | } 228 | 229 | .dijitSelect .dijitArrowButtonInner, .dijitButtonNode .dijitArrowButtonInner { 230 | background: no-repeat center; 231 | width: 12px; 232 | height: 12px; 233 | direction: ltr; 234 | } 235 | 236 | .dijitLeft { 237 | background-position: left top; 238 | background-repeat: no-repeat; 239 | } 240 | 241 | .dijitStretch { 242 | white-space: nowrap; 243 | background-repeat: repeat-x; 244 | } 245 | 246 | .dijitRight { 247 | #display:inline; 248 | background-position: right top; 249 | background-repeat: no-repeat; 250 | } 251 | 252 | .dijitToggleButton, .dijitButton, .dijitDropDownButton, .dijitComboButton { 253 | margin: 0.2em; 254 | vertical-align: middle; 255 | } 256 | 257 | .dijitButtonContents { 258 | display: block; 259 | } 260 | 261 | td.dijitButtonContents { 262 | display: table-cell; 263 | } 264 | 265 | .dijitToolbar .dijitButtonContents { 266 | padding: 1px 2px; 267 | } 268 | 269 | .dijitSelect { 270 | border: 1px solid gray; 271 | } 272 | 273 | .dijitButtonNode { 274 | border: 1px solid gray; 275 | margin: 0; 276 | line-height: normal; 277 | vertical-align: middle; 278 | #vertical-align: auto; 279 | text-align: center; 280 | white-space: nowrap; 281 | } 282 | 283 | .dijitTextBox .dijitButtonNode { 284 | border-width: 0; 285 | } 286 | 287 | .dijitSelect, .dijitSelect *, .dijitButtonNode, .dijitButtonNode * { 288 | cursor: pointer; 289 | } 290 | 291 | .dijitTextBox { 292 | border: solid black 1px; 293 | #overflow: hidden; 294 | width: 15em; 295 | vertical-align: middle; 296 | } 297 | 298 | .dijitTextBox input:focus { 299 | outline: none; 300 | } 301 | 302 | .dijitTextBoxFocused { 303 | outline: 5px -webkit-focus-ring-color; 304 | } 305 | 306 | .dijitSelect input, .dijitTextBox input { 307 | float: left; 308 | } 309 | 310 | .dijitInputInner { 311 | border: 0 !important; 312 | background-color: transparent !important; 313 | width: 100% !important; 314 | padding-left: 0 !important; 315 | padding-right: 0 !important; 316 | margin-left: 0 !important; 317 | margin-right: 0 !important; 318 | } 319 | 320 | .dj_a11y .dijitTextBox input { 321 | margin: 0 !important; 322 | } 323 | 324 | .dijitValidationTextBoxError input.dijitValidationInner, .dijitSelect input, .dijitTextBox input.dijitArrowButtonInner { 325 | text-indent: -2em !important; 326 | direction: ltr !important; 327 | text-align: left !important; 328 | height: auto !important; 329 | #text-indent: 0 !important; 330 | #letter-spacing: -5em !important; 331 | #text-align: right !important; 332 | } 333 | 334 | .dijitSelect .dijitSelectLabel span { 335 | line-height: 100%; 336 | } 337 | 338 | .dj_a11y input.dijitValidationInner, .dj_a11y input.dijitArrowButtonInner { 339 | text-indent: 0 !important; 340 | width: 1em !important; 341 | #text-align: left !important; 342 | color: black !important; 343 | } 344 | 345 | .dijitValidationTextBoxError .dijitValidationContainer { 346 | display: inline; 347 | cursor: default; 348 | } 349 | 350 | .dj_a11y .dijitFocusedLabel { 351 | border: 1px dotted; 352 | outline: 0px !important; 353 | } 354 | 355 | .dijitTooltip { 356 | position: absolute; 357 | z-index: 2000; 358 | display: block; 359 | left: 0; 360 | top: -10000px; 361 | overflow: visible; 362 | } 363 | 364 | .dijitTooltipContainer { 365 | border: solid black 2px; 366 | background: #b8b5b5; 367 | color: black; 368 | font-size: small; 369 | } 370 | 371 | .dijitTooltipFocusNode { 372 | padding: 2px 2px 2px 2px; 373 | } 374 | 375 | .dijitTooltipConnector { 376 | position: absolute; 377 | } 378 | 379 | .dijitLayoutContainer { 380 | position: relative; 381 | display: block; 382 | overflow: hidden; 383 | } 384 | 385 | .dijitAlignTop, .dijitAlignBottom, .dijitAlignLeft, .dijitAlignRight { 386 | position: absolute; 387 | overflow: hidden; 388 | } 389 | 390 | .dijitBorderContainer, .dijitBorderContainerNoGutter { 391 | position: relative; 392 | overflow: hidden; 393 | z-index: 0; 394 | } 395 | 396 | .dijitBorderContainerPane, .dijitBorderContainerNoGutterPane { 397 | position: absolute !important; 398 | z-index: 2; 399 | } 400 | 401 | .dijitContentPane { 402 | display: block; 403 | overflow: auto; 404 | } 405 | 406 | .dijitContentPaneSingleChild { 407 | overflow: hidden; 408 | } 409 | 410 | .dijitTitlePane { 411 | display: block; 412 | overflow: hidden; 413 | } 414 | 415 | .dijitTitlePaneTitle { 416 | cursor: pointer; 417 | } 418 | 419 | .dijitDialog { 420 | position: absolute; 421 | z-index: 999; 422 | overflow: hidden; 423 | } 424 | 425 | .dijitDialogTitleBar { 426 | cursor: move; 427 | } 428 | 429 | .dijitDialogFixed .dijitDialogTitleBar { 430 | cursor: default; 431 | } 432 | 433 | .dijitDialogCloseIcon { 434 | cursor: pointer; 435 | } 436 | 437 | .dijitDialogUnderlayWrapper { 438 | position: absolute; 439 | left: 0; 440 | top: 0; 441 | z-index: 998; 442 | display: none; 443 | background: transparent !important; 444 | } 445 | 446 | .dijitDialogUnderlay { 447 | background: #eee; 448 | opacity: 0.5; 449 | } 450 | 451 | .dijitDialog .closeText { 452 | display: none; 453 | position: absolute; 454 | } 455 | 456 | .dj_a11y .dijitDialog .closeText { 457 | display: inline; 458 | } 459 | 460 | .dijitTextArea { 461 | width: 100%; 462 | overflow-y: auto; 463 | } 464 | 465 | .dijitSelect .dijitButtonContents { 466 | padding: 0; 467 | white-space: nowrap; 468 | text-align: left; 469 | border-style: none solid none none; 470 | border-width: 1px; 471 | } 472 | 473 | .dijitToggleButtonIconChar { 474 | display: none !important; 475 | } 476 | 477 | .dj_a11y .dijitToggleButton .dijitToggleButtonIconChar { 478 | display: inline !important; 479 | visibility: hidden; 480 | } -------------------------------------------------------------------------------- /images/Screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WestonSF/ArcGISServerMonitor/1787497b4cd73226a5f885eb18f0b02230d00b46/images/Screenshot.jpg -------------------------------------------------------------------------------- /libraries/bootstrap/3.0.2/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.2 by @fat and @mdo 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | * 6 | * Designed and built with all the love in the world by @mdo and @fat. 7 | */ 8 | 9 | .btn-default, 10 | .btn-primary, 11 | .btn-success, 12 | .btn-info, 13 | .btn-warning, 14 | .btn-danger { 15 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 16 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 17 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 18 | } 19 | 20 | .btn-default:active, 21 | .btn-primary:active, 22 | .btn-success:active, 23 | .btn-info:active, 24 | .btn-warning:active, 25 | .btn-danger:active, 26 | .btn-default.active, 27 | .btn-primary.active, 28 | .btn-success.active, 29 | .btn-info.active, 30 | .btn-warning.active, 31 | .btn-danger.active { 32 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 33 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 34 | } 35 | 36 | .btn:active, 37 | .btn.active { 38 | background-image: none; 39 | } 40 | 41 | .btn-default { 42 | text-shadow: 0 1px 0 #fff; 43 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e0e0e0)); 44 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); 45 | background-image: -moz-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); 46 | background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%); 47 | background-repeat: repeat-x; 48 | border-color: #dbdbdb; 49 | border-color: #ccc; 50 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 51 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 52 | } 53 | 54 | .btn-default:hover, 55 | .btn-default:focus { 56 | background-color: #e0e0e0; 57 | background-position: 0 -15px; 58 | } 59 | 60 | .btn-default:active, 61 | .btn-default.active { 62 | background-color: #e0e0e0; 63 | border-color: #dbdbdb; 64 | } 65 | 66 | .btn-primary { 67 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#2d6ca2)); 68 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 69 | background-image: -moz-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 70 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%); 71 | background-repeat: repeat-x; 72 | border-color: #2b669a; 73 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0); 74 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 75 | } 76 | 77 | .btn-primary:hover, 78 | .btn-primary:focus { 79 | background-color: #2d6ca2; 80 | background-position: 0 -15px; 81 | } 82 | 83 | .btn-primary:active, 84 | .btn-primary.active { 85 | background-color: #2d6ca2; 86 | border-color: #2b669a; 87 | } 88 | 89 | .btn-success { 90 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#419641)); 91 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 92 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #419641 100%); 93 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 94 | background-repeat: repeat-x; 95 | border-color: #3e8f3e; 96 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 97 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 98 | } 99 | 100 | .btn-success:hover, 101 | .btn-success:focus { 102 | background-color: #419641; 103 | background-position: 0 -15px; 104 | } 105 | 106 | .btn-success:active, 107 | .btn-success.active { 108 | background-color: #419641; 109 | border-color: #3e8f3e; 110 | } 111 | 112 | .btn-warning { 113 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#eb9316)); 114 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 115 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 116 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 117 | background-repeat: repeat-x; 118 | border-color: #e38d13; 119 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 120 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 121 | } 122 | 123 | .btn-warning:hover, 124 | .btn-warning:focus { 125 | background-color: #eb9316; 126 | background-position: 0 -15px; 127 | } 128 | 129 | .btn-warning:active, 130 | .btn-warning.active { 131 | background-color: #eb9316; 132 | border-color: #e38d13; 133 | } 134 | 135 | .btn-danger { 136 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c12e2a)); 137 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 138 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 139 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 140 | background-repeat: repeat-x; 141 | border-color: #b92c28; 142 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 143 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 144 | } 145 | 146 | .btn-danger:hover, 147 | .btn-danger:focus { 148 | background-color: #c12e2a; 149 | background-position: 0 -15px; 150 | } 151 | 152 | .btn-danger:active, 153 | .btn-danger.active { 154 | background-color: #c12e2a; 155 | border-color: #b92c28; 156 | } 157 | 158 | .btn-info { 159 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#2aabd2)); 160 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 161 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 162 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 163 | background-repeat: repeat-x; 164 | border-color: #28a4c9; 165 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 166 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 167 | } 168 | 169 | .btn-info:hover, 170 | .btn-info:focus { 171 | background-color: #2aabd2; 172 | background-position: 0 -15px; 173 | } 174 | 175 | .btn-info:active, 176 | .btn-info.active { 177 | background-color: #2aabd2; 178 | border-color: #28a4c9; 179 | } 180 | 181 | .thumbnail, 182 | .img-thumbnail { 183 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 184 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 185 | } 186 | 187 | .dropdown-menu > li > a:hover, 188 | .dropdown-menu > li > a:focus { 189 | background-color: #e8e8e8; 190 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); 191 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 192 | background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 193 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 194 | background-repeat: repeat-x; 195 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 196 | } 197 | 198 | .dropdown-menu > .active > a, 199 | .dropdown-menu > .active > a:hover, 200 | .dropdown-menu > .active > a:focus { 201 | background-color: #357ebd; 202 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 203 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 204 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 205 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 206 | background-repeat: repeat-x; 207 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 208 | } 209 | 210 | .navbar-default { 211 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8)); 212 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 213 | background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 214 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); 215 | background-repeat: repeat-x; 216 | border-radius: 4px; 217 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 218 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 219 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 220 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 221 | } 222 | 223 | .navbar-default .navbar-nav > .active > a { 224 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f3f3f3)); 225 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 226 | background-image: -moz-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 227 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%); 228 | background-repeat: repeat-x; 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0); 230 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 231 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 232 | } 233 | 234 | .navbar-brand, 235 | .navbar-nav > li > a { 236 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); 237 | } 238 | 239 | .navbar-inverse { 240 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222)); 241 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%); 242 | background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%); 243 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); 244 | background-repeat: repeat-x; 245 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 246 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 247 | } 248 | 249 | .navbar-inverse .navbar-nav > .active > a { 250 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#222222), to(#282828)); 251 | background-image: -webkit-linear-gradient(top, #222222 0%, #282828 100%); 252 | background-image: -moz-linear-gradient(top, #222222 0%, #282828 100%); 253 | background-image: linear-gradient(to bottom, #222222 0%, #282828 100%); 254 | background-repeat: repeat-x; 255 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0); 256 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 257 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 258 | } 259 | 260 | .navbar-inverse .navbar-brand, 261 | .navbar-inverse .navbar-nav > li > a { 262 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 263 | } 264 | 265 | .navbar-static-top, 266 | .navbar-fixed-top, 267 | .navbar-fixed-bottom { 268 | border-radius: 0; 269 | } 270 | 271 | .alert { 272 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 273 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 274 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 275 | } 276 | 277 | .alert-success { 278 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc)); 279 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 280 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 281 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 282 | background-repeat: repeat-x; 283 | border-color: #b2dba1; 284 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 285 | } 286 | 287 | .alert-info { 288 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0)); 289 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 290 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 291 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 292 | background-repeat: repeat-x; 293 | border-color: #9acfea; 294 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 295 | } 296 | 297 | .alert-warning { 298 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0)); 299 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 300 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 301 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 302 | background-repeat: repeat-x; 303 | border-color: #f5e79e; 304 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 305 | } 306 | 307 | .alert-danger { 308 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3)); 309 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 310 | background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 311 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 312 | background-repeat: repeat-x; 313 | border-color: #dca7a7; 314 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 315 | } 316 | 317 | .progress { 318 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5)); 319 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 320 | background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 321 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 322 | background-repeat: repeat-x; 323 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 324 | } 325 | 326 | .progress-bar { 327 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); 328 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%); 329 | background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); 330 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 331 | background-repeat: repeat-x; 332 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 333 | } 334 | 335 | .progress-bar-success { 336 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); 337 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 338 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); 339 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 340 | background-repeat: repeat-x; 341 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 342 | } 343 | 344 | .progress-bar-info { 345 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); 346 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 347 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 348 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 349 | background-repeat: repeat-x; 350 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 351 | } 352 | 353 | .progress-bar-warning { 354 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); 355 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 356 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 357 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 358 | background-repeat: repeat-x; 359 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 360 | } 361 | 362 | .progress-bar-danger { 363 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); 364 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 365 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); 366 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 367 | background-repeat: repeat-x; 368 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 369 | } 370 | 371 | .list-group { 372 | border-radius: 4px; 373 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 374 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 375 | } 376 | 377 | .list-group-item.active, 378 | .list-group-item.active:hover, 379 | .list-group-item.active:focus { 380 | text-shadow: 0 -1px 0 #3071a9; 381 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3)); 382 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%); 383 | background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%); 384 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 385 | background-repeat: repeat-x; 386 | border-color: #3278b3; 387 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 388 | } 389 | 390 | .panel { 391 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 392 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 393 | } 394 | 395 | .panel-default > .panel-heading { 396 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); 397 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 398 | background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 399 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 400 | background-repeat: repeat-x; 401 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 402 | } 403 | 404 | .panel-primary > .panel-heading { 405 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 406 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 407 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 408 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 409 | background-repeat: repeat-x; 410 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 411 | } 412 | 413 | .panel-success > .panel-heading { 414 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6)); 415 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 416 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 417 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 418 | background-repeat: repeat-x; 419 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 420 | } 421 | 422 | .panel-info > .panel-heading { 423 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3)); 424 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 425 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 426 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 427 | background-repeat: repeat-x; 428 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 429 | } 430 | 431 | .panel-warning > .panel-heading { 432 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc)); 433 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 434 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 435 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 436 | background-repeat: repeat-x; 437 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 438 | } 439 | 440 | .panel-danger > .panel-heading { 441 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc)); 442 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 443 | background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 444 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 445 | background-repeat: repeat-x; 446 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 447 | } 448 | 449 | .well { 450 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5)); 451 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 452 | background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 453 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 454 | background-repeat: repeat-x; 455 | border-color: #dcdcdc; 456 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 457 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 458 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 459 | } -------------------------------------------------------------------------------- /libraries/bootstrap/3.0.2/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.2 by @fat and @mdo 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | * 6 | * Designed and built with all the love in the world by @mdo and @fat. 7 | */ 8 | 9 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#e0e0e0));background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-moz-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe0e0e0',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#2d6ca2));background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:-moz-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);background-repeat:repeat-x;border-color:#2b669a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff2d6ca2',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#419641));background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);background-repeat:repeat-x;border-color:#3e8f3e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff419641',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#eb9316));background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);background-repeat:repeat-x;border-color:#e38d13;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffeb9316',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c12e2a));background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);background-repeat:repeat-x;border-color:#b92c28;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc12e2a',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#2aabd2));background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);background-repeat:repeat-x;border-color:#28a4c9;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2aabd2',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.navbar-default{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#f8f8f8));background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-moz-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff8f8f8',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f3f3f3));background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff3f3f3',GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.075);box-shadow:inset 0 3px 9px rgba(0,0,0,0.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-gradient(linear,left 0,left 100%,from(#3c3c3c),to(#222));background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-moz-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-gradient(linear,left 0,left 100%,from(#222),to(#282828));background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:-moz-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff282828',GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.25);box-shadow:inset 0 3px 9px rgba(0,0,0,0.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#c8e5bc));background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffc8e5bc',GradientType=0)}.alert-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#b9def0));background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffb9def0',GradientType=0)}.alert-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#f8efc0));background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fff8efc0',GradientType=0)}.alert-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#e7c3c3));background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-moz-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffe7c3c3',GradientType=0)}.progress{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff5f5f5',GradientType=0)}.progress-bar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.progress-bar-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.progress-bar-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.progress-bar-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.progress-bar-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3278b3));background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-moz-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3278b3',GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#d0e9c6));background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffd0e9c6',GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#c4e3f3));background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffc4e3f3',GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#faf2cc));background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fffaf2cc',GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#ebcccc));background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-moz-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffebcccc',GradientType=0)}.well{background-image:-webkit-gradient(linear,left 0,left 100%,from(#e8e8e8),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-moz-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)} -------------------------------------------------------------------------------- /libraries/bootstrap/3.0.2/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WestonSF/ArcGISServerMonitor/1787497b4cd73226a5f885eb18f0b02230d00b46/libraries/bootstrap/3.0.2/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /libraries/bootstrap/3.0.2/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WestonSF/ArcGISServerMonitor/1787497b4cd73226a5f885eb18f0b02230d00b46/libraries/bootstrap/3.0.2/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /libraries/bootstrap/3.0.2/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WestonSF/ArcGISServerMonitor/1787497b4cd73226a5f885eb18f0b02230d00b46/libraries/bootstrap/3.0.2/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /libraries/bootstrap/3.0.2/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.2 by @fat and @mdo 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | * 6 | * Designed and built with all the love in the world by @mdo and @fat. 7 | */ 8 | 9 | if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); -------------------------------------------------------------------------------- /proxy/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /proxy/proxy.ashx: -------------------------------------------------------------------------------- 1 | <%@ WebHandler Language="C#" Class="proxy" %> 2 | 3 | /* 4 | * DotNet proxy client. 5 | * 6 | * Version 1.1.1-beta 7 | * See https://github.com/Esri/resource-proxy for more information. 8 | * 9 | */ 10 | 11 | #define TRACE 12 | using System; 13 | using System.IO; 14 | using System.Web; 15 | using System.Xml.Serialization; 16 | using System.Web.Caching; 17 | using System.Collections.Concurrent; 18 | using System.Diagnostics; 19 | using System.Text.RegularExpressions; 20 | 21 | public class proxy : IHttpHandler { 22 | 23 | private static String version = "1.1.1-beta"; 24 | 25 | class RateMeter { 26 | double _rate; //internal rate is stored in requests per second 27 | int _countCap; 28 | double _count = 0; 29 | DateTime _lastUpdate = DateTime.Now; 30 | 31 | public RateMeter(int rate_limit, int rate_limit_period) { 32 | _rate = (double) rate_limit / rate_limit_period / 60; 33 | _countCap = rate_limit; 34 | } 35 | 36 | //called when rate-limited endpoint is invoked 37 | public bool click() { 38 | TimeSpan ts = DateTime.Now - _lastUpdate; 39 | _lastUpdate = DateTime.Now; 40 | //assuming uniform distribution of requests over time, 41 | //reducing the counter according to # of seconds passed 42 | //since last invocation 43 | _count = Math.Max(0, _count - ts.TotalSeconds * _rate); 44 | if (_count <= _countCap) { 45 | //good to proceed 46 | _count++; 47 | return true; 48 | } 49 | return false; 50 | } 51 | 52 | public bool canBeCleaned() { 53 | TimeSpan ts = DateTime.Now - _lastUpdate; 54 | return _count - ts.TotalSeconds * _rate <= 0; 55 | } 56 | } 57 | 58 | private static string PROXY_REFERER = "http://localhost/ArcGISServerMonitor/proxy/proxy.ashx"; 59 | private static string DEFAULT_OAUTH = "https://www.arcgis.com/sharing/oauth2/"; 60 | private static int CLEAN_RATEMAP_AFTER = 10000; //clean the rateMap every xxxx requests 61 | private static System.Net.IWebProxy SYSTEM_PROXY = System.Net.HttpWebRequest.DefaultWebProxy; // Use the default system proxy 62 | private static LogTraceListener logTraceListener = null; 63 | private static Object _rateMapLock = new Object(); 64 | 65 | public void ProcessRequest(HttpContext context) { 66 | 67 | 68 | if (logTraceListener == null) 69 | { 70 | logTraceListener = new LogTraceListener(); 71 | Trace.Listeners.Add(logTraceListener); 72 | } 73 | 74 | 75 | HttpResponse response = context.Response; 76 | if (context.Request.Url.Query.Length < 1) 77 | { 78 | string errorMsg = "This proxy does not support empty parameters."; 79 | log(TraceLevel.Error, errorMsg); 80 | sendErrorResponse(context.Response, null, errorMsg, System.Net.HttpStatusCode.BadRequest); 81 | return; 82 | } 83 | 84 | string uri = context.Request.Url.Query.Substring(1); 85 | 86 | //if uri is ping 87 | if (uri.Equals("ping", StringComparison.InvariantCultureIgnoreCase)) 88 | { 89 | ProxyConfig proxyConfig = ProxyConfig.GetCurrentConfig(); 90 | 91 | String checkConfig = (proxyConfig == null) ? "Not Readable" : "OK"; 92 | String checkLog = ""; 93 | if (checkConfig != "OK") 94 | { 95 | checkLog = "Can not verify"; 96 | } 97 | else 98 | { 99 | String filename = proxyConfig.logFile; 100 | checkLog = (filename != null && filename != "") ? "OK" : "Not Exist/Readable"; 101 | 102 | if (checkLog == "OK") 103 | log(TraceLevel.Info, "Log from ping"); 104 | 105 | } 106 | 107 | sendPingResponse(response, version, checkConfig, checkLog); 108 | return; 109 | } 110 | 111 | //if url is encoded, decode it. 112 | if (uri.StartsWith("http%3a%2f%2f", StringComparison.InvariantCultureIgnoreCase) || uri.StartsWith("https%3a%2f%2f", StringComparison.InvariantCultureIgnoreCase)) 113 | uri = HttpUtility.UrlDecode(uri); 114 | 115 | log(TraceLevel.Info, uri); 116 | ServerUrl serverUrl; 117 | try { 118 | serverUrl = getConfig().GetConfigServerUrl(uri); 119 | 120 | if (serverUrl == null) { 121 | //if no serverUrl found, send error message and get out. 122 | string errorMsg = "The request URL does not match with the ServerUrl in proxy.config! Please check the proxy.config!"; 123 | log(TraceLevel.Error, errorMsg); 124 | sendErrorResponse(context.Response, null, errorMsg, System.Net.HttpStatusCode.BadRequest); 125 | return; 126 | } 127 | } 128 | //if XML couldn't be parsed 129 | catch (InvalidOperationException ex) { 130 | 131 | string errorMsg = ex.InnerException.Message + " " + uri; 132 | log(TraceLevel.Error, errorMsg); 133 | sendErrorResponse(context.Response, null, errorMsg, System.Net.HttpStatusCode.InternalServerError); 134 | return; 135 | } 136 | //if mustMatch was set to true and URL wasn't in the list 137 | catch (ArgumentException ex) { 138 | string errorMsg = ex.Message + " " + uri; 139 | log(TraceLevel.Error, errorMsg); 140 | sendErrorResponse(context.Response, null, errorMsg, System.Net.HttpStatusCode.Forbidden); 141 | return; 142 | } 143 | //use actual request header instead of a placeholder, if present 144 | if (context.Request.Headers["referer"] != null) 145 | PROXY_REFERER = context.Request.Headers["referer"]; 146 | 147 | //referer 148 | //check against the list of referers if they have been specified in the proxy.config 149 | String[] allowedReferersArray = ProxyConfig.GetAllowedReferersArray(); 150 | if (allowedReferersArray != null && allowedReferersArray.Length > 0 && context.Request.Headers["referer"] != null) 151 | { 152 | PROXY_REFERER = context.Request.Headers["referer"]; 153 | string requestReferer = context.Request.Headers["referer"]; 154 | try 155 | { 156 | String checkValidUri = new UriBuilder(requestReferer.StartsWith("//") ? requestReferer.Substring(requestReferer.IndexOf("//") + 2) : requestReferer).Host; 157 | 158 | } 159 | catch (Exception e) 160 | { 161 | log(TraceLevel.Warning, "Proxy is being used from an invalid referer: " + context.Request.Headers["referer"]); 162 | sendErrorResponse(context.Response, "Error verifying referer. ", "403 - Forbidden: Access is denied.", System.Net.HttpStatusCode.Forbidden); 163 | return; 164 | } 165 | 166 | if (!checkReferer(allowedReferersArray, requestReferer)) 167 | { 168 | log(TraceLevel.Warning, "Proxy is being used from an unknown referer: " + context.Request.Headers["referer"]); 169 | sendErrorResponse(context.Response, "Unsupported referer. ", "403 - Forbidden: Access is denied.", System.Net.HttpStatusCode.Forbidden); 170 | } 171 | 172 | 173 | } 174 | 175 | //Check to see if allowed referer list is specified and reject if referer is null 176 | if (context.Request.Headers["referer"] == null && allowedReferersArray != null && !allowedReferersArray[0].Equals("*")) 177 | { 178 | log(TraceLevel.Warning, "Proxy is being called by a null referer. Access denied."); 179 | sendErrorResponse(response, "Current proxy configuration settings do not allow requests which do not include a referer header.", "403 - Forbidden: Access is denied.", System.Net.HttpStatusCode.Forbidden); 180 | return; 181 | } 182 | 183 | //Throttling: checking the rate limit coming from particular client IP 184 | if (serverUrl.RateLimit > -1) { 185 | lock (_rateMapLock) 186 | { 187 | ConcurrentDictionary ratemap = (ConcurrentDictionary)context.Application["rateMap"]; 188 | if (ratemap == null) 189 | { 190 | ratemap = new ConcurrentDictionary(); 191 | context.Application["rateMap"] = ratemap; 192 | context.Application["rateMap_cleanup_counter"] = 0; 193 | } 194 | string key = "[" + serverUrl.Url + "]x[" + context.Request.UserHostAddress + "]"; 195 | RateMeter rate; 196 | if (!ratemap.TryGetValue(key, out rate)) 197 | { 198 | rate = new RateMeter(serverUrl.RateLimit, serverUrl.RateLimitPeriod); 199 | ratemap.TryAdd(key, rate); 200 | } 201 | if (!rate.click()) 202 | { 203 | log(TraceLevel.Warning, " Pair " + key + " is throttled to " + serverUrl.RateLimit + " requests per " + serverUrl.RateLimitPeriod + " minute(s). Come back later."); 204 | sendErrorResponse(context.Response, "This is a metered resource, number of requests have exceeded the rate limit interval.", "Unable to proxy request for requested resource", (System.Net.HttpStatusCode)429); 205 | return; 206 | } 207 | 208 | //making sure the rateMap gets periodically cleaned up so it does not grow uncontrollably 209 | int cnt = (int)context.Application["rateMap_cleanup_counter"]; 210 | cnt++; 211 | if (cnt >= CLEAN_RATEMAP_AFTER) 212 | { 213 | cnt = 0; 214 | cleanUpRatemap(ratemap); 215 | } 216 | context.Application["rateMap_cleanup_counter"] = cnt; 217 | } 218 | } 219 | 220 | //readying body (if any) of POST request 221 | byte[] postBody = readRequestPostBody(context); 222 | string post = System.Text.Encoding.UTF8.GetString(postBody); 223 | 224 | System.Net.NetworkCredential credentials = null; 225 | string requestUri = uri; 226 | bool hasClientToken = false; 227 | string token = string.Empty; 228 | string tokenParamName = null; 229 | 230 | if ((serverUrl.HostRedirect != null) && (serverUrl.HostRedirect != string.Empty)) 231 | { 232 | requestUri = serverUrl.HostRedirect + new Uri(requestUri).PathAndQuery; 233 | } 234 | 235 | if (serverUrl.Domain != null) 236 | { 237 | credentials = new System.Net.NetworkCredential(serverUrl.Username, serverUrl.Password, serverUrl.Domain); 238 | } 239 | else 240 | { 241 | //if token comes with client request, it takes precedence over token or credentials stored in configuration 242 | hasClientToken = requestUri.Contains("?token=") || requestUri.Contains("&token=") || post.Contains("?token=") || post.Contains("&token="); 243 | 244 | if (!hasClientToken) 245 | { 246 | // Get new token and append to the request. 247 | // But first, look up in the application scope, maybe it's already there: 248 | token = (String)context.Application["token_for_" + serverUrl.Url]; 249 | bool tokenIsInApplicationScope = !String.IsNullOrEmpty(token); 250 | 251 | //if still no token, let's see if there is an access token or if are credentials stored in configuration which we can use to obtain new token 252 | if (!tokenIsInApplicationScope) 253 | { 254 | token = serverUrl.AccessToken; 255 | if (String.IsNullOrEmpty(token)) 256 | token = getNewTokenIfCredentialsAreSpecified(serverUrl, requestUri); 257 | } 258 | 259 | if (!String.IsNullOrEmpty(token) && !tokenIsInApplicationScope) 260 | { 261 | //storing the token in Application scope, to do not waste time on requesting new one untill it expires or the app is restarted. 262 | context.Application.Lock(); 263 | context.Application["token_for_" + serverUrl.Url] = token; 264 | context.Application.UnLock(); 265 | } 266 | } 267 | 268 | //name by which token parameter is passed (if url actually came from the list) 269 | tokenParamName = serverUrl != null ? serverUrl.TokenParamName : null; 270 | 271 | if (String.IsNullOrEmpty(tokenParamName)) 272 | tokenParamName = "token"; 273 | 274 | requestUri = addTokenToUri(requestUri, token, tokenParamName); 275 | } 276 | 277 | //forwarding original request 278 | System.Net.WebResponse serverResponse = null; 279 | try { 280 | serverResponse = forwardToServer(context, requestUri, postBody, credentials); 281 | } catch (System.Net.WebException webExc) { 282 | 283 | string errorMsg = webExc.Message + " " + uri; 284 | log(TraceLevel.Error, errorMsg); 285 | 286 | if (webExc.Response != null) 287 | { 288 | copyHeaders(webExc.Response as System.Net.HttpWebResponse, context.Response); 289 | 290 | using (Stream responseStream = webExc.Response.GetResponseStream()) 291 | { 292 | byte[] bytes = new byte[32768]; 293 | int bytesRead = 0; 294 | 295 | while ((bytesRead = responseStream.Read(bytes, 0, bytes.Length)) > 0) 296 | { 297 | responseStream.Write(bytes, 0, bytesRead); 298 | } 299 | 300 | context.Response.StatusCode = (int)(webExc.Response as System.Net.HttpWebResponse).StatusCode; 301 | context.Response.OutputStream.Write(bytes, 0, bytes.Length); 302 | } 303 | } 304 | else 305 | { 306 | System.Net.HttpStatusCode statusCode = System.Net.HttpStatusCode.InternalServerError; 307 | sendErrorResponse(context.Response, null, errorMsg, statusCode); 308 | } 309 | return; 310 | } 311 | 312 | if (string.IsNullOrEmpty(token) || hasClientToken) 313 | //if token is not required or provided by the client, just fetch the response as is: 314 | fetchAndPassBackToClient(serverResponse, response, true); 315 | else { 316 | //credentials for secured service have come from configuration file: 317 | //it means that the proxy is responsible for making sure they were properly applied: 318 | 319 | //first attempt to send the request: 320 | bool tokenRequired = fetchAndPassBackToClient(serverResponse, response, false); 321 | 322 | 323 | //checking if previously used token has expired and needs to be renewed 324 | if (tokenRequired) { 325 | log(TraceLevel.Info, "Renewing token and trying again."); 326 | //server returned error - potential cause: token has expired. 327 | //we'll do second attempt to call the server with renewed token: 328 | token = getNewTokenIfCredentialsAreSpecified(serverUrl, requestUri); 329 | serverResponse = forwardToServer(context, addTokenToUri(requestUri, token, tokenParamName), postBody); 330 | 331 | //storing the token in Application scope, to do not waste time on requesting new one untill it expires or the app is restarted. 332 | context.Application.Lock(); 333 | context.Application["token_for_" + serverUrl.Url] = token; 334 | context.Application.UnLock(); 335 | 336 | fetchAndPassBackToClient(serverResponse, response, true); 337 | } 338 | } 339 | response.End(); 340 | } 341 | 342 | public bool IsReusable { 343 | get { return true; } 344 | } 345 | 346 | /** 347 | * Private 348 | */ 349 | private byte[] readRequestPostBody(HttpContext context) { 350 | if (context.Request.InputStream.Length > 0) { 351 | byte[] bytes = new byte[context.Request.InputStream.Length]; 352 | context.Request.InputStream.Read(bytes, 0, (int)context.Request.InputStream.Length); 353 | return bytes; 354 | } 355 | return new byte[0]; 356 | } 357 | 358 | private System.Net.WebResponse forwardToServer(HttpContext context, string uri, byte[] postBody, System.Net.NetworkCredential credentials = null) 359 | { 360 | return 361 | postBody.Length > 0? 362 | doHTTPRequest(uri, postBody, "POST", context.Request.Headers["referer"], context.Request.ContentType, credentials): 363 | doHTTPRequest(uri, context.Request.HttpMethod, credentials); 364 | } 365 | 366 | /// 367 | /// Attempts to copy all headers from the fromResponse to the the toResponse. 368 | /// 369 | /// The response that we are copying the headers from 370 | /// The response that we are copying the headers to 371 | private void copyHeaders(System.Net.WebResponse fromResponse, HttpResponse toResponse) 372 | { 373 | foreach (var headerKey in fromResponse.Headers.AllKeys) 374 | { 375 | switch (headerKey.ToLower()) 376 | { 377 | case "content-type": 378 | case "transfer-encoding": 379 | case "accept-ranges": // Prevent requests for partial content 380 | continue; 381 | default: 382 | toResponse.AddHeader(headerKey, fromResponse.Headers[headerKey]); 383 | break; 384 | } 385 | } 386 | toResponse.ContentType = fromResponse.ContentType; 387 | } 388 | 389 | private bool fetchAndPassBackToClient(System.Net.WebResponse serverResponse, HttpResponse clientResponse, bool ignoreAuthenticationErrors) { 390 | if (serverResponse != null) { 391 | using (Stream byteStream = serverResponse.GetResponseStream()) { 392 | // Text response 393 | if (serverResponse.ContentType.Contains("text") || 394 | serverResponse.ContentType.Contains("json") || 395 | serverResponse.ContentType.Contains("xml")) { 396 | using (StreamReader sr = new StreamReader(byteStream)) { 397 | string strResponse = sr.ReadToEnd(); 398 | if ( 399 | !ignoreAuthenticationErrors 400 | && strResponse.Contains("error") 401 | && (strResponse.Contains("\"code\": 498") || strResponse.Contains("\"code\": 499") || strResponse.Contains("\"code\":498") || strResponse.Contains("\"code\":499")) 402 | ) 403 | return true; 404 | 405 | //Copy the header info and the content to the reponse to client 406 | copyHeaders(serverResponse, clientResponse); 407 | clientResponse.Write(strResponse); 408 | } 409 | } else { 410 | // Binary response (image, lyr file, other binary file) 411 | 412 | //Copy the header info to the reponse to client 413 | copyHeaders(serverResponse, clientResponse); 414 | // Tell client not to cache the image since it's dynamic 415 | clientResponse.CacheControl = "no-cache"; 416 | byte[] buffer = new byte[32768]; 417 | int read; 418 | while ((read = byteStream.Read(buffer, 0, buffer.Length)) > 0) 419 | { 420 | clientResponse.OutputStream.Write(buffer, 0, read); 421 | } 422 | clientResponse.OutputStream.Close(); 423 | } 424 | serverResponse.Close(); 425 | } 426 | } 427 | return false; 428 | } 429 | 430 | private System.Net.WebResponse doHTTPRequest(string uri, string method, System.Net.NetworkCredential credentials = null) 431 | { 432 | byte[] bytes = null; 433 | String contentType = null; 434 | log(TraceLevel.Info, "Sending request!"); 435 | 436 | if (method.Equals("POST")) 437 | { 438 | String[] uriArray = uri.Split(new char[] { '?' }, 2); 439 | uri = uriArray[0]; 440 | if (uriArray.Length > 1) 441 | { 442 | contentType = "application/x-www-form-urlencoded"; 443 | String queryString = uriArray[1]; 444 | 445 | bytes = System.Text.Encoding.UTF8.GetBytes(queryString); 446 | } 447 | } 448 | 449 | return doHTTPRequest(uri, bytes, method, PROXY_REFERER, contentType, credentials); 450 | } 451 | 452 | private System.Net.WebResponse doHTTPRequest(string uri, byte[] bytes, string method, string referer, string contentType, System.Net.NetworkCredential credentials = null) 453 | { 454 | System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(uri); 455 | req.ServicePoint.Expect100Continue = false; 456 | req.Referer = referer; 457 | req.Method = method; 458 | 459 | // Use the default system proxy 460 | req.Proxy = SYSTEM_PROXY; 461 | 462 | if (credentials != null) 463 | req.Credentials = credentials; 464 | 465 | if (bytes != null && bytes.Length > 0 || method == "POST") { 466 | req.Method = "POST"; 467 | req.ContentType = string.IsNullOrEmpty(contentType) ? "application/x-www-form-urlencoded" : contentType; 468 | if (bytes != null && bytes.Length > 0) 469 | req.ContentLength = bytes.Length; 470 | using (Stream outputStream = req.GetRequestStream()) { 471 | outputStream.Write(bytes, 0, bytes.Length); 472 | } 473 | } 474 | return req.GetResponse(); 475 | } 476 | 477 | private string webResponseToString(System.Net.WebResponse serverResponse) { 478 | using (Stream byteStream = serverResponse.GetResponseStream()) { 479 | using (StreamReader sr = new StreamReader(byteStream)) { 480 | string strResponse = sr.ReadToEnd(); 481 | return strResponse; 482 | } 483 | } 484 | } 485 | 486 | private string getNewTokenIfCredentialsAreSpecified(ServerUrl su, string reqUrl) { 487 | string token = ""; 488 | string infoUrl = ""; 489 | 490 | bool isUserLogin = !String.IsNullOrEmpty(su.Username) && !String.IsNullOrEmpty(su.Password); 491 | bool isAppLogin = !String.IsNullOrEmpty(su.ClientId) && !String.IsNullOrEmpty(su.ClientSecret); 492 | if (isUserLogin || isAppLogin) { 493 | log(TraceLevel.Info, "Matching credentials found in configuration file. OAuth 2.0 mode: " + isAppLogin); 494 | if (isAppLogin) { 495 | //OAuth 2.0 mode authentication 496 | //"App Login" - authenticating using client_id and client_secret stored in config 497 | su.OAuth2Endpoint = string.IsNullOrEmpty(su.OAuth2Endpoint) ? DEFAULT_OAUTH : su.OAuth2Endpoint; 498 | if (su.OAuth2Endpoint[su.OAuth2Endpoint.Length - 1] != '/') 499 | su.OAuth2Endpoint += "/"; 500 | log(TraceLevel.Info, "Service is secured by " + su.OAuth2Endpoint + ": getting new token..."); 501 | string uri = su.OAuth2Endpoint + "token?client_id=" + su.ClientId + "&client_secret=" + su.ClientSecret + "&grant_type=client_credentials&f=json"; 502 | string tokenResponse = webResponseToString(doHTTPRequest(uri, "POST")); 503 | token = extractToken(tokenResponse, "token"); 504 | if (!string.IsNullOrEmpty(token)) 505 | token = exchangePortalTokenForServerToken(token, su); 506 | } else { 507 | //standalone ArcGIS Server/ArcGIS Online token-based authentication 508 | 509 | //if a request is already being made to generate a token, just let it go 510 | if (reqUrl.ToLower().Contains("/generatetoken")) { 511 | string tokenResponse = webResponseToString(doHTTPRequest(reqUrl, "POST")); 512 | token = extractToken(tokenResponse, "token"); 513 | return token; 514 | } 515 | 516 | //lets look for '/rest/' in the requested URL (could be 'rest/services', 'rest/community'...) 517 | if (reqUrl.ToLower().Contains("/rest/")) 518 | infoUrl = reqUrl.Substring(0, reqUrl.IndexOf("/rest/", StringComparison.OrdinalIgnoreCase)); 519 | 520 | //if we don't find 'rest', lets look for the portal specific 'sharing' instead 521 | else if (reqUrl.ToLower().Contains("/sharing/")) { 522 | infoUrl = reqUrl.Substring(0, reqUrl.IndexOf("/sharing/", StringComparison.OrdinalIgnoreCase)); 523 | infoUrl = infoUrl + "/sharing"; 524 | } 525 | //additional condition: admin urls have a single generatetoken url 526 | else if (reqUrl.ToLower().Contains("/admin/")) { 527 | infoUrl = reqUrl.Substring(0, reqUrl.IndexOf("/admin/", StringComparison.OrdinalIgnoreCase)); 528 | infoUrl = infoUrl + "/tokens/generateToken"; 529 | } 530 | 531 | else 532 | throw new ApplicationException("Unable to determine the correct URL to request a token to access private resources."); 533 | 534 | String tokenServiceUri; 535 | if (infoUrl != "") { 536 | 537 | if (infoUrl.Contains("/generateToken")) { 538 | log(TraceLevel.Info," Querying admin's security endpoint..."); 539 | tokenServiceUri = infoUrl; 540 | } 541 | 542 | else { 543 | 544 | log(TraceLevel.Info," Querying security endpoint..."); 545 | infoUrl += "/rest/info?f=json"; 546 | //lets send a request to try and determine the URL of a token generator 547 | string infoResponse = webResponseToString(doHTTPRequest(infoUrl, "GET")); 548 | /*String*/ tokenServiceUri = getJsonValue(infoResponse, "tokenServicesUrl"); 549 | if (string.IsNullOrEmpty(tokenServiceUri)) { 550 | string owningSystemUrl = getJsonValue(infoResponse, "owningSystemUrl"); 551 | if (!string.IsNullOrEmpty(owningSystemUrl)) { 552 | tokenServiceUri = owningSystemUrl + "/sharing/generateToken"; 553 | } 554 | } 555 | } 556 | if (tokenServiceUri != "") { 557 | log(TraceLevel.Info," Service is secured by " + tokenServiceUri + ": getting new token..."); 558 | string uri = tokenServiceUri + "?f=json&request=getToken&referer=" + PROXY_REFERER + "&expiration=60&username=" + su.Username + "&password=" + su.Password; 559 | string tokenResponse = webResponseToString(doHTTPRequest(uri, "POST")); 560 | token = extractToken(tokenResponse, "token"); 561 | } 562 | 563 | } 564 | 565 | 566 | 567 | } 568 | } 569 | return token; 570 | } 571 | 572 | private bool checkWildcardSubdomain(String allowedReferer, String requestedReferer) 573 | { 574 | String[] allowedRefererParts = Regex.Split(allowedReferer, "(\\.)"); 575 | String[] refererParts = Regex.Split(requestedReferer, "(\\.)"); 576 | 577 | if (allowedRefererParts.Length != refererParts.Length) 578 | { 579 | return false; 580 | } 581 | 582 | int index = allowedRefererParts.Length - 1; 583 | while (index >= 0) 584 | { 585 | if (allowedRefererParts[index].Equals(refererParts[index], StringComparison.OrdinalIgnoreCase)) 586 | { 587 | index = index - 1; 588 | } 589 | else 590 | { 591 | if (allowedRefererParts[index].Equals("*")) 592 | { 593 | index = index - 1; 594 | continue; //next 595 | } 596 | return false; 597 | } 598 | } 599 | return true; 600 | } 601 | 602 | private bool pathMatched(String allowedRefererPath, String refererPath) 603 | { 604 | //If equal, return true 605 | if (refererPath.Equals(allowedRefererPath)) 606 | { 607 | return true; 608 | } 609 | 610 | //If the allowedRefererPath contain a ending star and match the begining part of referer, it is proper start with. 611 | if (allowedRefererPath.EndsWith("*")) 612 | { 613 | String allowedRefererPathShort = allowedRefererPath.Substring(0, allowedRefererPath.Length - 1); 614 | if (refererPath.ToLower().StartsWith(allowedRefererPathShort.ToLower())) 615 | { 616 | return true; 617 | } 618 | } 619 | return false; 620 | } 621 | 622 | private bool domainMatched(String allowedRefererDomain, String refererDomain) 623 | { 624 | if (allowedRefererDomain.Equals(refererDomain)){ 625 | return true; 626 | } 627 | 628 | //try if the allowed referer contains wildcard for subdomain 629 | if (allowedRefererDomain.Contains("*")){ 630 | if (checkWildcardSubdomain(allowedRefererDomain, refererDomain)){ 631 | return true;//return true if match wildcard subdomain 632 | } 633 | } 634 | 635 | return false; 636 | } 637 | 638 | private bool protocolMatch(String allowedRefererProtocol, String refererProtocol) 639 | { 640 | return allowedRefererProtocol.Equals(refererProtocol); 641 | } 642 | 643 | private String getDomainfromURL(String url, String protocol) 644 | { 645 | String domain = url.Substring(protocol.Length + 3); 646 | 647 | domain = domain.IndexOf('/') >= 0 ? domain.Substring(0, domain.IndexOf('/')) : domain; 648 | 649 | return domain; 650 | } 651 | 652 | private bool checkReferer(String[] allowedReferers, String referer) 653 | { 654 | if (allowedReferers != null && allowedReferers.Length > 0) 655 | { 656 | if (allowedReferers.Length == 1 && allowedReferers[0].Equals("*")) return true; //speed-up 657 | 658 | foreach (String allowedReferer in allowedReferers) 659 | { 660 | 661 | //Parse the protocol, domain and path of the referer 662 | String refererProtocol = referer.StartsWith("https://") ? "https" : "http"; 663 | String refererDomain = getDomainfromURL(referer, refererProtocol); 664 | String refererPath = referer.Substring(refererProtocol.Length + 3 + refererDomain.Length); 665 | 666 | 667 | String allowedRefererCannonical = null; 668 | 669 | //since the allowedReferer can be a malformed URL, we first construct a valid one to be compared with referer 670 | //if allowedReferer starts with https:// or http://, then exact match is required 671 | if (allowedReferer.StartsWith("https://") || allowedReferer.StartsWith("http://")) 672 | { 673 | allowedRefererCannonical = allowedReferer; 674 | 675 | } 676 | else 677 | { 678 | 679 | String protocol = refererProtocol; 680 | //if allowedReferer starts with "//" or no protocol, we use the one from refererURL to prefix to allowedReferer. 681 | if (allowedReferer.StartsWith("//")) 682 | { 683 | allowedRefererCannonical = protocol + ":" + allowedReferer; 684 | } 685 | else 686 | { 687 | //if the allowedReferer looks like "example.esri.com" 688 | allowedRefererCannonical = protocol + "://" + allowedReferer; 689 | } 690 | } 691 | 692 | //parse the protocol, domain and the path of the allowedReferer 693 | String allowedRefererProtocol = allowedRefererCannonical.StartsWith("https://") ? "https" : "http"; 694 | String allowedRefererDomain = getDomainfromURL(allowedRefererCannonical, allowedRefererProtocol); 695 | String allowedRefererPath = allowedRefererCannonical.Substring(allowedRefererProtocol.Length + 3 + allowedRefererDomain.Length); 696 | 697 | //Check if both domain and path match 698 | if (protocolMatch(allowedRefererProtocol, refererProtocol) && 699 | domainMatched(allowedRefererDomain, refererDomain) && 700 | pathMatched(allowedRefererPath, refererPath)) 701 | { 702 | return true; 703 | } 704 | } 705 | return false;//no-match 706 | } 707 | return true;//when allowedReferer is null, then allow everything 708 | } 709 | 710 | private string exchangePortalTokenForServerToken(string portalToken, ServerUrl su) { 711 | //ideally, we should POST the token request 712 | log(TraceLevel.Info," Exchanging Portal token for Server-specific token for " + su.Url + "..."); 713 | string uri = su.OAuth2Endpoint.Substring(0, su.OAuth2Endpoint.IndexOf("/oauth2/", StringComparison.OrdinalIgnoreCase)) + 714 | "/generateToken?token=" + portalToken + "&serverURL=" + su.Url + "&f=json"; 715 | string tokenResponse = webResponseToString(doHTTPRequest(uri, "GET")); 716 | return extractToken(tokenResponse, "token"); 717 | } 718 | 719 | 720 | private static void sendPingResponse(HttpResponse response, String version, String config, String log) 721 | { 722 | response.AddHeader("Content-Type", "application/json"); 723 | response.AddHeader("Accept-Encoding", "gzip"); 724 | String message = "{ " + 725 | "\"Proxy Version\": \"" + version + "\"" + 726 | ", \"Configuration File\": \"" + config + "\"" + 727 | ", \"Log File\": \"" + log + "\"" + 728 | "}"; 729 | response.StatusCode = 200; 730 | response.Write(message); 731 | response.Flush(); 732 | } 733 | 734 | private static void sendErrorResponse(HttpResponse response, String errorDetails, String errorMessage, System.Net.HttpStatusCode errorCode) 735 | { 736 | String message = string.Format("{{\"error\": {{\"code\": {0},\"message\":\"{1}\"", (int)errorCode, errorMessage); 737 | if (!string.IsNullOrEmpty(errorDetails)) 738 | message += string.Format(",\"details\":[\"message\":\"{0}\"]", errorDetails); 739 | message += "}}"; 740 | response.StatusCode = (int)errorCode; 741 | //custom status description for when the rate limit has been exceeded 742 | if (response.StatusCode == 429) { 743 | response.StatusDescription = "Too Many Requests"; 744 | } 745 | //this displays our customized error messages instead of IIS's custom errors 746 | response.TrySkipIisCustomErrors = true; 747 | response.Write(message); 748 | response.Flush(); 749 | } 750 | 751 | private static string getClientIp(HttpRequest request) 752 | { 753 | if (request == null) 754 | return null; 755 | string remoteAddr = request.ServerVariables["HTTP_X_FORWARDED_FOR"]; 756 | if (string.IsNullOrWhiteSpace(remoteAddr)) 757 | { 758 | remoteAddr = request.ServerVariables["REMOTE_ADDR"]; 759 | } 760 | else 761 | { 762 | // the HTTP_X_FORWARDED_FOR may contain an array of IP, this can happen if you connect through a proxy. 763 | string[] ipRange = remoteAddr.Split(','); 764 | remoteAddr = ipRange[ipRange.Length - 1]; 765 | } 766 | return remoteAddr; 767 | } 768 | 769 | private string addTokenToUri(string uri, string token, string tokenParamName) { 770 | if (!String.IsNullOrEmpty(token)) 771 | uri += uri.Contains("?")? "&" + tokenParamName + "=" + token : "?" + tokenParamName + "=" + token; 772 | return uri; 773 | } 774 | 775 | private string extractToken(string tokenResponse, string key) { 776 | string token = getJsonValue(tokenResponse, key); 777 | if (string.IsNullOrEmpty(token)) 778 | log(TraceLevel.Error," Token cannot be obtained: " + tokenResponse); 779 | else 780 | log(TraceLevel.Info," Token obtained: " + token); 781 | return token; 782 | } 783 | 784 | private string getJsonValue(string text, string key) { 785 | int i = text.IndexOf(key); 786 | String value = ""; 787 | if (i > -1) { 788 | value = text.Substring(text.IndexOf(':', i) + 1).Trim(); 789 | value = value.Length > 0 && value[0] == '"' ? 790 | value.Substring(1, value.IndexOf('"', 1) - 1): 791 | value = value.Substring(0, Math.Max(0, Math.Min(Math.Min(value.IndexOf(","), value.IndexOf("]")), value.IndexOf("}")))); 792 | } 793 | return value; 794 | } 795 | 796 | private void cleanUpRatemap(ConcurrentDictionary ratemap) { 797 | foreach (string key in ratemap.Keys){ 798 | RateMeter rate = ratemap[key]; 799 | if (rate.canBeCleaned()) 800 | ratemap.TryRemove(key, out rate); 801 | } 802 | } 803 | 804 | /** 805 | * Static 806 | */ 807 | private static ProxyConfig getConfig() { 808 | ProxyConfig config = ProxyConfig.GetCurrentConfig(); 809 | if (config != null) 810 | return config; 811 | else 812 | throw new ApplicationException("The proxy configuration file cannot be found, or is not readable."); 813 | } 814 | 815 | //writing Log file 816 | private static void log(TraceLevel logLevel, string msg) { 817 | string logMessage = string.Format("{0} {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), msg); 818 | 819 | ProxyConfig config = ProxyConfig.GetCurrentConfig(); 820 | TraceSwitch ts = null; 821 | 822 | if (config.logLevel != null) 823 | { 824 | ts = new TraceSwitch("TraceLevelSwitch2", "TraceSwitch in the proxy.config file", config.logLevel); 825 | } 826 | else 827 | { 828 | ts = new TraceSwitch("TraceLevelSwitch2", "TraceSwitch in the proxy.config file", "Error"); 829 | config.logLevel = "Error"; 830 | } 831 | 832 | Trace.WriteLineIf(logLevel <= ts.Level, logMessage); 833 | } 834 | 835 | private static object _lockobject = new object(); 836 | 837 | } 838 | 839 | class LogTraceListener : TraceListener 840 | { 841 | private static object _lockobject = new object(); 842 | public override void Write(string message) 843 | { 844 | //Only log messages to disk if logFile has value in configuration, otherwise log nothing. 845 | ProxyConfig config = ProxyConfig.GetCurrentConfig(); 846 | 847 | if (config.LogFile != null) 848 | { 849 | string log = config.LogFile; 850 | if (!log.Contains("\\") || log.Contains(".\\")) 851 | { 852 | if (log.Contains(".\\")) //If this type of relative pathing .\log.txt 853 | { 854 | log = log.Replace(".\\", ""); 855 | } 856 | string configDirectory = HttpContext.Current.Server.MapPath("proxy.config"); //Cannot use System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath b/ config may be in a child directory 857 | string path = configDirectory.Replace("proxy.config", ""); 858 | log = path + log; 859 | } 860 | 861 | lock (_lockobject) 862 | { 863 | using (StreamWriter sw = File.AppendText(log)) 864 | { 865 | sw.Write(message); 866 | } 867 | } 868 | } 869 | } 870 | 871 | 872 | public override void WriteLine(string message) 873 | { 874 | //Only log messages to disk if logFile has value in configuration, otherwise log nothing. 875 | ProxyConfig config = ProxyConfig.GetCurrentConfig(); 876 | if (config.LogFile != null) 877 | { 878 | string log = config.LogFile; 879 | if (!log.Contains("\\") || log.Contains(".\\")) 880 | { 881 | if (log.Contains(".\\")) //If this type of relative pathing .\log.txt 882 | { 883 | log = log.Replace(".\\", ""); 884 | } 885 | string configDirectory = HttpContext.Current.Server.MapPath("proxy.config"); //Cannot use System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath b/ config may be in a child directory 886 | string path = configDirectory.Replace("proxy.config", ""); 887 | log = path + log; 888 | } 889 | 890 | lock (_lockobject) 891 | { 892 | using (StreamWriter sw = File.AppendText(log)) 893 | { 894 | sw.WriteLine(message); 895 | } 896 | } 897 | } 898 | } 899 | 900 | } 901 | 902 | 903 | [XmlRoot("ProxyConfig")] 904 | public class ProxyConfig 905 | { 906 | private static object _lockobject = new object(); 907 | public static ProxyConfig LoadProxyConfig(string fileName) { 908 | ProxyConfig config = null; 909 | lock (_lockobject) { 910 | if (System.IO.File.Exists(fileName)) { 911 | XmlSerializer reader = new XmlSerializer(typeof(ProxyConfig)); 912 | using (System.IO.StreamReader file = new System.IO.StreamReader(fileName)) { 913 | try { 914 | config = (ProxyConfig)reader.Deserialize(file); 915 | } 916 | catch (Exception ex) { 917 | throw ex; 918 | } 919 | } 920 | } 921 | } 922 | return config; 923 | } 924 | 925 | public static ProxyConfig GetCurrentConfig() { 926 | ProxyConfig config = HttpRuntime.Cache["proxyConfig"] as ProxyConfig; 927 | if (config == null) { 928 | string fileName = HttpContext.Current.Server.MapPath("proxy.config"); 929 | config = LoadProxyConfig(fileName); 930 | if (config != null) { 931 | CacheDependency dep = new CacheDependency(fileName); 932 | HttpRuntime.Cache.Insert("proxyConfig", config, dep); 933 | } 934 | } 935 | return config; 936 | } 937 | 938 | //referer 939 | //create an array with valid referers using the allowedReferers String that is defined in the proxy.config 940 | public static String[] GetAllowedReferersArray() 941 | { 942 | if (allowedReferers == null) 943 | return null; 944 | 945 | return allowedReferers.Split(','); 946 | } 947 | 948 | //referer 949 | //check if URL starts with prefix... 950 | public static bool isUrlPrefixMatch(String prefix, String uri) 951 | { 952 | 953 | return uri.ToLower().StartsWith(prefix.ToLower()) || 954 | uri.ToLower().Replace("https://", "http://").StartsWith(prefix.ToLower()) || 955 | uri.ToLower().Substring(uri.IndexOf("//")).StartsWith(prefix.ToLower()); 956 | } 957 | 958 | ServerUrl[] serverUrls; 959 | public String logFile; 960 | public String logLevel; 961 | bool mustMatch; 962 | //referer 963 | static String allowedReferers; 964 | 965 | [XmlArray("serverUrls")] 966 | [XmlArrayItem("serverUrl")] 967 | public ServerUrl[] ServerUrls { 968 | get { return this.serverUrls; } 969 | set 970 | { 971 | this.serverUrls = value; 972 | } 973 | } 974 | [XmlAttribute("mustMatch")] 975 | public bool MustMatch { 976 | get { return mustMatch; } 977 | set 978 | { mustMatch = value; } 979 | } 980 | 981 | //logFile 982 | [XmlAttribute("logFile")] 983 | public String LogFile 984 | { 985 | get { return logFile; } 986 | set 987 | { logFile = value; } 988 | } 989 | 990 | //logLevel 991 | [XmlAttribute("logLevel")] 992 | public String LogLevel 993 | { 994 | get { return logLevel; } 995 | set 996 | { logLevel = value; } 997 | } 998 | 999 | 1000 | //referer 1001 | [XmlAttribute("allowedReferers")] 1002 | public string AllowedReferers 1003 | { 1004 | get { return allowedReferers; } 1005 | set 1006 | { 1007 | allowedReferers = Regex.Replace(value, @"\s", ""); 1008 | } 1009 | } 1010 | 1011 | public ServerUrl GetConfigServerUrl(string uri) { 1012 | //split both request and proxy.config urls and compare them 1013 | string[] uriParts = uri.Split(new char[] {'/','?'}, StringSplitOptions.RemoveEmptyEntries); 1014 | string[] configUriParts = new string[] {}; 1015 | 1016 | foreach (ServerUrl su in serverUrls) { 1017 | //if a relative path is specified in the proxy.config, append what's in the request itself 1018 | if (!su.Url.StartsWith("http")) 1019 | su.Url = su.Url.Insert(0, uriParts[0]); 1020 | 1021 | configUriParts = su.Url.Split(new char[] { '/','?' }, StringSplitOptions.RemoveEmptyEntries); 1022 | 1023 | //if the request has less parts than the config, don't allow 1024 | if (configUriParts.Length > uriParts.Length) continue; 1025 | 1026 | int i = 0; 1027 | for (i = 0; i < configUriParts.Length; i++) { 1028 | 1029 | if (!configUriParts[i].ToLower().Equals(uriParts[i].ToLower())) break; 1030 | } 1031 | if (i == configUriParts.Length) { 1032 | //if the urls don't match exactly, and the individual matchAll tag is 'false', don't allow 1033 | if (configUriParts.Length == uriParts.Length || su.MatchAll) 1034 | return su; 1035 | } 1036 | } 1037 | 1038 | if (!mustMatch) 1039 | { 1040 | return new ServerUrl(uri); 1041 | } 1042 | else 1043 | { 1044 | throw new ArgumentException("Proxy has not been set up for this URL. Make sure there is a serverUrl in the configuration file that matches: " + uri); 1045 | } 1046 | } 1047 | } 1048 | 1049 | public class ServerUrl { 1050 | string url; 1051 | string hostRedirect; 1052 | bool matchAll; 1053 | string oauth2Endpoint; 1054 | string domain; 1055 | string username; 1056 | string password; 1057 | string clientId; 1058 | string clientSecret; 1059 | string accessToken; 1060 | string tokenParamName; 1061 | string rateLimit; 1062 | string rateLimitPeriod; 1063 | 1064 | private ServerUrl() 1065 | { 1066 | } 1067 | 1068 | public ServerUrl(String url) 1069 | { 1070 | this.url = url; 1071 | } 1072 | 1073 | [XmlAttribute("url")] 1074 | public string Url { 1075 | get { return url; } 1076 | set { url = value; } 1077 | } 1078 | [XmlAttribute("hostRedirect")] 1079 | public string HostRedirect 1080 | { 1081 | get { return hostRedirect; } 1082 | set { hostRedirect = value; } 1083 | } 1084 | [XmlAttribute("matchAll")] 1085 | public bool MatchAll { 1086 | get { return matchAll; } 1087 | set { matchAll = value; } 1088 | } 1089 | [XmlAttribute("oauth2Endpoint")] 1090 | public string OAuth2Endpoint { 1091 | get { return oauth2Endpoint; } 1092 | set { oauth2Endpoint = value; } 1093 | } 1094 | [XmlAttribute("domain")] 1095 | public string Domain 1096 | { 1097 | get { return domain; } 1098 | set { domain = value; } 1099 | } 1100 | [XmlAttribute("username")] 1101 | public string Username { 1102 | get { return username; } 1103 | set { username = value; } 1104 | } 1105 | [XmlAttribute("password")] 1106 | public string Password { 1107 | get { return password; } 1108 | set { password = value; } 1109 | } 1110 | [XmlAttribute("clientId")] 1111 | public string ClientId { 1112 | get { return clientId; } 1113 | set { clientId = value; } 1114 | } 1115 | [XmlAttribute("clientSecret")] 1116 | public string ClientSecret { 1117 | get { return clientSecret; } 1118 | set { clientSecret = value; } 1119 | } 1120 | [XmlAttribute("accessToken")] 1121 | public string AccessToken { 1122 | get { return accessToken; } 1123 | set { accessToken = value; } 1124 | } 1125 | [XmlAttribute("tokenParamName")] 1126 | public string TokenParamName { 1127 | get { return tokenParamName; } 1128 | set { tokenParamName = value; } 1129 | } 1130 | [XmlAttribute("rateLimit")] 1131 | public int RateLimit { 1132 | get { return string.IsNullOrEmpty(rateLimit)? -1 : int.Parse(rateLimit); } 1133 | set { rateLimit = value.ToString(); } 1134 | } 1135 | [XmlAttribute("rateLimitPeriod")] 1136 | public int RateLimitPeriod { 1137 | get { return string.IsNullOrEmpty(rateLimitPeriod)? 60 : int.Parse(rateLimitPeriod); } 1138 | set { rateLimitPeriod = value.ToString(); } 1139 | } 1140 | } 1141 | -------------------------------------------------------------------------------- /proxy/proxy.config: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /proxy/proxy.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------